prostgles-server 4.2.159 → 4.2.161
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/Auth/setEmailProvider.js +2 -2
- package/dist/Auth/setEmailProvider.js.map +1 -1
- package/lib/Auth/AuthHandler.ts +436 -0
- package/lib/Auth/AuthTypes.ts +280 -0
- package/lib/Auth/getSafeReturnURL.ts +35 -0
- package/lib/Auth/sendEmail.ts +83 -0
- package/lib/Auth/setAuthProviders.ts +128 -0
- package/lib/Auth/setEmailProvider.ts +85 -0
- package/lib/Auth/setupAuthRoutes.ts +161 -0
- package/lib/DBEventsManager.ts +178 -0
- package/lib/DBSchemaBuilder.ts +225 -0
- package/lib/DboBuilder/DboBuilder.ts +319 -0
- package/lib/DboBuilder/DboBuilderTypes.ts +361 -0
- package/lib/DboBuilder/QueryBuilder/Functions.ts +1153 -0
- package/lib/DboBuilder/QueryBuilder/QueryBuilder.ts +288 -0
- package/lib/DboBuilder/QueryBuilder/getJoinQuery.ts +263 -0
- package/lib/DboBuilder/QueryBuilder/getNewQuery.ts +271 -0
- package/lib/DboBuilder/QueryBuilder/getSelectQuery.ts +136 -0
- package/lib/DboBuilder/QueryBuilder/prepareHaving.ts +22 -0
- package/lib/DboBuilder/QueryStreamer.ts +250 -0
- package/lib/DboBuilder/TableHandler/DataValidator.ts +428 -0
- package/lib/DboBuilder/TableHandler/TableHandler.ts +205 -0
- package/lib/DboBuilder/TableHandler/delete.ts +115 -0
- package/lib/DboBuilder/TableHandler/insert.ts +183 -0
- package/lib/DboBuilder/TableHandler/insertTest.ts +78 -0
- package/lib/DboBuilder/TableHandler/onDeleteFromFileTable.ts +62 -0
- package/lib/DboBuilder/TableHandler/runInsertUpdateQuery.ts +134 -0
- package/lib/DboBuilder/TableHandler/update.ts +126 -0
- package/lib/DboBuilder/TableHandler/updateBatch.ts +49 -0
- package/lib/DboBuilder/TableHandler/updateFile.ts +48 -0
- package/lib/DboBuilder/TableHandler/upsert.ts +34 -0
- package/lib/DboBuilder/ViewHandler/ViewHandler.ts +393 -0
- package/lib/DboBuilder/ViewHandler/count.ts +38 -0
- package/lib/DboBuilder/ViewHandler/find.ts +153 -0
- package/lib/DboBuilder/ViewHandler/getExistsCondition.ts +73 -0
- package/lib/DboBuilder/ViewHandler/getExistsFilters.ts +74 -0
- package/lib/DboBuilder/ViewHandler/getInfo.ts +32 -0
- package/lib/DboBuilder/ViewHandler/getTableJoinQuery.ts +84 -0
- package/lib/DboBuilder/ViewHandler/parseComplexFilter.ts +96 -0
- package/lib/DboBuilder/ViewHandler/parseFieldFilter.ts +105 -0
- package/lib/DboBuilder/ViewHandler/parseJoinPath.ts +208 -0
- package/lib/DboBuilder/ViewHandler/prepareSortItems.ts +163 -0
- package/lib/DboBuilder/ViewHandler/prepareWhere.ts +90 -0
- package/lib/DboBuilder/ViewHandler/size.ts +37 -0
- package/lib/DboBuilder/ViewHandler/subscribe.ts +118 -0
- package/lib/DboBuilder/ViewHandler/validateViewRules.ts +70 -0
- package/lib/DboBuilder/dboBuilderUtils.ts +222 -0
- package/lib/DboBuilder/getColumns.ts +114 -0
- package/lib/DboBuilder/getCondition.ts +201 -0
- package/lib/DboBuilder/getSubscribeRelatedTables.ts +190 -0
- package/lib/DboBuilder/getTablesForSchemaPostgresSQL.ts +426 -0
- package/lib/DboBuilder/insertNestedRecords.ts +355 -0
- package/lib/DboBuilder/parseUpdateRules.ts +187 -0
- package/lib/DboBuilder/prepareShortestJoinPaths.ts +186 -0
- package/lib/DboBuilder/runSQL.ts +182 -0
- package/lib/DboBuilder/runTransaction.ts +50 -0
- package/lib/DboBuilder/sqlErrCodeToMsg.ts +254 -0
- package/lib/DboBuilder/uploadFile.ts +69 -0
- package/lib/Event_Trigger_Tags.ts +118 -0
- package/lib/FileManager/FileManager.ts +358 -0
- package/lib/FileManager/getValidatedFileType.ts +69 -0
- package/lib/FileManager/initFileManager.ts +187 -0
- package/lib/FileManager/upload.ts +62 -0
- package/lib/FileManager/uploadStream.ts +79 -0
- package/lib/Filtering.ts +463 -0
- package/lib/JSONBValidation/validate_jsonb_schema_sql.ts +502 -0
- package/lib/JSONBValidation/validation.ts +143 -0
- package/lib/Logging.ts +127 -0
- package/lib/PostgresNotifListenManager.ts +143 -0
- package/lib/Prostgles.ts +485 -0
- package/lib/ProstglesTypes.ts +196 -0
- package/lib/PubSubManager/PubSubManager.ts +609 -0
- package/lib/PubSubManager/addSub.ts +138 -0
- package/lib/PubSubManager/addSync.ts +141 -0
- package/lib/PubSubManager/getCreatePubSubManagerError.ts +72 -0
- package/lib/PubSubManager/getPubSubManagerInitQuery.ts +662 -0
- package/lib/PubSubManager/initPubSubManager.ts +79 -0
- package/lib/PubSubManager/notifListener.ts +173 -0
- package/lib/PubSubManager/orphanTriggerCheck.ts +70 -0
- package/lib/PubSubManager/pushSubData.ts +55 -0
- package/lib/PublishParser/PublishParser.ts +162 -0
- package/lib/PublishParser/getFileTableRules.ts +124 -0
- package/lib/PublishParser/getSchemaFromPublish.ts +141 -0
- package/lib/PublishParser/getTableRulesWithoutFileTable.ts +177 -0
- package/lib/PublishParser/publishTypesAndUtils.ts +399 -0
- package/lib/RestApi.ts +127 -0
- package/lib/SchemaWatch/SchemaWatch.ts +90 -0
- package/lib/SchemaWatch/createSchemaWatchEventTrigger.ts +3 -0
- package/lib/SchemaWatch/getValidatedWatchSchemaType.ts +45 -0
- package/lib/SchemaWatch/getWatchSchemaTagList.ts +27 -0
- package/lib/SyncReplication.ts +557 -0
- package/lib/TableConfig/TableConfig.ts +468 -0
- package/lib/TableConfig/getColumnDefinitionQuery.ts +111 -0
- package/lib/TableConfig/getConstraintDefinitionQueries.ts +95 -0
- package/lib/TableConfig/getFutureTableSchema.ts +64 -0
- package/lib/TableConfig/getPGIndexes.ts +53 -0
- package/lib/TableConfig/getTableColumnQueries.ts +129 -0
- package/lib/TableConfig/initTableConfig.ts +326 -0
- package/lib/index.ts +13 -0
- package/lib/initProstgles.ts +319 -0
- package/lib/onSocketConnected.ts +102 -0
- package/lib/runClientRequest.ts +129 -0
- package/lib/shortestPath.ts +122 -0
- package/lib/typeTests/DBoGenerated.d.ts +320 -0
- package/lib/typeTests/dboTypeCheck.ts +81 -0
- package/lib/utils.ts +15 -0
- package/package.json +1 -1
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { asName } from "prostgles-types";
|
|
2
|
+
import { pgp } from "../DboBuilder/DboBuilder";
|
|
3
|
+
import { DB } from "../Prostgles";
|
|
4
|
+
import { ColumnMinimalInfo, getTableColumns } from "./getColumnDefinitionQuery";
|
|
5
|
+
import { ColConstraint, ConstraintDef, getColConstraints } from "./getConstraintDefinitionQueries";
|
|
6
|
+
|
|
7
|
+
type Args = {
|
|
8
|
+
db: DB,
|
|
9
|
+
columnDefs: string[];
|
|
10
|
+
tableName: string;
|
|
11
|
+
constraintDefs?: ConstraintDef[];
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
export const getFutureTableSchema = async ({ columnDefs, tableName, constraintDefs = [], db }: Args): Promise<{
|
|
15
|
+
constraints: ColConstraint[];
|
|
16
|
+
cols: ColumnMinimalInfo[];
|
|
17
|
+
}> => {
|
|
18
|
+
const { TransactionMode, isolationLevel } = pgp.txMode;
|
|
19
|
+
|
|
20
|
+
let constraints: ColConstraint[] = [];
|
|
21
|
+
let cols: ColumnMinimalInfo[] = [];
|
|
22
|
+
const ROLLBACK = "Rollback";
|
|
23
|
+
try {
|
|
24
|
+
const txMode = new TransactionMode({
|
|
25
|
+
tiLevel: isolationLevel.serializable
|
|
26
|
+
});
|
|
27
|
+
await db.tx({ mode: txMode }, async t => {
|
|
28
|
+
|
|
29
|
+
/** To prevent deadlocks we use a random table name -> Not feasible because named constraints cannot be recreated without dropping the existing ones from actual table */
|
|
30
|
+
// const tableEsc = asName(tableName.slice(0, 12) + (await t.oneOrNone(`SELECT md5(now()::text) as md5`)).md5);
|
|
31
|
+
|
|
32
|
+
const tableEsc = asName(tableName);
|
|
33
|
+
|
|
34
|
+
const consQueries = constraintDefs.map(c =>
|
|
35
|
+
`ALTER TABLE ${tableEsc} ADD ${c.name? ` CONSTRAINT ${asName(c.name)}` : ""} ${c.content};`
|
|
36
|
+
).join("\n");
|
|
37
|
+
|
|
38
|
+
const query = `
|
|
39
|
+
DROP TABLE IF EXISTS ${tableEsc} CASCADE;
|
|
40
|
+
CREATE TABLE ${tableEsc} (
|
|
41
|
+
${columnDefs.join(",\n")}
|
|
42
|
+
);
|
|
43
|
+
${consQueries}
|
|
44
|
+
`;
|
|
45
|
+
|
|
46
|
+
await t.any(query);
|
|
47
|
+
|
|
48
|
+
constraints = await getColConstraints({ db: t, table: tableName });
|
|
49
|
+
cols = await getTableColumns({ db: t, table: tableName });
|
|
50
|
+
|
|
51
|
+
/** Rollback */
|
|
52
|
+
return Promise.reject(new Error(ROLLBACK));
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
} catch(e: any){
|
|
56
|
+
if(e instanceof Error && e.message === ROLLBACK) {
|
|
57
|
+
// Ignore
|
|
58
|
+
} else {
|
|
59
|
+
throw e;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
return { cols, constraints };
|
|
64
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { DB } from "../Prostgles";
|
|
2
|
+
|
|
3
|
+
type PGIndex = {
|
|
4
|
+
schemaname: string;
|
|
5
|
+
indexname: string;
|
|
6
|
+
indexdef: string;
|
|
7
|
+
escaped_identifier: string;
|
|
8
|
+
type: string;
|
|
9
|
+
owner: string;
|
|
10
|
+
tablename: string;
|
|
11
|
+
persistence: string;
|
|
12
|
+
access_method: string;
|
|
13
|
+
size: string;
|
|
14
|
+
description: string | null;
|
|
15
|
+
};
|
|
16
|
+
export const getPGIndexes = async (db: DB, tableName: string, schema: string): Promise<PGIndex[]> => {
|
|
17
|
+
|
|
18
|
+
const indexQuery = `
|
|
19
|
+
SELECT n.nspname as schemaname,
|
|
20
|
+
c.relname as indexname,
|
|
21
|
+
pg_get_indexdef(c.oid) as indexdef,
|
|
22
|
+
format('%I', c.relname) as escaped_identifier,
|
|
23
|
+
CASE c.relkind WHEN 'r'
|
|
24
|
+
THEN 'table' WHEN 'v'
|
|
25
|
+
THEN 'view' WHEN 'm'
|
|
26
|
+
THEN 'materialized view'
|
|
27
|
+
WHEN 'i' THEN 'index'
|
|
28
|
+
WHEN 'S' THEN 'sequence' WHEN 's' THEN 'special'
|
|
29
|
+
WHEN 't' THEN 'TOAST table' WHEN 'f' THEN 'foreign table'
|
|
30
|
+
WHEN 'p' THEN 'partitioned table' WHEN 'I' THEN 'partitioned index' END as "type",
|
|
31
|
+
pg_catalog.pg_get_userbyid(c.relowner) as "owner",
|
|
32
|
+
c2.relname as tablename,
|
|
33
|
+
CASE c.relpersistence WHEN 'p' THEN 'permanent' WHEN 't' THEN 'temporary'
|
|
34
|
+
WHEN 'u' THEN 'unlogged' END as "persistence",
|
|
35
|
+
am.amname as "access_method",
|
|
36
|
+
pg_catalog.pg_size_pretty(pg_catalog.pg_table_size(c.oid)) as "size",
|
|
37
|
+
pg_catalog.obj_description(c.oid, 'pg_class') as "description"
|
|
38
|
+
FROM pg_catalog.pg_class c
|
|
39
|
+
LEFT JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace
|
|
40
|
+
LEFT JOIN pg_catalog.pg_am am ON am.oid = c.relam
|
|
41
|
+
LEFT JOIN pg_catalog.pg_index i ON i.indexrelid = c.oid
|
|
42
|
+
LEFT JOIN pg_catalog.pg_class c2 ON i.indrelid = c2.oid
|
|
43
|
+
WHERE c.relkind IN ('i','I','')
|
|
44
|
+
AND n.nspname <> 'pg_catalog'
|
|
45
|
+
AND n.nspname !~ '^pg_toast'
|
|
46
|
+
AND n.nspname <> 'information_schema'
|
|
47
|
+
AND pg_catalog.pg_table_is_visible(c.oid)
|
|
48
|
+
AND c2.relname = \${tableName}
|
|
49
|
+
AND n.nspname = \${schema}
|
|
50
|
+
ORDER BY 1,2;
|
|
51
|
+
`
|
|
52
|
+
return db.any(indexQuery, { tableName, schema });
|
|
53
|
+
};
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
import { getKeys, asName as _asName, isObject, asName } from "prostgles-types";
|
|
2
|
+
import { DB, DBHandlerServer } from "../Prostgles";
|
|
3
|
+
import { validate_jsonb_schema_sql } from "../JSONBValidation/validate_jsonb_schema_sql";
|
|
4
|
+
import { getColumnDefinitionQuery, getTableColumns } from "./getColumnDefinitionQuery";
|
|
5
|
+
import { TableConfig } from "./TableConfig";
|
|
6
|
+
import { getFutureTableSchema } from "./getFutureTableSchema";
|
|
7
|
+
|
|
8
|
+
type Args = {
|
|
9
|
+
db: DB;
|
|
10
|
+
tableConf: TableConfig[string];
|
|
11
|
+
tableName: string;
|
|
12
|
+
tableHandler: DBHandlerServer[string] | undefined;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export const getTableColumnQueries = async ({ db, tableConf, tableName, tableHandler }: Args): Promise<undefined | {
|
|
16
|
+
columnDefs: string[];
|
|
17
|
+
newColumnDefs: string[];
|
|
18
|
+
fullQuery: string;
|
|
19
|
+
isCreate: boolean;
|
|
20
|
+
}> => {
|
|
21
|
+
|
|
22
|
+
let newColumnDefs: string[] = [];
|
|
23
|
+
const droppedColNames: string[] = [];
|
|
24
|
+
const alteredColQueries: string[] = [];
|
|
25
|
+
let fullQuery = "";
|
|
26
|
+
let isCreate = false;
|
|
27
|
+
|
|
28
|
+
if ("columns" in tableConf && tableConf.columns) {
|
|
29
|
+
|
|
30
|
+
const hasJSONBValidation = getKeys(tableConf.columns).some(c => {
|
|
31
|
+
const cConf = tableConf.columns?.[c];
|
|
32
|
+
return cConf && isObject(cConf) && (cConf.jsonbSchema || cConf.jsonbSchemaType)
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
/** Must install validation function */
|
|
36
|
+
if(hasJSONBValidation){
|
|
37
|
+
try {
|
|
38
|
+
await db.any(validate_jsonb_schema_sql);
|
|
39
|
+
} catch(err: any){
|
|
40
|
+
console.error("Could not install the jsonb validation function due to error: ", err);
|
|
41
|
+
throw err;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const columns = getKeys(tableConf.columns).filter(c => {
|
|
46
|
+
const colDef = tableConf.columns![c]!;
|
|
47
|
+
/** Exclude NamedJoinColumn */
|
|
48
|
+
return typeof colDef === "string" || !("joinDef" in colDef)
|
|
49
|
+
}) as string[];
|
|
50
|
+
|
|
51
|
+
const colDefs: { name: string; def: string }[] = [];
|
|
52
|
+
|
|
53
|
+
for (const colName of columns) {
|
|
54
|
+
const colConf = tableConf.columns![colName]!;
|
|
55
|
+
|
|
56
|
+
/* Get column definition */
|
|
57
|
+
const colDef = await getColumnDefinitionQuery({ colConf, column: colName, db, table: tableName });
|
|
58
|
+
if(colDef){
|
|
59
|
+
colDefs.push({ name: colName, def: colDef});
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
const columnDefs = colDefs.map(c => c.def);
|
|
63
|
+
|
|
64
|
+
if(!colDefs.length){
|
|
65
|
+
return undefined;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
const ALTERQ = `ALTER TABLE ${asName(tableName)}`;
|
|
70
|
+
if (!tableHandler) {
|
|
71
|
+
newColumnDefs.push(...colDefs.map(c => c.def));
|
|
72
|
+
|
|
73
|
+
} else if (tableHandler) {
|
|
74
|
+
const currCols = await getTableColumns({ db, table: tableName });
|
|
75
|
+
|
|
76
|
+
/** Add new columns */
|
|
77
|
+
newColumnDefs = colDefs.filter(nc => !tableHandler.columns?.some(c => nc.name === c.name)).map(c => c.def);
|
|
78
|
+
|
|
79
|
+
/** Altered/Dropped columns */
|
|
80
|
+
const { cols: futureCols } = await getFutureTableSchema({ tableName, columnDefs, constraintDefs: [], db });
|
|
81
|
+
currCols.forEach(c => {
|
|
82
|
+
const newCol = futureCols.find(nc => nc.column_name === c.column_name);
|
|
83
|
+
if(!newCol){
|
|
84
|
+
droppedColNames.push(c.column_name);
|
|
85
|
+
} else if(newCol.nullable !== c.nullable){
|
|
86
|
+
alteredColQueries.push(`${ALTERQ} ALTER COLUMN ${asName(c.column_name)} ${newCol.nullable? "SET" : "DROP"} NOT NULL;`)
|
|
87
|
+
} else if(newCol.udt_name !== c.udt_name){
|
|
88
|
+
alteredColQueries.push(`${ALTERQ} ALTER COLUMN ${asName(c.column_name)} TYPE ${newCol.udt_name} USING ${asName(c.column_name)}::${newCol.udt_name};`)
|
|
89
|
+
} else if(newCol.column_default !== c.column_default){
|
|
90
|
+
const colConfig = colDefs.find(cd => cd.name === c.column_name);
|
|
91
|
+
if(["serial", "bigserial"].some(t => colConfig?.def.toLowerCase().includes(` ${t}`)) && c.column_default?.toLowerCase().includes("nextval")){
|
|
92
|
+
/** Ignore SERIAL/BIGSERIAL <> nextval mismatch */
|
|
93
|
+
} else {
|
|
94
|
+
alteredColQueries.push(`${ALTERQ} ALTER COLUMN ${asName(c.column_name)} ${newCol.column_default === null? "DROP DEFAULT" : `SET DEFAULT ${newCol.column_default}`};`)
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
if (!tableHandler || tableConf.dropIfExists || tableConf.dropIfExistsCascade) {
|
|
101
|
+
isCreate = true;
|
|
102
|
+
const DROPQ = `DROP TABLE IF EXISTS ${asName(tableName)}`;
|
|
103
|
+
fullQuery = ([
|
|
104
|
+
...(tableConf.dropIfExists? [`${DROPQ};`] : tableConf.dropIfExistsCascade? [`${DROPQ} CASCADE;`] : []),
|
|
105
|
+
`CREATE TABLE ${asName(tableName)} (`,
|
|
106
|
+
columnDefs.join(", \n"),
|
|
107
|
+
`);`
|
|
108
|
+
].join("\n"));
|
|
109
|
+
|
|
110
|
+
} else {
|
|
111
|
+
fullQuery = [
|
|
112
|
+
...droppedColNames.map(c => `${ALTERQ} DROP COLUMN ${asName(c)};`),
|
|
113
|
+
...newColumnDefs.map(c => `${ALTERQ} ADD COLUMN ${c};`),
|
|
114
|
+
...alteredColQueries,
|
|
115
|
+
].join("\n");
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
return {
|
|
119
|
+
fullQuery,
|
|
120
|
+
columnDefs,
|
|
121
|
+
isCreate,
|
|
122
|
+
newColumnDefs,
|
|
123
|
+
}
|
|
124
|
+
} else {
|
|
125
|
+
return undefined;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
}
|
|
@@ -0,0 +1,326 @@
|
|
|
1
|
+
|
|
2
|
+
import { asName as _asName } from "prostgles-types";
|
|
3
|
+
import { PubSubManager, asValue, log } from "../PubSubManager/PubSubManager";
|
|
4
|
+
import TableConfigurator from "./TableConfig";
|
|
5
|
+
import { getColConstraints, getConstraintDefinitionQueries } from "./getConstraintDefinitionQueries";
|
|
6
|
+
import { getFutureTableSchema } from "./getFutureTableSchema";
|
|
7
|
+
import { getTableColumnQueries } from "./getTableColumnQueries";
|
|
8
|
+
import { getPGIndexes } from "./getPGIndexes";
|
|
9
|
+
|
|
10
|
+
export const initTableConfig = async function (this: TableConfigurator<any>) {
|
|
11
|
+
|
|
12
|
+
let changedSchema = false;
|
|
13
|
+
const failedQueries: { query: string; error: any }[] = [];
|
|
14
|
+
this.initialising = true;
|
|
15
|
+
const queryHistory: string[] = [];
|
|
16
|
+
let queries: string[] = [];
|
|
17
|
+
const makeQuery = (q: string[]) => q.filter(v => v.trim().length).map(v => v.trim().endsWith(";") ? v : `${v};`).join("\n");
|
|
18
|
+
const runQueries = async (_queries = queries) => {
|
|
19
|
+
let q = makeQuery(queries);
|
|
20
|
+
if (!_queries.some(q => q.trim().length)) {
|
|
21
|
+
return 0;
|
|
22
|
+
}
|
|
23
|
+
q = `/* ${PubSubManager.EXCLUDE_QUERY_FROM_SCHEMA_WATCH_ID} */ \n\n` + q;
|
|
24
|
+
queryHistory.push(q);
|
|
25
|
+
this.prostgles.opts.onLog?.({ type: "debug", command: "TableConfig.runQueries.start", data: { q }, duration: -1 });
|
|
26
|
+
const now = Date.now();
|
|
27
|
+
await this.db.multi(q).catch(err => {
|
|
28
|
+
log({ err, q });
|
|
29
|
+
failedQueries.push({ query: q, error: err });
|
|
30
|
+
return Promise.reject(err);
|
|
31
|
+
});
|
|
32
|
+
this.prostgles.opts.onLog?.({ type: "debug", command: "TableConfig.runQueries.end", duration: Date.now() - now, data: { q } });
|
|
33
|
+
changedSchema = true;
|
|
34
|
+
_queries = [];
|
|
35
|
+
queries = [];
|
|
36
|
+
return 1;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
if (!this.prostgles.pgp) {
|
|
40
|
+
throw "pgp missing";
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const MAX_IDENTIFIER_LENGTH = +(await this.db.one("SHOW max_identifier_length;") as any).max_identifier_length;
|
|
44
|
+
if (!Number.isFinite(MAX_IDENTIFIER_LENGTH)) throw `Could not obtain a valid max_identifier_length`;
|
|
45
|
+
const asName = (v: string) => {
|
|
46
|
+
if (v.length > MAX_IDENTIFIER_LENGTH - 1) {
|
|
47
|
+
throw `The identifier name provided (${v}) is longer than the allowed limit (max_identifier_length - 1 = ${MAX_IDENTIFIER_LENGTH - 1} characters )\n Longest allowed: ${_asName(v.slice(0, MAX_IDENTIFIER_LENGTH - 1))} `
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
return _asName(v);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
let migrations: { version: number; table: string; } | undefined;
|
|
54
|
+
if (this.prostgles.opts.tableConfigMigrations) {
|
|
55
|
+
const { onMigrate, version, versionTableName = "schema_version" } = this.prostgles.opts.tableConfigMigrations;
|
|
56
|
+
await this.db.any(`
|
|
57
|
+
/* ${PubSubManager.EXCLUDE_QUERY_FROM_SCHEMA_WATCH_ID} */
|
|
58
|
+
CREATE TABLE IF NOT EXISTS ${asName(versionTableName)}(id NUMERIC PRIMARY KEY, table_config JSONB NOT NULL)
|
|
59
|
+
`);
|
|
60
|
+
migrations = { version, table: versionTableName };
|
|
61
|
+
let latestVersion: number | undefined;
|
|
62
|
+
try {
|
|
63
|
+
latestVersion = Number((await this.db.oneOrNone(`SELECT MAX(id) as v FROM ${asName(versionTableName)}`)).v);
|
|
64
|
+
} catch (e) {
|
|
65
|
+
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
if (latestVersion === version) {
|
|
69
|
+
const isLatest = (await this.db.oneOrNone(`SELECT table_config = \${table_config} as v FROM ${asName(versionTableName)} WHERE id = \${version}`, { version, table_config: this.config })).v;
|
|
70
|
+
if (isLatest) {
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
if (!latestVersion || latestVersion < version) {
|
|
75
|
+
await onMigrate({ db: this.db, oldVersion: latestVersion, getConstraints: (table, col, types) => getColConstraints({ db: this.db, table, column: col, types }) })
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/* Create lookup tables */
|
|
80
|
+
for (const [tableNameRaw, tableConf] of Object.entries(this.config)) {
|
|
81
|
+
const tableName = asName(tableNameRaw);
|
|
82
|
+
|
|
83
|
+
if ("isLookupTable" in tableConf && Object.keys(tableConf.isLookupTable?.values).length) {
|
|
84
|
+
const { dropIfExists = false, dropIfExistsCascade = false } = tableConf;
|
|
85
|
+
const isDropped = dropIfExists || dropIfExistsCascade;
|
|
86
|
+
|
|
87
|
+
if (dropIfExistsCascade) {
|
|
88
|
+
queries.push(`DROP TABLE IF EXISTS ${tableName} CASCADE;`);
|
|
89
|
+
} else if (dropIfExists) {
|
|
90
|
+
queries.push(`DROP TABLE IF EXISTS ${tableName};`);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
const rows = Object.entries(tableConf.isLookupTable?.values).map(([id, otherColumns])=> ({ id, ...otherColumns }));
|
|
94
|
+
const lookupTableHandler = this.dbo?.[tableNameRaw];
|
|
95
|
+
const columnNames = Object.keys(rows[0]!).filter(k => k !== "id");
|
|
96
|
+
if (isDropped || !lookupTableHandler) {
|
|
97
|
+
queries.push(
|
|
98
|
+
`CREATE TABLE IF NOT EXISTS ${tableName} (
|
|
99
|
+
id TEXT PRIMARY KEY
|
|
100
|
+
${columnNames.length ? (", " + columnNames.map(k => asName(k) + " TEXT ").join(", ")) : ""}
|
|
101
|
+
);`
|
|
102
|
+
);
|
|
103
|
+
}
|
|
104
|
+
if(rows.length){
|
|
105
|
+
const existingValues: { id: any }[] = !lookupTableHandler? [] : await this.db.any(`SELECT id FROM ${tableName} WHERE id IN (${rows.map(r => asValue(r.id)).join(", ")});`);
|
|
106
|
+
rows
|
|
107
|
+
.filter(r => !existingValues.some(ev => ev.id === r.id))
|
|
108
|
+
.map(row => {
|
|
109
|
+
const allColumns = ["id", ...columnNames]
|
|
110
|
+
const values = allColumns.map(key => (row as any)[key]);
|
|
111
|
+
queries.push(this.prostgles.pgp!.as.format(`INSERT INTO ${tableName} (${allColumns.map(t => asName(t)).join(", ")}) ` + " VALUES (${values:csv});", { values }))
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
if (queries.length) {
|
|
118
|
+
await runQueries(queries);
|
|
119
|
+
await this.prostgles.refreshDBO();
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/* Create/Alter columns */
|
|
123
|
+
for (const [tableName, tableConf] of Object.entries(this.config)) {
|
|
124
|
+
const tableHandler = this.dbo[tableName];
|
|
125
|
+
|
|
126
|
+
const ALTER_TABLE_Q = `ALTER TABLE ${asName(tableName)}`;
|
|
127
|
+
|
|
128
|
+
/* isLookupTable table has already been created */
|
|
129
|
+
const coldef = "isLookupTable" in tableConf? undefined : await getTableColumnQueries({ db: this.db, tableConf, tableHandler, tableName });
|
|
130
|
+
|
|
131
|
+
if (coldef) {
|
|
132
|
+
queries.push(coldef.fullQuery);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/** CONSTRAINTS */
|
|
136
|
+
const constraintDefs = getConstraintDefinitionQueries({ tableName, tableConf });
|
|
137
|
+
if (coldef?.isCreate) {
|
|
138
|
+
queries.push(...constraintDefs?.map(c => c.alterQuery) ?? []);
|
|
139
|
+
|
|
140
|
+
} else if (coldef) {
|
|
141
|
+
const fullSchema = await getFutureTableSchema({ db: this.db, tableName, columnDefs: coldef.columnDefs, constraintDefs });
|
|
142
|
+
const futureCons = fullSchema.constraints.map(nc => ({
|
|
143
|
+
...nc,
|
|
144
|
+
isNamed: constraintDefs?.some(c => c.name === nc.name)
|
|
145
|
+
}));
|
|
146
|
+
|
|
147
|
+
/** Run this first to ensure any dropped cols drop their constraints as well */
|
|
148
|
+
await runQueries(queries);
|
|
149
|
+
const currCons = await getColConstraints({ db: this.db, table: tableName });
|
|
150
|
+
|
|
151
|
+
/** Drop removed/modified */
|
|
152
|
+
currCons.forEach(c => {
|
|
153
|
+
if (!futureCons.some(nc => nc.definition === c.definition && (!nc.isNamed || nc.name === c.name))) {
|
|
154
|
+
queries.push(`${ALTER_TABLE_Q} DROP CONSTRAINT ${asName(c.name)};`)
|
|
155
|
+
}
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
/** Add missing named constraints */
|
|
159
|
+
constraintDefs?.forEach(c => {
|
|
160
|
+
if (c.name && !currCons.some(cc => cc.name === c.name)) {
|
|
161
|
+
const fc = futureCons.find(nc => nc.name === c.name);
|
|
162
|
+
if (fc) {
|
|
163
|
+
queries.push(`${ALTER_TABLE_Q} ADD CONSTRAINT ${asName(c.name)} ${c.content};`);
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
/** Add remaining missing constraints */
|
|
169
|
+
futureCons
|
|
170
|
+
.filter(nc =>
|
|
171
|
+
!currCons.some(c => c.definition === nc.definition)
|
|
172
|
+
)
|
|
173
|
+
.forEach(c => {
|
|
174
|
+
queries.push(`${ALTER_TABLE_Q} ADD ${c.definition};`)
|
|
175
|
+
});
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
|
|
179
|
+
if ("indexes" in tableConf && tableConf.indexes) {
|
|
180
|
+
/*
|
|
181
|
+
CREATE [ UNIQUE ] INDEX [ CONCURRENTLY ] [ [ IF NOT EXISTS ] name ] ON [ ONLY ] table_name [ USING method ]
|
|
182
|
+
( { column_name | ( expression ) } [ COLLATE collation ] [ opclass [ ( opclass_parameter = value [, ... ] ) ] ] [ ASC | DESC ] [ NULLS { FIRST | LAST } ] [, ...] )
|
|
183
|
+
[ INCLUDE ( column_name [, ...] ) ]
|
|
184
|
+
[ NULLS [ NOT ] DISTINCT ]
|
|
185
|
+
[ WITH ( storage_parameter [= value] [, ... ] ) ]
|
|
186
|
+
[ TABLESPACE tablespace_name ]
|
|
187
|
+
[ WHERE predicate ]
|
|
188
|
+
*/
|
|
189
|
+
const currIndexes = await getPGIndexes(this.db, tableName, "public");
|
|
190
|
+
Object.entries(tableConf.indexes).forEach(([
|
|
191
|
+
indexName,
|
|
192
|
+
{
|
|
193
|
+
columns,
|
|
194
|
+
concurrently,
|
|
195
|
+
replace,
|
|
196
|
+
unique,
|
|
197
|
+
using,
|
|
198
|
+
where = ""
|
|
199
|
+
}
|
|
200
|
+
]) => {
|
|
201
|
+
|
|
202
|
+
if (replace || typeof replace !== "boolean" && tableConf.replaceUniqueIndexes) {
|
|
203
|
+
queries.push(`DROP INDEX IF EXISTS ${asName(indexName)};`);
|
|
204
|
+
}
|
|
205
|
+
if (!currIndexes.some(idx => idx.indexname === indexName)) {
|
|
206
|
+
queries.push([
|
|
207
|
+
"CREATE",
|
|
208
|
+
unique && "UNIQUE",
|
|
209
|
+
concurrently && "CONCURRENTLY",
|
|
210
|
+
`INDEX ${asName(indexName)} ON ${asName(tableName)}`,
|
|
211
|
+
using && ("USING " + using),
|
|
212
|
+
`(${columns})`,
|
|
213
|
+
where && `WHERE ${where}`
|
|
214
|
+
].filter(v => v).join(" ") + ";");
|
|
215
|
+
}
|
|
216
|
+
});
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
const { triggers, dropIfExists, dropIfExistsCascade } = tableConf;
|
|
220
|
+
if (triggers) {
|
|
221
|
+
const isDropped = dropIfExists || dropIfExistsCascade;
|
|
222
|
+
|
|
223
|
+
const existingTriggers = await this.dbo.sql!(`
|
|
224
|
+
SELECT event_object_table
|
|
225
|
+
,trigger_name
|
|
226
|
+
FROM information_schema.triggers
|
|
227
|
+
WHERE event_object_table = \${tableName}
|
|
228
|
+
ORDER BY event_object_table
|
|
229
|
+
`,
|
|
230
|
+
{ tableName },
|
|
231
|
+
{ returnType: "rows" }
|
|
232
|
+
) as { trigger_name: string }[];
|
|
233
|
+
|
|
234
|
+
// const existingTriggerFuncs = await this.dbo.sql!(`
|
|
235
|
+
// SELECT p.oid,proname,prosrc,u.usename
|
|
236
|
+
// FROM pg_proc p
|
|
237
|
+
// JOIN pg_user u ON u.usesysid = p.proowner
|
|
238
|
+
// WHERE prorettype = 2279;
|
|
239
|
+
// `, {}, { returnType: "rows" }) as { proname: string }[];
|
|
240
|
+
|
|
241
|
+
Object.entries(triggers).forEach(([triggerFuncName, trigger]) => {
|
|
242
|
+
|
|
243
|
+
const funcNameParsed = asName(triggerFuncName);
|
|
244
|
+
|
|
245
|
+
let addedFunc = false;
|
|
246
|
+
const addFuncDef = () => {
|
|
247
|
+
if (addedFunc) return;
|
|
248
|
+
addedFunc = true;
|
|
249
|
+
queries.push(`
|
|
250
|
+
CREATE OR REPLACE FUNCTION ${funcNameParsed}()
|
|
251
|
+
RETURNS trigger
|
|
252
|
+
LANGUAGE plpgsql
|
|
253
|
+
AS
|
|
254
|
+
$$
|
|
255
|
+
|
|
256
|
+
${trigger.query}
|
|
257
|
+
|
|
258
|
+
$$;
|
|
259
|
+
`);
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
trigger.actions.forEach(action => {
|
|
263
|
+
const triggerActionName = triggerFuncName + "_" + action;
|
|
264
|
+
|
|
265
|
+
const triggerActionNameParsed = asName(triggerActionName)
|
|
266
|
+
if (isDropped) {
|
|
267
|
+
queries.push(`DROP TRIGGER IF EXISTS ${triggerActionNameParsed} ON ${tableName};`)
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
if (isDropped || !existingTriggers.some(t => t.trigger_name === triggerActionName)) {
|
|
271
|
+
addFuncDef();
|
|
272
|
+
const newTableName = action !== "delete" ? "NEW TABLE AS new_table" : "";
|
|
273
|
+
const oldTableName = action !== "insert" ? "OLD TABLE AS old_table" : "";
|
|
274
|
+
queries.push(`
|
|
275
|
+
CREATE TRIGGER ${triggerActionNameParsed}
|
|
276
|
+
${trigger.type} ${action} ON ${tableName}
|
|
277
|
+
REFERENCING ${newTableName} ${oldTableName}
|
|
278
|
+
FOR EACH ${trigger.forEach}
|
|
279
|
+
EXECUTE PROCEDURE ${funcNameParsed}();
|
|
280
|
+
`);
|
|
281
|
+
}
|
|
282
|
+
})
|
|
283
|
+
})
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
if (queries.length) {
|
|
288
|
+
const q = makeQuery(queries);
|
|
289
|
+
|
|
290
|
+
try {
|
|
291
|
+
await runQueries(queries);
|
|
292
|
+
} catch (err: any) {
|
|
293
|
+
this.initialising = false;
|
|
294
|
+
|
|
295
|
+
console.error("TableConfig error: ", err);
|
|
296
|
+
if (err.position) {
|
|
297
|
+
const pos = +err.position;
|
|
298
|
+
if (Number.isInteger(pos)) {
|
|
299
|
+
return Promise.reject(err.toString() + "\n At:" + q.slice(pos - 50, pos + 50));
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
return Promise.reject(err);
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
if (migrations) {
|
|
308
|
+
await this.db.any(`INSERT INTO ${migrations.table}(id, table_config) VALUES (${asValue(migrations.version)}, ${asValue(this.config)}) ON CONFLICT DO NOTHING;`)
|
|
309
|
+
}
|
|
310
|
+
this.initialising = false;
|
|
311
|
+
if (changedSchema && !failedQueries.length) {
|
|
312
|
+
if (!this.prevInitQueryHistory) {
|
|
313
|
+
this.prevInitQueryHistory = queryHistory;
|
|
314
|
+
} else if (this.prevInitQueryHistory.join() !== queryHistory.join()) {
|
|
315
|
+
this.prostgles.init(this.prostgles.opts.onReady as any, { type: "TableConfig" });
|
|
316
|
+
} else {
|
|
317
|
+
console.error("TableConfig loop bug", queryHistory)
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
if (failedQueries.length) {
|
|
321
|
+
console.error("Table config failed queries: ", failedQueries)
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
await this.prostgles.refreshDBO();
|
|
325
|
+
this.setTableOnMounts();
|
|
326
|
+
}
|
package/lib/index.ts
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { SessionUser } from "./Auth/AuthTypes";
|
|
2
|
+
import { Prostgles } from "./Prostgles";
|
|
3
|
+
import { ProstglesInitOptions } from "./ProstglesTypes";
|
|
4
|
+
|
|
5
|
+
import { testDboTypes } from "./typeTests/dboTypeCheck";
|
|
6
|
+
testDboTypes();
|
|
7
|
+
|
|
8
|
+
function prostgles<S = void, SUser extends SessionUser = SessionUser>(params: ProstglesInitOptions<S, SUser>){
|
|
9
|
+
|
|
10
|
+
const prgl = new Prostgles(params as any);
|
|
11
|
+
return prgl.init(params.onReady as any, { type: "init" });
|
|
12
|
+
}
|
|
13
|
+
export = prostgles;
|