Skip to content

Commit

Permalink
add where returnType
Browse files Browse the repository at this point in the history
  • Loading branch information
prostgles committed Oct 22, 2023
1 parent 1c73272 commit 26379d4
Show file tree
Hide file tree
Showing 11 changed files with 174 additions and 116 deletions.
14 changes: 7 additions & 7 deletions lib/DboBuilder/QueryBuilder/getNewQuery.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import { get } from "../../utils";
import { TableHandler } from "../TableHandler/TableHandler";
import { TableRule } from "../../PublishParser";
import { DetailedJoinSelect, JoinPath, JoinSelect, RawJoinPath, SelectParams, SimpleJoinSelect, getKeys } from "prostgles-types";
import { Filter, LocalParams } from "../../DboBuilder";
import { SelectParams, ColumnInfo, getKeys, DetailedJoinSelect, SimpleJoinSelect, JoinPath, JoinSelect, RawJoinPath } from "prostgles-types";
import { COMPUTED_FIELDS, FUNCTIONS } from "./Functions";
import { NewQuery, NewQueryJoin, SelectItemBuilder } from "./QueryBuilder";
import { TableRule } from "../../PublishParser";
import { get } from "../../utils";
import { ViewHandler } from "../ViewHandler/ViewHandler";
import { parseJoinPath } from "../ViewHandler/parseJoinPath";
import { prepareSortItems } from "../ViewHandler/prepareSortItems";
import { COMPUTED_FIELDS, FUNCTIONS } from "./Functions";
import { NewQuery, NewQueryJoin, SelectItemBuilder } from "./QueryBuilder";

const JOIN_KEYS = ["$innerJoin", "$leftJoin"] as const;
type ParsedJoin =
Expand Down Expand Up @@ -63,7 +63,7 @@ const parseJoinSelect = (joinParams: string | JoinSelect): ParsedJoin => {
}

export async function getNewQuery(
_this: TableHandler,
_this: ViewHandler,
filter: Filter,
selectParams: (SelectParams & { alias?: string }) = {},
param3_unused = null,
Expand Down
131 changes: 79 additions & 52 deletions lib/DboBuilder/delete.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,15 +47,15 @@ export async function _delete(this: TableHandler, filter?: Filter, params?: Dele
}

let queryType: keyof pgPromise.ITask<{}> = 'none';
let _query = `DELETE FROM ${this.escapedName} `;
let queryWithoutRLS = `DELETE FROM ${this.escapedName} `;
const filterOpts = (await this.prepareWhere({
filter,
forcedFilter,
filterFields,
localParams,
tableRule: table_rules
}))
_query += filterOpts.where;
queryWithoutRLS += filterOpts.where;
if (validate) {
const _filter = filterOpts.filter;
await validate(_filter);
Expand All @@ -68,67 +68,94 @@ export async function _delete(this: TableHandler, filter?: Filter, params?: Dele
throw "Returning dissallowed";
}
returningQuery = this.makeReturnQuery(await this.prepareReturning(returning, this.parseFieldFilter(returningFields)));
_query += returningQuery
queryWithoutRLS += returningQuery
}

_query = withUserRLS(localParams, _query);
if (returnQuery) return _query;
const queryWithRLS = withUserRLS(localParams, queryWithoutRLS);
if (returnQuery) return queryWithRLS;

/**
* Delete file
*/
if (this.is_media) {
if (!this.dboBuilder.prostgles.fileManager) throw new Error("fileManager missing")
if (this.dboBuilder.prostgles.opts.fileTable?.delayedDelete) {
return this.dbHandler[queryType](`UPDATE ${asName(this.name)} SET deleted = now() ${filterOpts.where} ${returningQuery};`)
} else {

const txDelete = async (tbl: TableHandler) => {
if (!tbl.tx) throw new Error("Missing transaction object tx");
let files: { id: string; name: string }[] = [];
const totalFiles = await tbl.count(filterOpts.filter);
do {
const batch = await tbl.find(filterOpts.filter, { limit: 100, offset: files.length });
files = files.concat(batch);
} while(files.length < totalFiles)

const fileManager = tbl.dboBuilder.prostgles.fileManager
if (!fileManager) throw new Error("fileManager missing");

for await (const file of files) {
await tbl.tx.t.any(`DELETE FROM ${asName(this.name)} WHERE id = \${id}`, file);
}
/** If any table delete fails then do not delete files */
for await (const file of files) {
await fileManager.deleteFile(file.name);
/** TODO: Keep track of deleted files in case of failure */
// await tbl.t?.any(`UPDATE ${asName(this.name)} SET deleted = NOW(), deleted_from_storage = NOW() WHERE id = ` + "${id}", file);
}

if (returning) {
return files.map(f => pickKeys(f, ["id", "name"]));
}

return undefined;
}

if (localParams?.tx?.dbTX) {
return txDelete(localParams.tx.dbTX[this.name] as TableHandler)
} else if (this.tx) {
return txDelete(this)
} else {

return this.dboBuilder.getTX(tx => {
return txDelete(tx[this.name] as TableHandler)
})
}
}
return onDeleteFromFileTable.bind(this)({
localParams,
queryType,
returningQuery: returnQuery? returnQuery : undefined,
filterOpts,
});
}

return runQueryReturnType(_query, params?.returnType, this, localParams);
return runQueryReturnType({
queryWithoutRLS,
queryWithRLS,
newQuery: undefined,
returnType: params?.returnType,
handler: this,
localParams
});

} catch (e) {
if (localParams && localParams.testRule) throw e;
throw parseError(e, `dbo.${this.name}.delete(${JSON.stringify(filter || {}, null, 2)}, ${JSON.stringify(params || {}, null, 2)})`);
}
}
}


type OnDeleteFromFileTableArgs = {
localParams: LocalParams | undefined;
queryType: "one" | "none" | "many" | "any";
returningQuery: undefined | string;
filterOpts: {
where: string;
filter: AnyObject;
}
}
async function onDeleteFromFileTable(this: TableHandler, { localParams, queryType, returningQuery, filterOpts }: OnDeleteFromFileTableArgs){

if (!this.dboBuilder.prostgles.fileManager) throw new Error("fileManager missing")
if (this.dboBuilder.prostgles.opts.fileTable?.delayedDelete) {
return this.dbHandler[queryType](`UPDATE ${asName(this.name)} SET deleted = now() ${filterOpts.where} ${returningQuery ?? ""};`)
} else {

const txDelete = async (tbl: TableHandler) => {
if (!tbl.tx) throw new Error("Missing transaction object tx");
let files: { id: string; name: string }[] = [];
const totalFiles = await tbl.count(filterOpts.filter);
do {
const batch = await tbl.find(filterOpts.filter, { limit: 100, offset: files.length });
files = files.concat(batch);
} while(files.length < totalFiles)

const fileManager = tbl.dboBuilder.prostgles.fileManager
if (!fileManager) throw new Error("fileManager missing");

for await (const file of files) {
await tbl.tx.t.any(`DELETE FROM ${asName(this.name)} WHERE id = \${id}`, file);
}
/** If any table delete fails then do not delete files */
for await (const file of files) {
await fileManager.deleteFile(file.name);
/** TODO: Keep track of deleted files in case of failure */
// await tbl.t?.any(`UPDATE ${asName(this.name)} SET deleted = NOW(), deleted_from_storage = NOW() WHERE id = ` + "${id}", file);
}

if (returningQuery) {
return files.map(f => pickKeys(f, ["id", "name"]));
}

return undefined;
}

if (localParams?.tx?.dbTX) {
return txDelete(localParams.tx.dbTX[this.name] as TableHandler)
} else if (this.tx) {
return txDelete(this)
} else {

return this.dboBuilder.getTX(tx => {
return txDelete(tx[this.name] as TableHandler)
})
}
}
}
72 changes: 51 additions & 21 deletions lib/DboBuilder/find.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,13 @@ import { getNewQuery } from "./QueryBuilder/getNewQuery";
import { getSelectQuery } from "./QueryBuilder/getSelectQuery";
import { TableHandler } from "./TableHandler/TableHandler";
import { ViewHandler } from "./ViewHandler/ViewHandler";
import { NewQuery } from "./QueryBuilder/QueryBuilder";

export const find = async function(this: ViewHandler, filter?: Filter, selectParams?: SelectParams, param3_unused?: undefined, tableRules?: TableRule, localParams?: LocalParams): Promise<any[]> {
export const find = async function(this: ViewHandler, filter?: Filter, selectParams?: SelectParams, _?: undefined, tableRules?: TableRule, localParams?: LocalParams): Promise<any[]> {
try {
await this._log({ command: "find", localParams, data: { filter, selectParams } });
filter = filter || {};
const allowedReturnTypes: Array<SelectParams["returnType"]> = ["row", "value", "values", "statement"]
const allowedReturnTypes = Object.keys({ row: 1, statement: 1, value: 1, values: 1, "statement-no-rls": 1, "statement-where": 1 } satisfies Record<Required<SelectParams>["returnType"], 1>);
const { returnType } = selectParams || {};
if (returnType && !allowedReturnTypes.includes(returnType)) {
throw `returnType (${returnType}) can only be ${allowedReturnTypes.join(" OR ")}`
Expand All @@ -22,7 +23,10 @@ export const find = async function(this: ViewHandler, filter?: Filter, selectPar

if (testRule) return [];
if (selectParams) {
const good_params: Array<keyof SelectParams> = ["select", "orderBy", "offset", "limit", "returnType", "groupBy"];
const good_params = Object.keys({
"select": 1, "orderBy": 1, "offset": 1, "limit": 1, "returnType": 1, "groupBy": 1
} satisfies Record<keyof SelectParams, 1>);

const bad_params = Object.keys(selectParams).filter(k => !good_params.includes(k as any));
if (bad_params && bad_params.length) throw "Invalid params: " + bad_params.join(", ") + " \n Expecting: " + good_params.join(", ");
}
Expand All @@ -41,18 +45,18 @@ export const find = async function(this: ViewHandler, filter?: Filter, selectPar

const _selectParams = selectParams ?? {}
const selectParamsLimitCheck = localParams?.bypassLimit && !Number.isFinite(_selectParams.limit)? { ..._selectParams, limit: null } : { limit: 1000, ..._selectParams }
const q = await getNewQuery(
this as unknown as TableHandler,
const newQuery = await getNewQuery(
this,
filter,
selectParamsLimitCheck,
param3_unused,
_,
tableRules,
localParams,
);

const queryWithoutRLS = getSelectQuery(
this,
q,
newQuery,
undefined,
!!selectParamsLimitCheck?.groupBy
);
Expand All @@ -69,41 +73,67 @@ export const find = async function(this: ViewHandler, filter?: Filter, selectPar
}

/** Used for subscribe */
if(localParams?.returnNewQuery) return (q as unknown as any);
if(localParams?.returnNewQuery) return (newQuery as unknown as any);
if (localParams?.returnQuery) {
if(localParams?.returnQuery === "where-condition"){
return q.whereOpts.condition as any;
return newQuery.whereOpts.condition as any;
}
return ((localParams?.returnQuery === "noRLS"? queryWithoutRLS : queryWithRLS) as unknown as any[]);
}

return runQueryReturnType(queryWithRLS, returnType, this, localParams);
return runQueryReturnType({
queryWithoutRLS,
queryWithRLS,
returnType,
handler: this,
localParams,
newQuery,
});

} catch (e) {
if (localParams && localParams.testRule) throw e;
throw parseError(e, `dbo.${this.name}.find()`);
}
}

type RunQueryReturnTypeArgs = {
queryWithRLS: string;
queryWithoutRLS: string;
returnType: SelectParams["returnType"];
handler: ViewHandler | TableHandler;
localParams: LocalParams | undefined;
newQuery: NewQuery | undefined;
};

export const runQueryReturnType = async (query: string, returnType: SelectParams["returnType"], handler: ViewHandler | TableHandler, localParams: LocalParams | undefined) => {
export const runQueryReturnType = async ({ newQuery, handler, localParams, queryWithRLS, queryWithoutRLS, returnType,}: RunQueryReturnTypeArgs) => {

if (returnType === "statement") {
if (!(await canRunSQL(handler.dboBuilder.prostgles, localParams))) {
throw `Not allowed: {returnType: "statement"} requires sql privileges `
}
return query as unknown as any[];
const query = queryWithRLS;
const sqlTypes = ["statement", "statement-no-rls", "statement-where"];
if(!returnType || returnType === "values"){

} else if (["row", "value"].includes(returnType!)) {
return handler.dbHandler.oneOrNone(query).then(data => {
return (data && returnType === "value") ? Object.values(data)[0] : data;
}).catch(err => makeErrorFromPGError(err, localParams, this));
} else {
return handler.dbHandler.any(query).then(data => {
if (returnType === "values") {
return data.map(d => Object.values(d)[0]);
}
return data;
}).catch(err => makeErrorFromPGError(err, localParams, this));

} else if (sqlTypes.some(v => v === returnType)) {
if (!(await canRunSQL(handler.dboBuilder.prostgles, localParams))) {
throw `Not allowed: {returnType: ${JSON.stringify(returnType)}} requires execute sql privileges `
}
if(returnType === "statement-no-rls"){
return queryWithoutRLS as any;
}
if(returnType === "statement-where"){
if(!newQuery) throw `returnType ${returnType} not possible for this command type`;
return newQuery.whereOpts.condition as any;
}
return query as unknown as any[];

} else if (["row", "value"].includes(returnType)) {
return handler.dbHandler.oneOrNone(query).then(data => {
return (data && returnType === "value") ? Object.values(data)[0] : data;
}).catch(err => makeErrorFromPGError(err, localParams, this));
}
}
18 changes: 9 additions & 9 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "prostgles-server",
"version": "4.1.97",
"version": "4.1.98",
"description": "",
"main": "dist/index.js",
"types": "dist/index.d.ts",
Expand Down Expand Up @@ -41,7 +41,7 @@
"check-disk-space": "^3.3.1",
"file-type": "^17.1.4",
"pg-promise": "^11.3.0",
"prostgles-types": "^4.0.43"
"prostgles-types": "^4.0.44"
},
"devDependencies": {
"@types/bluebird": "^3.5.36",
Expand Down
2 changes: 1 addition & 1 deletion tests/client/PID.txt
Original file line number Diff line number Diff line change
@@ -1 +1 @@
258419
296994
Loading

0 comments on commit 26379d4

Please sign in to comment.