prostgles-server 2.0.185 → 2.0.188
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/DboBuilder/insert.d.ts +5 -0
- package/dist/DboBuilder/insert.d.ts.map +1 -0
- package/dist/DboBuilder/insert.js +129 -0
- package/dist/DboBuilder/insert.js.map +1 -0
- package/dist/DboBuilder/insertDataParse.d.ts +11 -0
- package/dist/DboBuilder/insertDataParse.d.ts.map +1 -0
- package/dist/DboBuilder/insertDataParse.js +283 -0
- package/dist/DboBuilder/insertDataParse.js.map +1 -0
- package/dist/DboBuilder.d.ts +10 -5
- package/dist/DboBuilder.d.ts.map +1 -1
- package/dist/DboBuilder.js +121 -374
- package/dist/DboBuilder.js.map +1 -1
- package/dist/FileManager.d.ts.map +1 -1
- package/dist/FileManager.js +17 -12
- package/dist/FileManager.js.map +1 -1
- package/dist/Prostgles.d.ts +16 -14
- package/dist/Prostgles.d.ts.map +1 -1
- package/dist/Prostgles.js +7 -7
- package/dist/Prostgles.js.map +1 -1
- package/dist/PubSubManager.d.ts +1 -0
- package/dist/PubSubManager.d.ts.map +1 -1
- package/dist/PubSubManager.js +2 -1
- package/dist/PubSubManager.js.map +1 -1
- package/dist/QueryBuilder.d.ts +7 -3
- package/dist/QueryBuilder.d.ts.map +1 -1
- package/dist/QueryBuilder.js +7 -2
- package/dist/QueryBuilder.js.map +1 -1
- package/dist/TableConfig.d.ts +1 -4
- package/dist/TableConfig.d.ts.map +1 -1
- package/dist/TableConfig.js +16 -1
- package/dist/TableConfig.js.map +1 -1
- package/lib/DboBuilder/insert.d.ts +5 -0
- package/lib/DboBuilder/insert.d.ts.map +1 -0
- package/lib/DboBuilder/insert.js +128 -0
- package/lib/DboBuilder/insert.ts +138 -0
- package/lib/DboBuilder/insertDataParse.d.ts +11 -0
- package/lib/DboBuilder/insertDataParse.d.ts.map +1 -0
- package/lib/DboBuilder/insertDataParse.js +282 -0
- package/lib/DboBuilder/insertDataParse.ts +355 -0
- package/lib/DboBuilder.d.ts +10 -5
- package/lib/DboBuilder.d.ts.map +1 -1
- package/lib/DboBuilder.js +121 -374
- package/lib/DboBuilder.ts +138 -453
- package/lib/FileManager.d.ts.map +1 -1
- package/lib/FileManager.js +17 -12
- package/lib/FileManager.ts +18 -13
- package/lib/Prostgles.d.ts +16 -14
- package/lib/Prostgles.d.ts.map +1 -1
- package/lib/Prostgles.js +7 -7
- package/lib/Prostgles.ts +664 -650
- package/lib/PubSubManager.d.ts +1 -0
- package/lib/PubSubManager.d.ts.map +1 -1
- package/lib/PubSubManager.js +2 -1
- package/lib/PubSubManager.ts +3 -1
- package/lib/QueryBuilder.d.ts.map +1 -1
- package/lib/QueryBuilder.js +7 -2
- package/lib/QueryBuilder.ts +12 -7
- package/lib/SchemaWatchManager.ts +72 -0
- package/lib/TableConfig.d.ts +1 -4
- package/lib/TableConfig.d.ts.map +1 -1
- package/lib/TableConfig.js +16 -1
- package/lib/TableConfig.ts +21 -8
- package/package.json +3 -3
- package/tests/client/PID.txt +1 -1
- package/tests/client/package-lock.json +15 -15
- package/tests/client/package.json +1 -1
- package/tests/client/tsconfig.json +1 -1
- package/tests/client_only_queries.d.ts +1 -1
- package/tests/client_only_queries.d.ts.map +1 -1
- package/tests/client_only_queries.ts +1 -1
- package/tests/isomorphic_queries.d.ts +1 -1
- package/tests/isomorphic_queries.d.ts.map +1 -1
- package/tests/isomorphic_queries.js +49 -1
- package/tests/isomorphic_queries.ts +66 -4
- package/tests/manual_test/DBoGenerated.d.ts +398 -0
- package/tests/manual_test/index.d.ts +2 -0
- package/tests/manual_test/index.d.ts.map +1 -0
- package/tests/{config_test2 → manual_test}/index.html +14 -23
- package/tests/{config_test2 → manual_test}/index.js +21 -15
- package/tests/{config_test2 → manual_test}/index.ts +22 -17
- package/tests/{config_test2 → manual_test}/init.sql +36 -5
- package/tests/manual_test/package-lock.json +2483 -0
- package/tests/{config_test2 → manual_test}/package.json +6 -7
- package/tests/manual_test/tsconfig.json +21 -0
- package/tests/server/DBoGenerated.d.ts +70 -0
- package/tests/server/index.js +29 -2
- package/tests/server/index.ts +30 -4
- package/tests/server/init.sql +25 -0
- package/tests/server/package-lock.json +5 -5
- package/tests/config_test2/DBoGenerated.d.ts +0 -135
- package/tests/config_test2/tsconfig.json +0 -21
package/lib/DboBuilder.ts
CHANGED
|
@@ -49,8 +49,8 @@ export type TX = {
|
|
|
49
49
|
(t: TxCB): Promise<(any | void)>;
|
|
50
50
|
}
|
|
51
51
|
|
|
52
|
-
type TableHandlers = {
|
|
53
|
-
[key: string]: Partial<TableHandler
|
|
52
|
+
export type TableHandlers = {
|
|
53
|
+
[key: string]: Partial<TableHandler> | TableHandler;
|
|
54
54
|
}
|
|
55
55
|
|
|
56
56
|
export type DBHandlerServer =
|
|
@@ -76,12 +76,14 @@ export const getUpdateFilter = (args: { filter?: AnyObject; forcedFilter?: AnyOb
|
|
|
76
76
|
import { get } from "./utils";
|
|
77
77
|
import { getNewQuery, makeQuery, COMPUTED_FIELDS, SelectItem, FieldSpec, asNameAlias, SelectItemBuilder, FUNCTIONS, parseFunction, parseFunctionObject } from "./QueryBuilder";
|
|
78
78
|
import {
|
|
79
|
-
Join, Prostgles, DB
|
|
79
|
+
Join, Prostgles, DB, isSuperUser
|
|
80
80
|
} from "./Prostgles";
|
|
81
81
|
import {
|
|
82
82
|
TableRule, UpdateRule, SyncRule, PublishParser, ValidateRow, ValidateUpdateRow, PublishAllOrNothing, PublishParams, DeleteRule
|
|
83
83
|
} from "./PublishParser";
|
|
84
84
|
import { PubSubManager, asValue, BasicCallback, pickKeys, omitKeys } from "./PubSubManager";
|
|
85
|
+
import { insertDataParse } from "./DboBuilder/insertDataParse";
|
|
86
|
+
import { insert } from "./DboBuilder/insert";
|
|
85
87
|
|
|
86
88
|
import { parseFilterItem } from "./Filtering";
|
|
87
89
|
|
|
@@ -242,9 +244,10 @@ export type JoinInfo = {
|
|
|
242
244
|
|
|
243
245
|
/**
|
|
244
246
|
* Source and target JOIN ON columns
|
|
245
|
-
*
|
|
247
|
+
* Each inner array group will be combined with AND and outer arrays with OR to allow multiple references to the same table
|
|
248
|
+
* e.g.: [[source_table_column: string, table_column: string]]
|
|
246
249
|
*/
|
|
247
|
-
on: [string, string][],
|
|
250
|
+
on: [string, string][][],
|
|
248
251
|
|
|
249
252
|
/**
|
|
250
253
|
* Source table name
|
|
@@ -335,7 +338,7 @@ export type ValidatedTableRules = CommonTableRules & {
|
|
|
335
338
|
}
|
|
336
339
|
|
|
337
340
|
/* DEBUG CLIENT ERRORS HERE */
|
|
338
|
-
function makeErr(err: any, localParams?: LocalParams, view?: ViewHandler, allowedKeys?: string[]){
|
|
341
|
+
export function makeErr(err: any, localParams?: LocalParams, view?: ViewHandler, allowedKeys?: string[]){
|
|
339
342
|
// console.trace(err)
|
|
340
343
|
if(process.env.TEST_TYPE || process.env.PRGL_DEBUG) {
|
|
341
344
|
console.trace(err)
|
|
@@ -367,10 +370,10 @@ export type EXISTS_KEY = typeof EXISTS_KEYS[number];
|
|
|
367
370
|
|
|
368
371
|
const FILTER_FUNCS = FUNCTIONS.filter(f => f.canBeUsedForFilter);
|
|
369
372
|
|
|
370
|
-
function parseError(e: any){
|
|
373
|
+
export function parseError(e: any){
|
|
371
374
|
|
|
372
375
|
// console.trace("INTERNAL ERROR: ", e);
|
|
373
|
-
let res = (!Object.keys(e || {}).length? e : (e && e.toString)? e.toString() : e);
|
|
376
|
+
let res = e instanceof Error? e.message : (!Object.keys(e || {}).length? e : (e && e.toString)? e.toString() : e);
|
|
374
377
|
if(isPlainObject(e)) res = JSON.stringify(e, null, 2)
|
|
375
378
|
return res;
|
|
376
379
|
}
|
|
@@ -627,6 +630,10 @@ export class ViewHandler {
|
|
|
627
630
|
// while (!result && searchedTables.length <= this.joins.length * 2){
|
|
628
631
|
|
|
629
632
|
// }
|
|
633
|
+
|
|
634
|
+
const getJoinCondition = (on: Record<string, string>[], leftTable: string, rightTable: string) => {
|
|
635
|
+
return on.map(cond => Object.keys(cond).map(lKey => `${leftTable}.${lKey} = ${rightTable}.${cond[lKey]}`).join("\nAND ")).join(" OR ")
|
|
636
|
+
}
|
|
630
637
|
|
|
631
638
|
let toOne = true,
|
|
632
639
|
query = this.joins.map(({ tables, on, type }, i) => {
|
|
@@ -635,7 +642,7 @@ export class ViewHandler {
|
|
|
635
642
|
}
|
|
636
643
|
const tl = `tl${startAlias + i}`,
|
|
637
644
|
tr = `tr${startAlias + i}`;
|
|
638
|
-
return `FROM ${tables[0]} ${tl} ${isInner? "INNER" : "LEFT"} JOIN ${tables[1]} ${tr} ON ${
|
|
645
|
+
return `FROM ${tables[0]} ${tl} ${isInner? "INNER" : "LEFT"} JOIN ${tables[1]} ${tr} ON ${getJoinCondition(on, tl, tr)}`;
|
|
639
646
|
}).join("\n");
|
|
640
647
|
return { query, toOne: false }
|
|
641
648
|
}
|
|
@@ -675,21 +682,26 @@ export class ViewHandler {
|
|
|
675
682
|
const jo = this.joins.find(j => j.tables.includes(t1) && j.tables.includes(t2));
|
|
676
683
|
if(!jo) throw `Joining ${t1} <-> ${t2} dissallowed or missing`;;
|
|
677
684
|
|
|
678
|
-
let on: [string, string][] = [];
|
|
679
|
-
|
|
680
|
-
Object.keys(jo.on).map(leftKey => {
|
|
681
|
-
const rightKey = jo.on[leftKey];
|
|
682
|
-
|
|
683
|
-
/* Left table is joining on keys */
|
|
684
|
-
if(jo.tables[0] === t1){
|
|
685
|
-
on.push([leftKey, rightKey])
|
|
686
|
-
|
|
687
|
-
/* Left table is joining on values */
|
|
688
|
-
} else {
|
|
689
|
-
on.push([rightKey, leftKey])
|
|
685
|
+
let on: [string, string][][] = [];
|
|
690
686
|
|
|
691
|
-
|
|
692
|
-
|
|
687
|
+
jo.on.map(cond => {
|
|
688
|
+
let condArr: [string, string][] = [];
|
|
689
|
+
Object.keys(cond).map(leftKey => {
|
|
690
|
+
const rightKey = cond[leftKey];
|
|
691
|
+
|
|
692
|
+
/* Left table is joining on keys */
|
|
693
|
+
if(jo.tables[0] === t1){
|
|
694
|
+
condArr.push([leftKey, rightKey])
|
|
695
|
+
|
|
696
|
+
/* Left table is joining on values */
|
|
697
|
+
} else {
|
|
698
|
+
condArr.push([rightKey, leftKey])
|
|
699
|
+
|
|
700
|
+
}
|
|
701
|
+
});
|
|
702
|
+
on.push(condArr);
|
|
703
|
+
})
|
|
704
|
+
|
|
693
705
|
|
|
694
706
|
return {
|
|
695
707
|
source,
|
|
@@ -1065,7 +1077,8 @@ export class ViewHandler {
|
|
|
1065
1077
|
} catch(e){
|
|
1066
1078
|
// console.trace(e)
|
|
1067
1079
|
if(localParams && localParams.testRule) throw e;
|
|
1068
|
-
|
|
1080
|
+
// ${JSON.stringify(filter || {}, null, 2)}, ${JSON.stringify(selectParams || {}, null, 2)}
|
|
1081
|
+
throw { err: parseError(e), msg: `Issue with dbo.${this.name}.find()`, args: { filter, selectParams} };
|
|
1069
1082
|
}
|
|
1070
1083
|
}
|
|
1071
1084
|
|
|
@@ -1295,8 +1308,9 @@ export class ViewHandler {
|
|
|
1295
1308
|
let tableAlias = asName(ji < paths.length - 1? `jd${ji}` : table);
|
|
1296
1309
|
let prevTableAlias = asName(ji? `jd${ji - 1}` : thisTable);
|
|
1297
1310
|
|
|
1298
|
-
let cond = `${jp.on.map(
|
|
1299
|
-
|
|
1311
|
+
let cond = `${jp.on.map(c => {
|
|
1312
|
+
return c.map(([c1, c2]) => `${prevTableAlias}.${asName(c1)} = ${tableAlias}.${asName(c2)}` ).join(" AND ")
|
|
1313
|
+
}).join("\n OR ")
|
|
1300
1314
|
}`;
|
|
1301
1315
|
|
|
1302
1316
|
let j = `SELECT 1 \n` +
|
|
@@ -1907,7 +1921,7 @@ export class ViewHandler {
|
|
|
1907
1921
|
}
|
|
1908
1922
|
}
|
|
1909
1923
|
|
|
1910
|
-
function isPojoObject<T>(obj: T): obj is Record<string, any> {
|
|
1924
|
+
export function isPojoObject<T>(obj: T): obj is Record<string, any> {
|
|
1911
1925
|
if(obj && (typeof obj !== "object" || Array.isArray(obj) || obj instanceof Date)){
|
|
1912
1926
|
return false;
|
|
1913
1927
|
}
|
|
@@ -2305,380 +2319,10 @@ export class TableHandler extends ViewHandler {
|
|
|
2305
2319
|
return { data, allowedCols: this.columns.filter(c => dataKeys.includes(c.name)).map(c => c.name) }
|
|
2306
2320
|
}
|
|
2307
2321
|
|
|
2308
|
-
|
|
2309
|
-
private async insertDataParse(data: (AnyObject | AnyObject[]), param2?: InsertParams, param3_unused?: undefined, tableRules?: TableRule, _localParams?: LocalParams): Promise<{
|
|
2310
|
-
data?: AnyObject | AnyObject[];
|
|
2311
|
-
insertResult?: AnyObject | AnyObject[];
|
|
2312
|
-
}>{
|
|
2313
|
-
const localParams = _localParams || {};
|
|
2314
|
-
let dbTX = localParams?.dbTX || this.dbTX;
|
|
2315
|
-
|
|
2316
|
-
const isMultiInsert = Array.isArray(data);
|
|
2317
|
-
const getExtraKeys = (d: AnyObject)=> Object.keys(d).filter(k => !this.columns.find(c => c.name === k));
|
|
2318
|
-
|
|
2319
|
-
/* Nested insert is not allowed for the file table */
|
|
2320
|
-
const isNestedInsert = this.is_media? false : (Array.isArray(data)? data : [data]).some(d => getExtraKeys(d).length);
|
|
2321
|
-
|
|
2322
|
-
/**
|
|
2323
|
-
* Make sure nested insert uses a transaction
|
|
2324
|
-
*/
|
|
2325
|
-
if(isNestedInsert && !dbTX){
|
|
2326
|
-
return {
|
|
2327
|
-
insertResult: await this.dboBuilder.getTX((dbTX) =>
|
|
2328
|
-
(dbTX[this.name] as TableHandler).insert(
|
|
2329
|
-
data,
|
|
2330
|
-
param2,
|
|
2331
|
-
param3_unused,
|
|
2332
|
-
tableRules,
|
|
2333
|
-
{dbTX, ...localParams}
|
|
2334
|
-
)
|
|
2335
|
-
)
|
|
2336
|
-
}
|
|
2337
|
-
}
|
|
2338
|
-
// if(!dbTX && this.t) dbTX = this.d;
|
|
2339
|
-
|
|
2340
|
-
const preValidate = tableRules?.insert?.preValidate,
|
|
2341
|
-
validate = tableRules?.insert?.validate;
|
|
2342
|
-
|
|
2343
|
-
let _data = await Promise.all((Array.isArray(data)? data : [data]).map(async row => {
|
|
2344
|
-
if(preValidate){
|
|
2345
|
-
row = await preValidate(row);
|
|
2346
|
-
}
|
|
2347
|
-
|
|
2348
|
-
const dataKeys = Object.keys(row);
|
|
2349
|
-
const extraKeys = getExtraKeys(row);
|
|
2350
|
-
|
|
2351
|
-
/* Upload file then continue insert */
|
|
2352
|
-
if(this.is_media){
|
|
2353
|
-
if(!this.dboBuilder.prostgles?.fileManager) throw "fileManager not set up";
|
|
2354
|
-
const { data, name } = row;
|
|
2355
|
-
|
|
2356
|
-
if(dataKeys.length !== 2) throw "Expecting only two properties: { name: string; data: File }";
|
|
2357
|
-
|
|
2358
|
-
// if(!Buffer.isBuffer(data)) throw "data is not of type Buffer"
|
|
2359
|
-
if(!data) throw "data not provided"
|
|
2360
|
-
if(typeof name !== "string"){
|
|
2361
|
-
throw "name is not of type string"
|
|
2362
|
-
}
|
|
2363
|
-
|
|
2364
|
-
const media_id = (await this.db.oneOrNone("SELECT gen_random_uuid() as name")).name;
|
|
2365
|
-
const type = await this.dboBuilder.prostgles.fileManager.getMIME(data, name)
|
|
2366
|
-
const media_name = `${media_id}.${type.ext}`;
|
|
2367
|
-
let media: Media = {
|
|
2368
|
-
id: media_id,
|
|
2369
|
-
name: media_name,
|
|
2370
|
-
original_name: name,
|
|
2371
|
-
extension: type.ext,
|
|
2372
|
-
content_type: type.mime
|
|
2373
|
-
}
|
|
2374
|
-
|
|
2375
|
-
if(validate){
|
|
2376
|
-
media = await validate(media);
|
|
2377
|
-
}
|
|
2378
|
-
|
|
2379
|
-
const _media: Media = await this.dboBuilder.prostgles.fileManager.uploadAsMedia({
|
|
2380
|
-
item: {
|
|
2381
|
-
data,
|
|
2382
|
-
name: media.name ?? "????",
|
|
2383
|
-
content_type: media.content_type as any
|
|
2384
|
-
},
|
|
2385
|
-
// imageCompression: {
|
|
2386
|
-
// inside: {
|
|
2387
|
-
// width: 1100,
|
|
2388
|
-
// height: 630
|
|
2389
|
-
// }
|
|
2390
|
-
// }
|
|
2391
|
-
});
|
|
2392
|
-
|
|
2393
|
-
return {
|
|
2394
|
-
...media,
|
|
2395
|
-
..._media,
|
|
2396
|
-
};
|
|
2397
|
-
|
|
2398
|
-
/* Potentially a nested join */
|
|
2399
|
-
} else if(extraKeys.length){
|
|
2400
|
-
|
|
2401
|
-
/* Ensure we're using the same transaction */
|
|
2402
|
-
const _this = this.t? this : dbTX![this.name] as TableHandler;
|
|
2403
|
-
|
|
2404
|
-
let rootData = Array.isArray(data)? data.map(d => omitKeys(d, extraKeys)) : omitKeys(data, extraKeys);
|
|
2405
|
-
|
|
2406
|
-
let insertedChildren: AnyObject[];
|
|
2407
|
-
let targetTableRules: TableRule;
|
|
2408
|
-
|
|
2409
|
-
const fullRootResult = await _this.insert(rootData, { returning: "*" }, undefined, tableRules, localParams);
|
|
2410
|
-
let returnData: AnyObject | undefined;
|
|
2411
|
-
const returning = param2?.returning;
|
|
2412
|
-
if(returning){
|
|
2413
|
-
returnData = {}
|
|
2414
|
-
const returningItems = await this.prepareReturning(returning, this.parseFieldFilter(tableRules?.insert?.returningFields));
|
|
2415
|
-
returningItems.filter(s => s.selected).map(rs => {
|
|
2416
|
-
returnData![rs.alias] = fullRootResult[rs.alias];
|
|
2417
|
-
})
|
|
2418
|
-
}
|
|
2419
|
-
|
|
2420
|
-
await Promise.all(extraKeys.map(async targetTable => {
|
|
2421
|
-
const childDataItems = Array.isArray(row[targetTable])? row[targetTable] : [row[targetTable]];
|
|
2422
|
-
|
|
2423
|
-
/* Must be allowed to insert into media table */
|
|
2424
|
-
const canInsert = async (tbl: string) => {
|
|
2425
|
-
const childRules = await this.dboBuilder.publishParser?.getValidatedRequestRuleWusr({ tableName: tbl, command: "insert", localParams });
|
|
2426
|
-
if(!childRules || !childRules.insert) throw "Dissallowed nested insert into table " + childRules;
|
|
2427
|
-
return childRules;
|
|
2428
|
-
}
|
|
2429
|
-
|
|
2430
|
-
// console.log(JSON.stringify(this.dboBuilder.joinPaths, null, 2))
|
|
2431
|
-
const jp = this.dboBuilder.joinPaths.find(jp => jp.t1 === this.name && jp.t2 === targetTable);
|
|
2432
|
-
if(!jp) throw `Could not find a valid table for the nested data { ${targetTable} } `;
|
|
2433
|
-
|
|
2434
|
-
const thisInfo = await this.getInfo();
|
|
2435
|
-
const childInsert = async (cdata: AnyObject | AnyObject[], tableName: string) => {
|
|
2436
|
-
// console.log("childInsert", {data, tableName})
|
|
2437
|
-
if(!cdata || !dbTX?.[tableName] || !("insert" in dbTX[tableName])) throw "childInsertErr: Child table handler missing for: " + tableName;
|
|
2438
|
-
|
|
2439
|
-
const tableRules = await canInsert(tableName);
|
|
2440
|
-
|
|
2441
|
-
if(thisInfo.has_media === "one" && thisInfo.media_table_name === tableName && Array.isArray(cdata) && cdata.length > 1){
|
|
2442
|
-
throw "Constraint check fail: Cannot insert more than one record into " + JSON.stringify(tableName);
|
|
2443
|
-
}
|
|
2444
|
-
return Promise.all(
|
|
2445
|
-
(Array.isArray(cdata)? cdata : [cdata])
|
|
2446
|
-
.map(m => (dbTX![tableName] as TableHandler)
|
|
2447
|
-
.insert(m, { returning: "*" }, undefined, tableRules, localParams)
|
|
2448
|
-
.catch(e => {
|
|
2449
|
-
console.trace({ childInsertErr: e })
|
|
2450
|
-
return Promise.reject({ childInsertErr: e });
|
|
2451
|
-
})
|
|
2452
|
-
)
|
|
2453
|
-
);
|
|
2454
|
-
}
|
|
2455
|
-
|
|
2456
|
-
const { path } = jp;
|
|
2457
|
-
const [tbl1, tbl2, tbl3] = path;
|
|
2458
|
-
targetTableRules = await canInsert(targetTable); // tbl3
|
|
2459
|
-
|
|
2460
|
-
const cols2 = this.dboBuilder.dbo[tbl2].columns || [];
|
|
2461
|
-
if(!this.dboBuilder.dbo[tbl2]) throw "Invalid/disallowed table: " + tbl2;
|
|
2462
|
-
const colsRefT1 = cols2?.filter(c => c.references?.cols.length === 1 && c.references?.ftable === tbl1);
|
|
2463
|
-
|
|
2464
|
-
|
|
2465
|
-
if(!path.length) {
|
|
2466
|
-
throw "Nested inserts join path not found for " + [this.name, targetTable];
|
|
2467
|
-
} else if(path.length === 2){
|
|
2468
|
-
if(targetTable !== tbl2) throw "Did not expect this";
|
|
2469
|
-
|
|
2470
|
-
if(!colsRefT1.length) throw `Target table ${tbl2} does not reference any columns from the root table ${this.name}. Cannot do nested insert`;
|
|
2471
|
-
|
|
2472
|
-
// console.log(childDataItems, JSON.stringify(colsRefT1, null, 2))
|
|
2473
|
-
insertedChildren = await childInsert(
|
|
2474
|
-
childDataItems.map((d: AnyObject) => {
|
|
2475
|
-
let result = {...d};
|
|
2476
|
-
colsRefT1.map(col => {
|
|
2477
|
-
result[col.references!.cols[0]] = fullRootResult[col.references!.fcols[0]]
|
|
2478
|
-
})
|
|
2479
|
-
return result;
|
|
2480
|
-
}),
|
|
2481
|
-
targetTable
|
|
2482
|
-
);
|
|
2483
|
-
// console.log({ insertedChildren })
|
|
2484
|
-
|
|
2485
|
-
} else if(path.length === 3){
|
|
2486
|
-
if(targetTable !== tbl3) throw "Did not expect this";
|
|
2487
|
-
const colsRefT3 = cols2?.filter(c => c.references?.cols.length === 1 && c.references?.ftable === tbl3);
|
|
2488
|
-
if(!colsRefT1.length || !colsRefT3.length) throw "Incorrectly referenced or missing columns for nested insert";
|
|
2489
|
-
|
|
2490
|
-
if(targetTable !== this.dboBuilder.prostgles.fileManager?.tableName){
|
|
2491
|
-
throw "Only media allowed to have nested inserts more than 2 tables apart"
|
|
2492
|
-
}
|
|
2493
|
-
|
|
2494
|
-
/* We expect tbl2 to have only 2 columns (media_id and foreign_id) */
|
|
2495
|
-
if(!cols2 || cols2.find(c => !["media_id", "foreign_id"].includes(c.name))){
|
|
2496
|
-
throw "Second joining table not of expected format";
|
|
2497
|
-
}
|
|
2498
|
-
|
|
2499
|
-
insertedChildren = await childInsert(childDataItems, targetTable);
|
|
2500
|
-
|
|
2501
|
-
/* Insert in key_lookup table */
|
|
2502
|
-
await Promise.all(insertedChildren.map(async t3Child => {
|
|
2503
|
-
let tbl2Row: AnyObject = {};
|
|
2504
|
-
|
|
2505
|
-
colsRefT3.map(col => {
|
|
2506
|
-
tbl2Row[col.name] = t3Child[col.references!.fcols[0]];
|
|
2507
|
-
})
|
|
2508
|
-
colsRefT1.map(col => {
|
|
2509
|
-
tbl2Row[col.name] = fullRootResult[col.references!.fcols[0]];
|
|
2510
|
-
})
|
|
2511
|
-
// console.log({ rootResult, tbl2Row, t3Child, colsRefT3, colsRefT1, t: this.t?.ctx?.start });
|
|
2512
|
-
|
|
2513
|
-
await childInsert(tbl2Row, tbl2);//.then(() => {});
|
|
2514
|
-
}));
|
|
2515
|
-
|
|
2516
|
-
} else {
|
|
2517
|
-
console.error(JSON.stringify({ path, thisTable: this.name, targetTable }, null, 2));
|
|
2518
|
-
throw "Unexpected path for Nested inserts";
|
|
2519
|
-
}
|
|
2520
|
-
|
|
2521
|
-
/* Return also the nested inserted data */
|
|
2522
|
-
if(targetTableRules && insertedChildren?.length && returning){
|
|
2523
|
-
const targetTableHandler = dbTX![targetTable] as TableHandler;
|
|
2524
|
-
const targetReturning = await targetTableHandler.prepareReturning("*", targetTableHandler.parseFieldFilter(targetTableRules?.insert?.returningFields));
|
|
2525
|
-
let clientTargetInserts = insertedChildren.map(d => {
|
|
2526
|
-
let _d = { ...d };
|
|
2527
|
-
let res: AnyObject = {};
|
|
2528
|
-
targetReturning.map(r => {
|
|
2529
|
-
res[r.alias] = _d[r.alias]
|
|
2530
|
-
});
|
|
2531
|
-
return res;
|
|
2532
|
-
});
|
|
2533
|
-
|
|
2534
|
-
returnData![targetTable] = clientTargetInserts.length === 1? clientTargetInserts[0] : clientTargetInserts;
|
|
2535
|
-
}
|
|
2536
|
-
}));
|
|
2537
|
-
|
|
2538
|
-
return returnData
|
|
2539
|
-
}
|
|
2540
|
-
|
|
2541
|
-
return row;
|
|
2542
|
-
}));
|
|
2543
|
-
|
|
2544
|
-
let result = isMultiInsert? _data : _data[0];
|
|
2545
|
-
// if(validate && !isNestedInsert){
|
|
2546
|
-
// result = isMultiInsert? await Promise.all(_data.map(async d => await validate({ ...d }))) : await validate({ ..._data[0] });
|
|
2547
|
-
// }
|
|
2548
|
-
let res = isNestedInsert?
|
|
2549
|
-
{ insertResult: result } :
|
|
2550
|
-
{ data: result };
|
|
2551
|
-
|
|
2552
|
-
return res;
|
|
2553
|
-
}
|
|
2554
|
-
|
|
2322
|
+
insertDataParse = insertDataParse;
|
|
2555
2323
|
async insert(rowOrRows: (AnyObject | AnyObject[]), param2?: InsertParams, param3_unused?: undefined, tableRules?: TableRule, _localParams?: LocalParams): Promise<any | any[] | boolean>{
|
|
2556
|
-
|
|
2557
|
-
|
|
2558
|
-
try {
|
|
2559
|
-
|
|
2560
|
-
const { returning, onConflictDoNothing, fixIssues = false } = param2 || {};
|
|
2561
|
-
const { testRule = false, returnQuery = false } = localParams || {};
|
|
2562
|
-
|
|
2563
|
-
let returningFields: FieldFilter | undefined,
|
|
2564
|
-
forcedData: AnyObject | undefined,
|
|
2565
|
-
fields: FieldFilter | undefined;
|
|
2566
|
-
|
|
2567
|
-
if(tableRules){
|
|
2568
|
-
if(!tableRules.insert) throw "insert rules missing for " + this.name;
|
|
2569
|
-
returningFields = tableRules.insert.returningFields;
|
|
2570
|
-
forcedData = tableRules.insert.forcedData;
|
|
2571
|
-
fields = tableRules.insert.fields;
|
|
2572
|
-
|
|
2573
|
-
/* If no returning fields specified then take select fields as returning */
|
|
2574
|
-
if(!returningFields) returningFields = get(tableRules, "select.fields") || get(tableRules, "insert.fields");
|
|
2575
|
-
|
|
2576
|
-
if(!fields) throw ` invalid insert rule for ${this.name} -> fields missing `;
|
|
2577
|
-
|
|
2578
|
-
/* Safely test publish rules */
|
|
2579
|
-
if(testRule){
|
|
2580
|
-
// if(this.is_media && tableRules.insert.preValidate) throw "Media table cannot have a preValidate. It already is used internally by prostgles for file upload";
|
|
2581
|
-
await this.validateViewRules({ fields, returningFields, forcedFilter: forcedData, rule: "insert"});
|
|
2582
|
-
if(forcedData) {
|
|
2583
|
-
const keys = Object.keys(forcedData);
|
|
2584
|
-
if(keys.length){
|
|
2585
|
-
try {
|
|
2586
|
-
const colset = new pgp.helpers.ColumnSet(this.columns.filter(c => keys.includes(c.name)).map(c => ({ name: c.name, cast: c.udt_name === "uuid"? c.udt_name : undefined }))),
|
|
2587
|
-
values = pgp.helpers.values(forcedData, colset),
|
|
2588
|
-
colNames = this.prepareSelect(keys, this.column_names);
|
|
2589
|
-
await this.db.any("EXPLAIN INSERT INTO " + this.escapedName + " (${colNames:raw}) SELECT * FROM ( VALUES ${values:raw} ) t WHERE FALSE;", { colNames, values })
|
|
2590
|
-
} catch(e){
|
|
2591
|
-
throw "\nissue with forcedData: \nVALUE: " + JSON.stringify(forcedData, null, 2) + "\nERROR: " + e;
|
|
2592
|
-
}
|
|
2593
|
-
}
|
|
2594
|
-
}
|
|
2595
|
-
|
|
2596
|
-
return true;
|
|
2597
|
-
}
|
|
2598
|
-
}
|
|
2599
|
-
|
|
2600
|
-
let conflict_query = "";
|
|
2601
|
-
if(typeof onConflictDoNothing === "boolean" && onConflictDoNothing){
|
|
2602
|
-
conflict_query = " ON CONFLICT DO NOTHING ";
|
|
2603
|
-
}
|
|
2604
|
-
|
|
2605
|
-
if(param2){
|
|
2606
|
-
const good_params = ["returning", "multi", "onConflictDoNothing", "fixIssues"];
|
|
2607
|
-
const bad_params = Object.keys(param2).filter(k => !good_params.includes(k));
|
|
2608
|
-
if(bad_params && bad_params.length) throw "Invalid params: " + bad_params.join(", ") + " \n Expecting: " + good_params.join(", ");
|
|
2609
|
-
}
|
|
2610
|
-
|
|
2611
|
-
if(!rowOrRows) rowOrRows = {}; //throw "Provide data in param1";
|
|
2612
|
-
let returningSelect = this.makeReturnQuery(await this.prepareReturning(returning, this.parseFieldFilter(returningFields)));
|
|
2613
|
-
const makeQuery = async (_row: AnyObject | undefined, isOne = false) => {
|
|
2614
|
-
let row = { ..._row };
|
|
2615
|
-
|
|
2616
|
-
if(!isPojoObject(row)) {
|
|
2617
|
-
console.trace(row)
|
|
2618
|
-
throw "\ninvalid insert data provided -> " + JSON.stringify(row);
|
|
2619
|
-
}
|
|
2620
|
-
|
|
2621
|
-
const { data, allowedCols } = this.validateNewData({ row, forcedData, allowedFields: fields, tableRules, fixIssues });
|
|
2622
|
-
let _data = { ...data };
|
|
2623
|
-
|
|
2624
|
-
let insertQ = "";
|
|
2625
|
-
if(!Object.keys(_data).length) insertQ = `INSERT INTO ${asName(this.name)} DEFAULT VALUES `;
|
|
2626
|
-
else insertQ = await this.colSet.getInsertQuery(_data, allowedCols, tableRules?.insert?.validate) // pgp.helpers.insert(_data, columnSet);
|
|
2627
|
-
return insertQ + conflict_query + returningSelect;
|
|
2628
|
-
};
|
|
2629
|
-
|
|
2630
|
-
let query = "";
|
|
2631
|
-
let queryType: keyof pgPromise.ITask<{}> = "none";
|
|
2632
|
-
|
|
2633
|
-
/**
|
|
2634
|
-
* If media it will: upload file and continue insert
|
|
2635
|
-
* If nested insert it will: make separate inserts and not continue main insert
|
|
2636
|
-
*/
|
|
2637
|
-
const insRes = await this.insertDataParse(rowOrRows, param2, param3_unused, tableRules, localParams);
|
|
2638
|
-
const { data, insertResult } = insRes;
|
|
2639
|
-
if("insertResult" in insRes){
|
|
2640
|
-
return insertResult;
|
|
2641
|
-
}
|
|
2642
|
-
|
|
2643
|
-
if(Array.isArray(data)){
|
|
2644
|
-
// if(returning) throw "Sorry but [returning] is dissalowed for multi insert";
|
|
2645
|
-
let queries = await Promise.all(data.map(async p => {
|
|
2646
|
-
const q = await makeQuery(p);
|
|
2647
|
-
return q;
|
|
2648
|
-
}));
|
|
2649
|
-
|
|
2650
|
-
query = pgp.helpers.concat(queries);
|
|
2651
|
-
if(returning) queryType = "many";
|
|
2652
|
-
} else {
|
|
2653
|
-
query = await makeQuery(data, true);
|
|
2654
|
-
if(returning) queryType = "one";
|
|
2655
|
-
}
|
|
2656
|
-
|
|
2657
|
-
if(returnQuery) return query;
|
|
2658
|
-
let result;
|
|
2659
|
-
|
|
2660
|
-
if(this.dboBuilder.prostgles.opts.DEBUG_MODE){
|
|
2661
|
-
console.log(this.t?.ctx?.start, "insert in " + this.name, data);
|
|
2662
|
-
}
|
|
2663
|
-
|
|
2664
|
-
const tx = dbTX?.[this.name]?.t || this.t;
|
|
2665
|
-
|
|
2666
|
-
const allowedFieldKeys = this.parseFieldFilter(fields);
|
|
2667
|
-
if(tx) {
|
|
2668
|
-
result = (tx as any)[queryType](query).catch((err: any) => makeErr(err, localParams, this, allowedFieldKeys));
|
|
2669
|
-
} else {
|
|
2670
|
-
result = this.db.tx(t => (t as any)[queryType](query)).catch(err => makeErr(err, localParams, this, allowedFieldKeys));
|
|
2671
|
-
}
|
|
2672
|
-
|
|
2673
|
-
return result;
|
|
2674
|
-
} catch(e){
|
|
2675
|
-
if(localParams && localParams.testRule) throw e;
|
|
2676
|
-
throw { err: parseError(e), msg: `Issue with dbo.${this.name}.insert(
|
|
2677
|
-
${JSON.stringify(rowOrRows || {}, null, 2)},
|
|
2678
|
-
${JSON.stringify(param2 || {}, null, 2)}
|
|
2679
|
-
)` };
|
|
2680
|
-
}
|
|
2681
|
-
};
|
|
2324
|
+
return insert.bind(this)(rowOrRows, param2, param3_unused, tableRules, _localParams)
|
|
2325
|
+
}
|
|
2682
2326
|
|
|
2683
2327
|
prepareReturning = async (returning: Select | undefined, allowedFields: string[]): Promise<SelectItem[]> => {
|
|
2684
2328
|
let result: SelectItem[] = [];
|
|
@@ -2928,9 +2572,9 @@ export class DboBuilder {
|
|
|
2928
2572
|
if(!this._pubSubManager){
|
|
2929
2573
|
let onSchemaChange;
|
|
2930
2574
|
|
|
2931
|
-
if(this.prostgles.opts.watchSchema && this.prostgles.opts.watchSchemaType === "
|
|
2575
|
+
if(this.prostgles.opts.watchSchema && this.prostgles.opts.watchSchemaType === "DDL_trigger"){
|
|
2932
2576
|
if(!this.prostgles.isSuperUser){
|
|
2933
|
-
console.warn(`watchSchemaType "events" cannot be used because db user is not a superuser. Will fallback to watchSchemaType "
|
|
2577
|
+
console.warn(`watchSchemaType "events" cannot be used because db user is not a superuser. Will fallback to watchSchemaType "prostgles_queries" `)
|
|
2934
2578
|
} else {
|
|
2935
2579
|
onSchemaChange = (event: { command: string; query: string }) => {
|
|
2936
2580
|
this.prostgles.onSchemaChange(event)
|
|
@@ -2949,7 +2593,10 @@ export class DboBuilder {
|
|
|
2949
2593
|
console.warn(`subscribe and sync cannot be used because db user is not a superuser `)
|
|
2950
2594
|
}
|
|
2951
2595
|
}
|
|
2952
|
-
if(!this._pubSubManager)
|
|
2596
|
+
if(!this._pubSubManager) {
|
|
2597
|
+
console.trace("Could not create this._pubSubManager")
|
|
2598
|
+
throw "Could not create this._pubSubManager";
|
|
2599
|
+
}
|
|
2953
2600
|
return this._pubSubManager;
|
|
2954
2601
|
}
|
|
2955
2602
|
|
|
@@ -2981,7 +2628,7 @@ export class DboBuilder {
|
|
|
2981
2628
|
|
|
2982
2629
|
/* If watchSchema then PubSubManager must be created */
|
|
2983
2630
|
await this.build();
|
|
2984
|
-
if(this.prostgles.opts.watchSchema){
|
|
2631
|
+
if(this.prostgles.opts.watchSchema && (this.prostgles.opts.watchSchemaType === "DDL_trigger" || !this.prostgles.opts.watchSchemaType) && this.prostgles.isSuperUser){
|
|
2985
2632
|
await this.getPubSubManager()
|
|
2986
2633
|
}
|
|
2987
2634
|
|
|
@@ -3008,13 +2655,16 @@ export class DboBuilder {
|
|
|
3008
2655
|
async parseJoins(): Promise<JoinPaths> {
|
|
3009
2656
|
if(this.prostgles.opts.joins){
|
|
3010
2657
|
let _joins = await this.prostgles.opts.joins;
|
|
3011
|
-
|
|
3012
|
-
|
|
2658
|
+
if(!this.tablesOrViews) throw new Error("Could not create join config. this.tablesOrViews missing");
|
|
2659
|
+
let inferredJoins = await getInferredJoins2(this.tablesOrViews);
|
|
2660
|
+
if(_joins === "inferred"){
|
|
3013
2661
|
_joins = inferredJoins
|
|
3014
2662
|
/* If joins are specified then include inferred joins except the explicit tables */
|
|
3015
2663
|
} else if(Array.isArray(_joins)){
|
|
3016
2664
|
const joinTables = _joins.map(j => j.tables).flat();
|
|
3017
2665
|
_joins = _joins.concat(inferredJoins.filter(j => !j.tables.find(t => joinTables.includes(t))))
|
|
2666
|
+
} else if(_joins){
|
|
2667
|
+
throw new Error("Unexpected joins init param. Expecting 'inferred' OR joinConfig but got: " + JSON.stringify(_joins))
|
|
3018
2668
|
}
|
|
3019
2669
|
let joins = JSON.parse(JSON.stringify(_joins)) as Join[];
|
|
3020
2670
|
this.joins = joins;
|
|
@@ -3221,7 +2871,7 @@ export class DboBuilder {
|
|
|
3221
2871
|
if(typeof this.prostgles.opts.transactions === "string") txKey = this.prostgles.opts.transactions;
|
|
3222
2872
|
this.dboDefinition += ` ${txKey}: (t: TxCB) => Promise<any | void> ;\n`;
|
|
3223
2873
|
|
|
3224
|
-
(this.dbo[txKey] as TX) = (cb: TxCB) => this.getTX(cb);
|
|
2874
|
+
(this.dbo[txKey] as unknown as TX) = (cb: TxCB) => this.getTX(cb);
|
|
3225
2875
|
}
|
|
3226
2876
|
|
|
3227
2877
|
if(!this.dbo.sql){
|
|
@@ -3272,15 +2922,16 @@ export class DboBuilder {
|
|
|
3272
2922
|
|
|
3273
2923
|
if(
|
|
3274
2924
|
watchSchema &&
|
|
3275
|
-
(!this.prostgles.isSuperUser || watchSchemaType === "
|
|
3276
|
-
(
|
|
3277
|
-
["CREATE", "ALTER", "DROP"].includes(command) ||
|
|
3278
|
-
|
|
3279
|
-
// Cover this case: `CREATE TABLE mytable AS SELECT`
|
|
3280
|
-
query && query.toLowerCase().replace(/\s\s+/g, ' ').includes("create table")
|
|
3281
|
-
)
|
|
2925
|
+
(!this.prostgles.isSuperUser || watchSchemaType === "prostgles_queries")
|
|
3282
2926
|
){
|
|
3283
|
-
|
|
2927
|
+
if(["CREATE", "ALTER", "DROP"].includes(command)){
|
|
2928
|
+
this.prostgles.onSchemaChange({ command, query })
|
|
2929
|
+
} else if(query) {
|
|
2930
|
+
const cleanedQuery = query.toLowerCase().replace(/\s\s+/g, ' ');
|
|
2931
|
+
if(PubSubManager.SCHEMA_ALTERING_QUERIES.some(q => cleanedQuery.includes(q.toLowerCase()))){
|
|
2932
|
+
this.prostgles.onSchemaChange({ command, query })
|
|
2933
|
+
}
|
|
2934
|
+
}
|
|
3284
2935
|
}
|
|
3285
2936
|
|
|
3286
2937
|
if(command === "LISTEN"){
|
|
@@ -3870,50 +3521,84 @@ function sqlErrCodeToMsg(code: string){
|
|
|
3870
3521
|
JSON.stringify([...THE_table_$0.rows].map(t => [...t.children].map(u => u.innerText)).filter((d, i) => i && d.length > 1).reduce((a, v)=>({ ...a, [v[0]]: v[1] }), {}))
|
|
3871
3522
|
*/
|
|
3872
3523
|
}
|
|
3873
|
-
|
|
3524
|
+
|
|
3525
|
+
|
|
3526
|
+
async function getInferredJoins2(schema: TableSchema[]): Promise<Join[]>{
|
|
3874
3527
|
let joins: Join[] = [];
|
|
3875
|
-
|
|
3876
|
-
|
|
3877
|
-
|
|
3878
|
-
|
|
3879
|
-
|
|
3880
|
-
ccu.table_schema AS foreign_table_schema,
|
|
3881
|
-
ccu.table_name AS foreign_table_name,
|
|
3882
|
-
ccu.column_name AS foreign_column_name,
|
|
3883
|
-
tc.constraint_type IN ('UNIQUE', 'PRIMARY KEY') as foreign_is_unique
|
|
3884
|
-
FROM
|
|
3885
|
-
information_schema.table_constraints AS tc
|
|
3886
|
-
JOIN information_schema.key_column_usage AS kcu
|
|
3887
|
-
ON tc.constraint_name = kcu.constraint_name
|
|
3888
|
-
AND tc.table_schema = kcu.table_schema
|
|
3889
|
-
JOIN information_schema.constraint_column_usage AS ccu
|
|
3890
|
-
ON ccu.constraint_name = tc.constraint_name
|
|
3891
|
-
AND ccu.table_schema = tc.table_schema
|
|
3892
|
-
WHERE tc.table_schema=` + "${schema}" + `
|
|
3893
|
-
AND tc.constraint_type = 'FOREIGN KEY'
|
|
3894
|
-
AND tc.table_name <> ccu.table_name -- Exclude self-referencing tables
|
|
3895
|
-
`, { schema });
|
|
3896
|
-
|
|
3897
|
-
res.map((d: any) => {
|
|
3898
|
-
let eIdx = joins.findIndex(j => j.tables.includes(d.table_name) && j.tables.includes(d.foreign_table_name));
|
|
3899
|
-
let existing = joins[eIdx];
|
|
3528
|
+
const upsertJoin = (t1: string, t2: string, cols: { col1: string; col2: string }[]) => {
|
|
3529
|
+
let existingIdx = joins.findIndex(j => j.tables.slice(0).sort().join() === [t1, t2].sort().join());
|
|
3530
|
+
let existing = joins[existingIdx];
|
|
3531
|
+
const normalCond = cols.reduce((a, v) => ({ ...a, [v.col1]: v.col2 }), {});
|
|
3532
|
+
const revertedCond = cols.reduce((a, v) => ({ ...a, [v.col2]: v.col1 }), {});
|
|
3900
3533
|
if(existing){
|
|
3901
|
-
|
|
3902
|
-
|
|
3903
|
-
|
|
3904
|
-
existing.on
|
|
3534
|
+
const cond = existing.tables[0] === t1? normalCond : revertedCond;
|
|
3535
|
+
/** Avoid duplicates */
|
|
3536
|
+
if(!existing.on.some(_cond => JSON.stringify(_cond) === JSON.stringify(cond))){
|
|
3537
|
+
existing.on.push(cond);
|
|
3538
|
+
joins[existingIdx] = existing;
|
|
3905
3539
|
}
|
|
3906
|
-
joins[eIdx] = existing;
|
|
3907
3540
|
} else {
|
|
3908
3541
|
joins.push({
|
|
3909
|
-
tables: [
|
|
3910
|
-
on:
|
|
3911
|
-
[d.column_name]: d.foreign_column_name
|
|
3912
|
-
},
|
|
3542
|
+
tables: [t1, t2],
|
|
3543
|
+
on: [normalCond],
|
|
3913
3544
|
type: "many-many"
|
|
3914
3545
|
})
|
|
3915
3546
|
}
|
|
3916
|
-
}
|
|
3917
|
-
|
|
3547
|
+
}
|
|
3548
|
+
schema.map(tov => {
|
|
3549
|
+
tov.columns.map(col => {
|
|
3550
|
+
if(col.references){
|
|
3551
|
+
const r = col.references;
|
|
3552
|
+
upsertJoin(tov.name, r.ftable, r.cols.map((c, i) => ({ col1: c, col2: r.fcols[i] })))
|
|
3553
|
+
}
|
|
3554
|
+
})
|
|
3555
|
+
})
|
|
3918
3556
|
return joins;
|
|
3919
|
-
}
|
|
3557
|
+
}
|
|
3558
|
+
// async function getInferredJoins(db: DB, schema: string = "public"): Promise<Join[]>{
|
|
3559
|
+
// let joins: Join[] = [];
|
|
3560
|
+
// let res = await db.any(`SELECT
|
|
3561
|
+
// tc.table_schema,
|
|
3562
|
+
// tc.constraint_name,
|
|
3563
|
+
// tc.table_name,
|
|
3564
|
+
// kcu.column_name,
|
|
3565
|
+
// ccu.table_schema AS foreign_table_schema,
|
|
3566
|
+
// ccu.table_name AS foreign_table_name,
|
|
3567
|
+
// ccu.column_name AS foreign_column_name,
|
|
3568
|
+
// tc.constraint_type IN ('UNIQUE', 'PRIMARY KEY') as foreign_is_unique
|
|
3569
|
+
// FROM
|
|
3570
|
+
// information_schema.table_constraints AS tc
|
|
3571
|
+
// JOIN information_schema.key_column_usage AS kcu
|
|
3572
|
+
// ON tc.constraint_name = kcu.constraint_name
|
|
3573
|
+
// AND tc.table_schema = kcu.table_schema
|
|
3574
|
+
// JOIN information_schema.constraint_column_usage AS ccu
|
|
3575
|
+
// ON ccu.constraint_name = tc.constraint_name
|
|
3576
|
+
// AND ccu.table_schema = tc.table_schema
|
|
3577
|
+
// WHERE tc.table_schema=` + "${schema}" + `
|
|
3578
|
+
// AND tc.constraint_type = 'FOREIGN KEY'
|
|
3579
|
+
// AND tc.table_name <> ccu.table_name -- Exclude self-referencing tables
|
|
3580
|
+
// `, { schema });
|
|
3581
|
+
|
|
3582
|
+
// res.map((d: any) => {
|
|
3583
|
+
// let eIdx = joins.findIndex(j => j.tables.includes(d.table_name) && j.tables.includes(d.foreign_table_name));
|
|
3584
|
+
// let existing = joins[eIdx];
|
|
3585
|
+
// if(existing){
|
|
3586
|
+
// if(existing.tables[0] === d.table_name){
|
|
3587
|
+
// existing.on = { ...existing.on, [d.column_name]: d.foreign_column_name }
|
|
3588
|
+
// } else {
|
|
3589
|
+
// existing.on = { ...existing.on, [d.foreign_column_name]: d.column_name }
|
|
3590
|
+
// }
|
|
3591
|
+
// joins[eIdx] = existing;
|
|
3592
|
+
// } else {
|
|
3593
|
+
// joins.push({
|
|
3594
|
+
// tables: [d.table_name, d.foreign_table_name],
|
|
3595
|
+
// on: {
|
|
3596
|
+
// [d.column_name]: d.foreign_column_name
|
|
3597
|
+
// },
|
|
3598
|
+
// type: "many-many"
|
|
3599
|
+
// })
|
|
3600
|
+
// }
|
|
3601
|
+
// });
|
|
3602
|
+
|
|
3603
|
+
// return joins;
|
|
3604
|
+
// }
|