prostgles-server 2.0.268 → 2.0.271

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.
Files changed (65) hide show
  1. package/dist/DBSchemaBuilder.d.ts.map +1 -1
  2. package/dist/DBSchemaBuilder.js +4 -0
  3. package/dist/DBSchemaBuilder.js.map +1 -1
  4. package/dist/DboBuilder/QueryBuilder/QueryBuilder.d.ts +149 -0
  5. package/dist/DboBuilder/QueryBuilder/QueryBuilder.d.ts.map +1 -0
  6. package/{lib → dist/DboBuilder/QueryBuilder}/QueryBuilder.js +4 -223
  7. package/dist/DboBuilder/QueryBuilder/QueryBuilder.js.map +1 -0
  8. package/dist/DboBuilder/QueryBuilder/makeSelectQuery.d.ts +5 -0
  9. package/dist/DboBuilder/QueryBuilder/makeSelectQuery.d.ts.map +1 -0
  10. package/dist/DboBuilder/QueryBuilder/makeSelectQuery.js +225 -0
  11. package/dist/DboBuilder/QueryBuilder/makeSelectQuery.js.map +1 -0
  12. package/dist/DboBuilder/runSQL.d.ts +2 -1
  13. package/dist/DboBuilder/runSQL.d.ts.map +1 -1
  14. package/dist/DboBuilder/runSQL.js +5 -1
  15. package/dist/DboBuilder/runSQL.js.map +1 -1
  16. package/dist/DboBuilder.d.ts +1 -1
  17. package/dist/DboBuilder.d.ts.map +1 -1
  18. package/dist/DboBuilder.js +3 -3
  19. package/dist/DboBuilder.js.map +1 -1
  20. package/dist/FileManager.d.ts.map +1 -1
  21. package/dist/FileManager.js +16 -12
  22. package/dist/FileManager.js.map +1 -1
  23. package/dist/Filtering.d.ts +1 -1
  24. package/dist/Filtering.d.ts.map +1 -1
  25. package/dist/TableConfig.d.ts +4 -1
  26. package/dist/TableConfig.d.ts.map +1 -1
  27. package/dist/TableConfig.js +8 -6
  28. package/dist/TableConfig.js.map +1 -1
  29. package/dist/validation.js +1 -2
  30. package/dist/validation.js.map +1 -1
  31. package/lib/DBSchemaBuilder.d.ts.map +1 -1
  32. package/lib/DBSchemaBuilder.js +4 -0
  33. package/lib/DBSchemaBuilder.ts +2 -0
  34. package/lib/{QueryBuilder.d.ts → DboBuilder/QueryBuilder/QueryBuilder.d.ts} +2 -3
  35. package/lib/DboBuilder/QueryBuilder/QueryBuilder.d.ts.map +1 -0
  36. package/lib/DboBuilder/QueryBuilder/QueryBuilder.js +1160 -0
  37. package/lib/{QueryBuilder.ts → DboBuilder/QueryBuilder/QueryBuilder.ts} +3 -282
  38. package/lib/DboBuilder/QueryBuilder/makeSelectQuery.d.ts +5 -0
  39. package/lib/DboBuilder/QueryBuilder/makeSelectQuery.d.ts.map +1 -0
  40. package/lib/DboBuilder/QueryBuilder/makeSelectQuery.js +224 -0
  41. package/lib/DboBuilder/QueryBuilder/makeSelectQuery.ts +284 -0
  42. package/lib/DboBuilder/runSQL.d.ts +2 -1
  43. package/lib/DboBuilder/runSQL.d.ts.map +1 -1
  44. package/lib/DboBuilder/runSQL.js +5 -1
  45. package/lib/DboBuilder/runSQL.ts +6 -2
  46. package/lib/DboBuilder.d.ts +1 -1
  47. package/lib/DboBuilder.d.ts.map +1 -1
  48. package/lib/DboBuilder.js +3 -3
  49. package/lib/DboBuilder.ts +3 -3
  50. package/lib/FileManager.d.ts.map +1 -1
  51. package/lib/FileManager.js +16 -12
  52. package/lib/FileManager.ts +16 -12
  53. package/lib/Filtering.d.ts +1 -1
  54. package/lib/Filtering.d.ts.map +1 -1
  55. package/lib/Filtering.ts +1 -1
  56. package/lib/TableConfig.d.ts +4 -1
  57. package/lib/TableConfig.d.ts.map +1 -1
  58. package/lib/TableConfig.js +8 -6
  59. package/lib/TableConfig.ts +11 -6
  60. package/lib/validation.js +1 -2
  61. package/lib/validation.ts +2 -2
  62. package/package.json +1 -1
  63. package/tests/client/PID.txt +1 -1
  64. package/tests/server/package-lock.json +1 -1
  65. package/lib/QueryBuilder.d.ts.map +0 -1
@@ -4,10 +4,10 @@
4
4
  * Licensed under the MIT License. See LICENSE in the project root for license information.
5
5
  *--------------------------------------------------------------------------------------------*/
6
6
 
7
- import { pgp, Filter, LocalParams, isPlainObject, TableHandler, ViewHandler, postgresToTsType } from "./DboBuilder";
8
- import { TableRule } from "./PublishParser";
7
+ import { pgp, Filter, LocalParams, isPlainObject, TableHandler, ViewHandler, postgresToTsType } from "../../DboBuilder";
8
+ import { TableRule } from "../../PublishParser";
9
9
  import { SelectParams, isEmpty, FieldFilter, asName, TextFilter_FullTextSearchFilterKeys, ColumnInfo, PG_COLUMN_UDT_DATA_TYPE, isObject, Select } from "prostgles-types";
10
- import { get } from "./utils";
10
+ import { get } from "../../utils";
11
11
 
12
12
 
13
13
  export type SelectItem = {
@@ -1382,282 +1382,3 @@ export async function getNewQuery(
1382
1382
  }
1383
1383
 
1384
1384
 
1385
-
1386
- /* No validation/authorisation at this point */
1387
- export function makeQuery(
1388
- _this: TableHandler,
1389
- q: NewQuery,
1390
- depth: number = 0,
1391
- joinFields: string[] = [],
1392
- selectParams: SelectParams = {},
1393
- ): string {
1394
- const PREF = `prostgles`,
1395
- joins = q.joins || [],
1396
- // aggs = q.aggs || [],
1397
- makePref = (q: NewQuery) => !q.tableAlias? q.table : `${q.tableAlias || ""}_${q.table}`,
1398
- makePrefANON = (joinAlias: string | undefined, table: string) => asName(!joinAlias? table : `${joinAlias || ""}_${table}`),
1399
- makePrefAN = (q: NewQuery) => asName(makePref(q));
1400
-
1401
- const indentLine = (numInd: number, str: string, indentStr = " ") => new Array(numInd).fill(indentStr).join("") + str;
1402
- const indStr = (numInd: number, str: string) => str.split("\n").map(s => indentLine(numInd, s)).join("\n");
1403
- const indjArr = (numInd: number, strArr: string[], indentStr = " "): string[] => strArr.map(str => indentLine(numInd, str) );
1404
- const indJ = (numInd: number, strArr: string[], separator = " \n ", indentStr = " ") => indjArr(numInd, strArr, indentStr).join(separator);
1405
- const selectArrComma = (strArr: string[]): string[] => strArr.map((s, i, arr)=> s + (i < arr.length - 1? " , " : " "));
1406
- const prefJCAN = (q: NewQuery, str: string) => asName(`${q.tableAlias || q.table}_${PREF}_${str}`);
1407
-
1408
- // const indent = (a, b) => a;
1409
- const joinTables = (q1: NewQuery, q2: NewQuery): string[] => {
1410
- const joinInfo = _this.getJoins(q1.table, q2.table, q2.$path, true);
1411
- const paths = joinInfo.paths;
1412
-
1413
- return paths.flatMap(({ table, on }, i) => {
1414
- const getColName = (col: string, q: NewQuery) => {
1415
- if(table === q.table){
1416
- const colFromSelect = q.select.find(s => s.getQuery() === asName(col));
1417
- if(!colFromSelect){
1418
- console.error(`${col} column might be missing in user publish `);
1419
- throw `Could not find join column (${col}) in allowe select. Some join tables and columns might be invalid/dissallowed`
1420
- }
1421
- return colFromSelect.alias;
1422
- }
1423
-
1424
- return col;
1425
- }
1426
- const getPrevColName = (col: string) => {
1427
- return getColName(col, q1);
1428
- }
1429
- const getThisColName = (col: string) => {
1430
- return getColName(col, q2);
1431
- }
1432
-
1433
- // console.log(JSON.stringify({i, table, on, q1, q2}, null, 2));
1434
-
1435
- const prevTable = i === 0? q1.table : (paths[i - 1].table);
1436
- const thisAlias = makePrefANON(q2.tableAlias, table);
1437
- // const prevAlias = i === 0? makePrefAN(q1) : thisAlias;
1438
- const prevAlias = i === 0? makePrefAN(q1) : makePrefANON(q2.tableAlias, prevTable)
1439
- // If root then prev table is aliased from root query. Alias from join otherwise
1440
-
1441
- let iQ = [
1442
- asName(table) + ` ${thisAlias}`
1443
- ];
1444
-
1445
- /* If target table then add filters, options, etc */
1446
- if(i === paths.length - 1){
1447
-
1448
- // const targetSelect = (
1449
- // q2.select.concat(
1450
- // (q2.joins || []).map(j => j.tableAlias || j.table)
1451
- // ).concat(
1452
- // /* Rename aggs to avoid collision with join cols */
1453
- // (q2.aggs || []).map(a => asName(`agg_${a.alias}`) + " AS " + asName(a.alias)) || [])
1454
- // ).filter(s => s).join(", ");
1455
-
1456
- const targetSelect = q2.select.filter(s => s.selected).map(s => {
1457
- /* Rename aggs to avoid collision with join cols */
1458
- if(s.type === "aggregation") return asName(`agg_${s.alias}`) + " AS " + asName(s.alias);
1459
- return asName(s.alias);
1460
- }).concat(q2.joins?.map(j => asName(j.table)) ?? []).join(", ");
1461
-
1462
- const _iiQ = makeQuery(
1463
- _this,
1464
- q2,
1465
- depth + 1,
1466
- // on.map(([c1, c2]) => asName(c2)),
1467
- on.flatMap(cond => cond.map(([c1, c2]) => asName(c2))),
1468
- selectParams,
1469
- );
1470
- // const iiQ = flat(_iiQ.split("\n")); // prettify for debugging
1471
- // console.log(_iiQ)
1472
- const iiQ = [_iiQ];
1473
-
1474
- iQ = [
1475
- "("
1476
- , ...indjArr(depth + 1, [
1477
- `-- 4. [target table] `
1478
- , `SELECT *,`
1479
- , `row_number() over() as ${prefJCAN(q2, `rowid_sorted`)},`
1480
- , `row_to_json((select x from (SELECT ${targetSelect}) as x)) AS ${prefJCAN(q2, `json`)}`
1481
- , `FROM (`
1482
- , ...iiQ
1483
- , `) ${asName(q2.table)} `
1484
- ])
1485
- , `) ${thisAlias}`
1486
- ]
1487
- }
1488
-
1489
- const getJoinCondition = (t1Alias: string, t2Alias: string, on: [string, string][][]) => {
1490
- return on.map(cond => cond.map(([c1, c2]) =>
1491
- `${t1Alias}.${asName(getPrevColName(c1))} = ${t2Alias}.${asName(getThisColName(c2))} `
1492
- ).join(" AND ")
1493
- ).join(" OR ")
1494
- }
1495
-
1496
- let jres: string[] = [
1497
- `${q2.isLeftJoin? "LEFT" : "INNER"} JOIN `
1498
- , ...iQ
1499
- , `ON ${getJoinCondition(prevAlias, thisAlias, on)}`
1500
- ];
1501
- return jres;
1502
- });
1503
- }
1504
-
1505
- const getGroupBy = (rootSelectItems: SelectItem[], groupByItems: SelectItem[]): string => {
1506
- if(groupByItems.length){
1507
- /** Root Select column index number is used where possible to prevent "non-integer constant in GROUP BY" error */
1508
-
1509
- return `GROUP BY ` + groupByItems.map(gi => {
1510
- const idx = rootSelectItems.findIndex(si => si.alias === gi.alias);
1511
- if(idx < 0) throw `Could not find GROUP BY column ${gi.alias} in ROOT SELECT ${rootSelectItems.map(s => s.alias)}`;
1512
- return idx + 1;
1513
- }).join(", ")
1514
- }
1515
-
1516
- return ""
1517
- }
1518
-
1519
- /* Leaf query -> no joins -> return simple query */
1520
- const aggs = q.select.filter(s => s.type === "aggregation");
1521
- const nonAggs = q.select.filter(s => depth || s.selected).filter(s => s.type !== "aggregation");
1522
- if(!joins.length){
1523
- /* Nested queries contain all fields to allow joining */
1524
- let groupBy = "";
1525
-
1526
- const rootSelectItems = q.select.filter(s => joinFields.includes(s.getQuery()) || s.selected)
1527
-
1528
- /* If aggs exist need to set groupBy add joinFields into select */
1529
- if(aggs.length || selectParams?.groupBy){
1530
- // const missingFields = joinFields.filter(jf => !q.select.find(s => s.type === "column" && s.alias === jf));
1531
- // if(depth && missingFields.length){
1532
- // // select = Array.from(new Set(missingFields.concat(select)));
1533
- // }
1534
-
1535
- if(nonAggs.length){
1536
- let groupByFields = nonAggs.filter(sf => !depth || joinFields.includes(sf.getQuery()));
1537
- groupBy = getGroupBy(rootSelectItems, groupByFields);
1538
- // if(groupByFields.length){
1539
- // groupBy = `GROUP BY ${groupByFields.map(sf => sf.type === "function"? sf.getQuery() : asName(sf.alias)).join(", ")}\n`;
1540
- // }
1541
- }
1542
- }
1543
-
1544
- // console.log(q.select, joinFields)
1545
- let simpleQuery = indJ(depth, [
1546
- `-- 0. or 5. [leaf query] `
1547
-
1548
- /* Group by selected fields + any join fields */
1549
- , `SELECT ` + rootSelectItems.map(s => {
1550
- // return s.getQuery() + ((s.type !== "column")? (" AS " + s.alias) : "")
1551
-
1552
- if(s.type === "aggregation"){
1553
- /* Rename aggs to avoid collision with join cols */
1554
- return s.getQuery() + " AS " + asName((depth? "agg_" : "") + s.alias);
1555
- }
1556
- return s.getQuery() + " AS " + asName(s.alias)
1557
- }).join(", ")
1558
- , `FROM ${asName(q.table)} `
1559
- , q.where
1560
- , groupBy //!aggs.length? "" : `GROUP BY ${nonAggs.map(sf => asName(sf.alias)).join(", ")}`,
1561
- , q.having? `HAVING ${q.having}` : ""
1562
- , q.orderBy.join(", ")
1563
- , !depth? `LIMIT ${q.limit} ` : null
1564
- , !depth? `OFFSET ${q.offset || 0} ` : null
1565
- ].filter(v => v && (v + "").trim().length) as unknown as string[]);
1566
- // console.log(fres);
1567
- return simpleQuery;
1568
- } else {
1569
- // if(q.aggs && q.aggs && q.aggs.length) throw "Cannot join an aggregate";
1570
- if(
1571
- q.select.find(s => s.type === "aggregation") &&
1572
- joins.find(j => j.select.find(s => s.type === "aggregation"))
1573
- ) throw "Cannot join two aggregates";
1574
- }
1575
-
1576
- if(joins && joins.length && (aggs.length || selectParams.groupBy)) throw "Joins within Aggs dissallowed";
1577
-
1578
- // if(q.selectFuncs.length) throw "Functions within select not allowed in joins yet. -> " + q.selectFuncs.map(s => s.alias).join(", ");
1579
-
1580
- const rootSelectItems = q.select.filter(s => depth || s.selected)
1581
-
1582
- let rootGroupBy: string | undefined;
1583
- if((selectParams.groupBy || aggs.length || q.joins && q.joins.length) && nonAggs.length){
1584
- // console.log({ aggs, nonAggs, joins: q.joins })
1585
- // rootGroupBy = getGroupBy(rootSelectItems, depth? rootSelectItems : nonAggs) + (aggs?.length? "" : ", ctid")
1586
- rootGroupBy = `GROUP BY ${
1587
- (depth?
1588
- q.allFields.map(f => asName(f)) :
1589
- nonAggs.map(s => s.type === "function"? s.getQuery() : asName(s.alias))
1590
- ).concat(
1591
- (aggs && aggs.length)?
1592
- [] :
1593
- [`ctid`]
1594
- ).filter(s => s).join(", ")} `
1595
- }
1596
-
1597
- /* Joined query */
1598
- const joinedQuery = [
1599
- " \n"
1600
- , `-- 0. [joined root] `
1601
- , "SELECT "
1602
- ,...selectArrComma(rootSelectItems.map(s => s.getQuery() + " AS " + asName(s.alias)).concat(
1603
- joins.map((j, i)=> {
1604
-
1605
- /** Apply LIMIT to joined items */
1606
- const jsq = `json_agg(${prefJCAN(j, `json`)}::jsonb ORDER BY ${prefJCAN(j, `rowid_sorted`)}) FILTER (WHERE ${prefJCAN(j, `limit`)} <= ${j.limit} AND ${prefJCAN(j, `dupes_rowid`)} = 1 AND ${prefJCAN(j, `json`)} IS NOT NULL)`;
1607
- const resAlias = asName(j.tableAlias || j.table)
1608
-
1609
- // If limit = 1 then return a single json object (first one)
1610
- return (j.limit === 1? `${jsq}->0 ` : `COALESCE(${jsq}, '[]') `) + ` AS ${resAlias}`;
1611
- })
1612
- ))
1613
- , `FROM ( `
1614
- , ...indjArr(depth + 1, [
1615
- "-- 1. [subquery limit + dupes] "
1616
- , "SELECT "
1617
- , ...selectArrComma([`t1.*`].concat(
1618
- joins.map((j, i)=> {
1619
- return `row_number() over(partition by ${prefJCAN(j, `dupes_rowid`)}, ` +
1620
- `ctid order by ${prefJCAN(j, `rowid_sorted`)}) AS ${prefJCAN(j, `limit`)} `
1621
- }))
1622
- )
1623
- , `FROM ( ----------- ${makePrefAN(q)}`
1624
- , ...indjArr(depth + 1, [
1625
- "-- 2. [source full select + ctid to group by] "
1626
- , "SELECT "
1627
- , ...selectArrComma(
1628
- q.allFields.concat(["ctid"])
1629
- .map(field => `${makePrefAN(q)}.${asName(field)} `)
1630
- .concat(
1631
- joins.map((j, i)=>
1632
- makePrefAN(j) + "." + prefJCAN(j, `json`) + ", " + makePrefAN(j) + "." + prefJCAN(j, `rowid_sorted`)
1633
- ).concat(
1634
- joins.map(j => `row_number() over(partition by ${makePrefAN(j)}.${prefJCAN(j, `rowid_sorted`)}, ${makePrefAN(q)}.ctid ) AS ${prefJCAN(j, `dupes_rowid`)}`)
1635
- )
1636
- ))
1637
- , `FROM ( `
1638
- , ...indjArr(depth + 1, [
1639
- "-- 3. [source table] "
1640
- , "SELECT "
1641
- , "*, row_number() over() as ctid "
1642
- , `FROM ${asName(q.table)} `
1643
- , `${q.where} `
1644
- ])
1645
- , `) ${makePrefAN(q)} `
1646
- , ...joins.flatMap((j, i)=> joinTables(q, j))
1647
- ])
1648
- , ") t1"
1649
- ])
1650
- , ") t0"
1651
- , rootGroupBy
1652
- , q.having? `HAVING ${q.having} ` : ""
1653
- , q.orderBy
1654
- , depth? null : `LIMIT ${q.limit || 0} OFFSET ${q.offset || 0}`
1655
- , "-- EOF 0. joined root"
1656
- , " \n"
1657
- ].filter(v => v)
1658
-
1659
- let res = indJ(depth, joinedQuery as unknown as string[]);
1660
- // res = indent(res, depth);
1661
- // console.log(res);
1662
- return res;
1663
- }
@@ -0,0 +1,5 @@
1
+ import { TableHandler } from "../../DboBuilder";
2
+ import { SelectParams } from "prostgles-types";
3
+ import { NewQuery } from "./QueryBuilder";
4
+ export declare function makeSelectQuery(_this: TableHandler, q: NewQuery, depth?: number, joinFields?: string[], selectParams?: SelectParams): string;
5
+ //# sourceMappingURL=makeSelectQuery.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"makeSelectQuery.d.ts","sourceRoot":"","sources":["makeSelectQuery.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,YAAY,EAAE,MAAM,kBAAkB,CAAC;AAChD,OAAO,EAAE,YAAY,EAAU,MAAM,iBAAiB,CAAC;AACvD,OAAO,EAAE,QAAQ,EAAc,MAAM,gBAAgB,CAAC;AAGtD,wBAAgB,eAAe,CAC7B,KAAK,EAAE,YAAY,EACnB,CAAC,EAAE,QAAQ,EACX,KAAK,GAAE,MAAU,EACjB,UAAU,GAAE,MAAM,EAAO,EACzB,YAAY,GAAE,YAAiB,GAC9B,MAAM,CA+QR"}
@@ -0,0 +1,224 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.makeSelectQuery = void 0;
4
+ const prostgles_types_1 = require("prostgles-types");
5
+ /* No validation/authorisation at this point */
6
+ function makeSelectQuery(_this, q, depth = 0, joinFields = [], selectParams = {}) {
7
+ const PREF = `prostgles`, joins = q.joins || [],
8
+ // aggs = q.aggs || [],
9
+ makePref = (q) => !q.tableAlias ? q.table : `${q.tableAlias || ""}_${q.table}`, makePrefANON = (joinAlias, table) => (0, prostgles_types_1.asName)(!joinAlias ? table : `${joinAlias || ""}_${table}`), makePrefAN = (q) => (0, prostgles_types_1.asName)(makePref(q));
10
+ const indentLine = (numInd, str, indentStr = " ") => new Array(numInd).fill(indentStr).join("") + str;
11
+ const indStr = (numInd, str) => str.split("\n").map(s => indentLine(numInd, s)).join("\n");
12
+ const indjArr = (numInd, strArr, indentStr = " ") => strArr.map(str => indentLine(numInd, str));
13
+ const indJ = (numInd, strArr, separator = " \n ", indentStr = " ") => indjArr(numInd, strArr, indentStr).join(separator);
14
+ const selectArrComma = (strArr) => strArr.map((s, i, arr) => s + (i < arr.length - 1 ? " , " : " "));
15
+ const prefJCAN = (q, str) => (0, prostgles_types_1.asName)(`${q.tableAlias || q.table}_${PREF}_${str}`);
16
+ // const indent = (a, b) => a;
17
+ const joinTables = (q1, q2) => {
18
+ const joinInfo = _this.getJoins(q1.table, q2.table, q2.$path, true);
19
+ const paths = joinInfo.paths;
20
+ return paths.flatMap(({ table, on }, i) => {
21
+ const getColName = (col, q) => {
22
+ if (table === q.table) {
23
+ const colFromSelect = q.select.find(s => s.getQuery() === (0, prostgles_types_1.asName)(col));
24
+ if (!colFromSelect) {
25
+ console.error(`${col} column might be missing in user publish `);
26
+ throw `Could not find join column (${col}) in allowe select. Some join tables and columns might be invalid/dissallowed`;
27
+ }
28
+ return colFromSelect.alias;
29
+ }
30
+ return col;
31
+ };
32
+ const getPrevColName = (col) => {
33
+ return getColName(col, q1);
34
+ };
35
+ const getThisColName = (col) => {
36
+ return getColName(col, q2);
37
+ };
38
+ // console.log(JSON.stringify({i, table, on, q1, q2}, null, 2));
39
+ const prevTable = i === 0 ? q1.table : (paths[i - 1].table);
40
+ const thisAlias = makePrefANON(q2.tableAlias, table);
41
+ // const prevAlias = i === 0? makePrefAN(q1) : thisAlias;
42
+ const prevAlias = i === 0 ? makePrefAN(q1) : makePrefANON(q2.tableAlias, prevTable);
43
+ // If root then prev table is aliased from root query. Alias from join otherwise
44
+ let iQ = [
45
+ (0, prostgles_types_1.asName)(table) + ` ${thisAlias}`
46
+ ];
47
+ /* If target table then add filters, options, etc */
48
+ if (i === paths.length - 1) {
49
+ // const targetSelect = (
50
+ // q2.select.concat(
51
+ // (q2.joins || []).map(j => j.tableAlias || j.table)
52
+ // ).concat(
53
+ // /* Rename aggs to avoid collision with join cols */
54
+ // (q2.aggs || []).map(a => asName(`agg_${a.alias}`) + " AS " + asName(a.alias)) || [])
55
+ // ).filter(s => s).join(", ");
56
+ const targetSelect = q2.select.filter(s => s.selected).map(s => {
57
+ /* Rename aggs to avoid collision with join cols */
58
+ if (s.type === "aggregation")
59
+ return (0, prostgles_types_1.asName)(`agg_${s.alias}`) + " AS " + (0, prostgles_types_1.asName)(s.alias);
60
+ return (0, prostgles_types_1.asName)(s.alias);
61
+ }).concat(q2.joins?.map(j => (0, prostgles_types_1.asName)(j.table)) ?? []).join(", ");
62
+ const _iiQ = makeSelectQuery(_this, q2, depth + 1,
63
+ // on.map(([c1, c2]) => asName(c2)),
64
+ on.flatMap(cond => cond.map(([c1, c2]) => (0, prostgles_types_1.asName)(c2))), selectParams);
65
+ // const iiQ = flat(_iiQ.split("\n")); // prettify for debugging
66
+ // console.log(_iiQ)
67
+ const iiQ = [_iiQ];
68
+ iQ = [
69
+ "(",
70
+ ...indjArr(depth + 1, [
71
+ `-- 4. [target table] `,
72
+ `SELECT *,`,
73
+ `row_number() over() as ${prefJCAN(q2, `rowid_sorted`)},`,
74
+ `row_to_json((select x from (SELECT ${targetSelect}) as x)) AS ${prefJCAN(q2, `json`)}`,
75
+ `FROM (`,
76
+ ...iiQ,
77
+ `) ${(0, prostgles_types_1.asName)(q2.table)} `
78
+ ]),
79
+ `) ${thisAlias}`
80
+ ];
81
+ }
82
+ const getJoinCondition = (t1Alias, t2Alias, on) => {
83
+ return on.map(cond => cond.map(([c1, c2]) => `${t1Alias}.${(0, prostgles_types_1.asName)(getPrevColName(c1))} = ${t2Alias}.${(0, prostgles_types_1.asName)(getThisColName(c2))} `).join(" AND ")).join(" OR ");
84
+ };
85
+ let jres = [
86
+ `${q2.isLeftJoin ? "LEFT" : "INNER"} JOIN `,
87
+ ...iQ,
88
+ `ON ${getJoinCondition(prevAlias, thisAlias, on)}`
89
+ ];
90
+ return jres;
91
+ });
92
+ };
93
+ const getGroupBy = (rootSelectItems, groupByItems) => {
94
+ if (groupByItems.length) {
95
+ /** Root Select column index number is used where possible to prevent "non-integer constant in GROUP BY" error */
96
+ return `GROUP BY ` + groupByItems.map(gi => {
97
+ const idx = rootSelectItems.findIndex(si => si.alias === gi.alias);
98
+ if (idx < 0)
99
+ throw `Could not find GROUP BY column ${gi.alias} in ROOT SELECT ${rootSelectItems.map(s => s.alias)}`;
100
+ return idx + 1;
101
+ }).join(", ");
102
+ }
103
+ return "";
104
+ };
105
+ /* Leaf query -> no joins -> return simple query */
106
+ const aggs = q.select.filter(s => s.type === "aggregation");
107
+ const nonAggs = q.select.filter(s => depth || s.selected).filter(s => s.type !== "aggregation");
108
+ if (!joins.length) {
109
+ /* Nested queries contain all fields to allow joining */
110
+ let groupBy = "";
111
+ const rootSelectItems = q.select.filter(s => joinFields.includes(s.getQuery()) || s.selected);
112
+ /* If aggs exist need to set groupBy add joinFields into select */
113
+ if (aggs.length || selectParams?.groupBy) {
114
+ // const missingFields = joinFields.filter(jf => !q.select.find(s => s.type === "column" && s.alias === jf));
115
+ // if(depth && missingFields.length){
116
+ // // select = Array.from(new Set(missingFields.concat(select)));
117
+ // }
118
+ if (nonAggs.length) {
119
+ let groupByFields = nonAggs.filter(sf => !depth || joinFields.includes(sf.getQuery()));
120
+ groupBy = getGroupBy(rootSelectItems, groupByFields);
121
+ // if(groupByFields.length){
122
+ // groupBy = `GROUP BY ${groupByFields.map(sf => sf.type === "function"? sf.getQuery() : asName(sf.alias)).join(", ")}\n`;
123
+ // }
124
+ }
125
+ }
126
+ // console.log(q.select, joinFields)
127
+ let simpleQuery = indJ(depth, [
128
+ `-- 0. or 5. [leaf query] `
129
+ /* Group by selected fields + any join fields */
130
+ ,
131
+ `SELECT ` + rootSelectItems.map(s => {
132
+ // return s.getQuery() + ((s.type !== "column")? (" AS " + s.alias) : "")
133
+ if (s.type === "aggregation") {
134
+ /* Rename aggs to avoid collision with join cols */
135
+ return s.getQuery() + " AS " + (0, prostgles_types_1.asName)((depth ? "agg_" : "") + s.alias);
136
+ }
137
+ return s.getQuery() + " AS " + (0, prostgles_types_1.asName)(s.alias);
138
+ }).join(", "),
139
+ `FROM ${(0, prostgles_types_1.asName)(q.table)} `,
140
+ q.where,
141
+ groupBy //!aggs.length? "" : `GROUP BY ${nonAggs.map(sf => asName(sf.alias)).join(", ")}`,
142
+ ,
143
+ q.having ? `HAVING ${q.having}` : "",
144
+ q.orderBy.join(", "),
145
+ !depth ? `LIMIT ${q.limit} ` : null,
146
+ !depth ? `OFFSET ${q.offset || 0} ` : null
147
+ ].filter(v => v && (v + "").trim().length));
148
+ // console.log(fres);
149
+ return simpleQuery;
150
+ }
151
+ else {
152
+ // if(q.aggs && q.aggs && q.aggs.length) throw "Cannot join an aggregate";
153
+ if (q.select.find(s => s.type === "aggregation") &&
154
+ joins.find(j => j.select.find(s => s.type === "aggregation")))
155
+ throw "Cannot join two aggregates";
156
+ }
157
+ if (joins && joins.length && (aggs.length || selectParams.groupBy))
158
+ throw "Joins within Aggs dissallowed";
159
+ // if(q.selectFuncs.length) throw "Functions within select not allowed in joins yet. -> " + q.selectFuncs.map(s => s.alias).join(", ");
160
+ const rootSelectItems = q.select.filter(s => depth || s.selected);
161
+ let rootGroupBy;
162
+ if ((selectParams.groupBy || aggs.length || q.joins && q.joins.length) && nonAggs.length) {
163
+ // console.log({ aggs, nonAggs, joins: q.joins })
164
+ // rootGroupBy = getGroupBy(rootSelectItems, depth? rootSelectItems : nonAggs) + (aggs?.length? "" : ", ctid")
165
+ rootGroupBy = `GROUP BY ${(depth ?
166
+ q.allFields.map(f => (0, prostgles_types_1.asName)(f)) :
167
+ nonAggs.map(s => s.type === "function" ? s.getQuery() : (0, prostgles_types_1.asName)(s.alias))).concat((aggs && aggs.length) ?
168
+ [] :
169
+ [`ctid`]).filter(s => s).join(", ")} `;
170
+ }
171
+ /* Joined query */
172
+ const joinedQuery = [
173
+ " \n",
174
+ `-- 0. [joined root] `,
175
+ "SELECT ",
176
+ ...selectArrComma(rootSelectItems.map(s => s.getQuery() + " AS " + (0, prostgles_types_1.asName)(s.alias)).concat(joins.map((j, i) => {
177
+ /** Apply LIMIT to joined items */
178
+ const jsq = `json_agg(${prefJCAN(j, `json`)}::jsonb ORDER BY ${prefJCAN(j, `rowid_sorted`)}) FILTER (WHERE ${prefJCAN(j, `limit`)} <= ${j.limit} AND ${prefJCAN(j, `dupes_rowid`)} = 1 AND ${prefJCAN(j, `json`)} IS NOT NULL)`;
179
+ const resAlias = (0, prostgles_types_1.asName)(j.tableAlias || j.table);
180
+ // If limit = 1 then return a single json object (first one)
181
+ return (j.limit === 1 ? `${jsq}->0 ` : `COALESCE(${jsq}, '[]') `) + ` AS ${resAlias}`;
182
+ }))),
183
+ `FROM ( `,
184
+ ...indjArr(depth + 1, [
185
+ "-- 1. [subquery limit + dupes] ",
186
+ "SELECT ",
187
+ ...selectArrComma([`t1.*`].concat(joins.map((j, i) => {
188
+ return `row_number() over(partition by ${prefJCAN(j, `dupes_rowid`)}, ` +
189
+ `ctid order by ${prefJCAN(j, `rowid_sorted`)}) AS ${prefJCAN(j, `limit`)} `;
190
+ }))),
191
+ `FROM ( ----------- ${makePrefAN(q)}`,
192
+ ...indjArr(depth + 1, [
193
+ "-- 2. [source full select + ctid to group by] ",
194
+ "SELECT ",
195
+ ...selectArrComma(q.allFields.concat(["ctid"])
196
+ .map(field => `${makePrefAN(q)}.${(0, prostgles_types_1.asName)(field)} `)
197
+ .concat(joins.map((j, i) => makePrefAN(j) + "." + prefJCAN(j, `json`) + ", " + makePrefAN(j) + "." + prefJCAN(j, `rowid_sorted`)).concat(joins.map(j => `row_number() over(partition by ${makePrefAN(j)}.${prefJCAN(j, `rowid_sorted`)}, ${makePrefAN(q)}.ctid ) AS ${prefJCAN(j, `dupes_rowid`)}`)))),
198
+ `FROM ( `,
199
+ ...indjArr(depth + 1, [
200
+ "-- 3. [source table] ",
201
+ "SELECT ",
202
+ "*, row_number() over() as ctid ",
203
+ `FROM ${(0, prostgles_types_1.asName)(q.table)} `,
204
+ `${q.where} `
205
+ ]),
206
+ `) ${makePrefAN(q)} `,
207
+ ...joins.flatMap((j, i) => joinTables(q, j))
208
+ ]),
209
+ ") t1"
210
+ ]),
211
+ ") t0",
212
+ rootGroupBy,
213
+ q.having ? `HAVING ${q.having} ` : "",
214
+ q.orderBy,
215
+ depth ? null : `LIMIT ${q.limit || 0} OFFSET ${q.offset || 0}`,
216
+ "-- EOF 0. joined root",
217
+ " \n"
218
+ ].filter(v => v);
219
+ let res = indJ(depth, joinedQuery);
220
+ // res = indent(res, depth);
221
+ // console.log(res);
222
+ return res;
223
+ }
224
+ exports.makeSelectQuery = makeSelectQuery;