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.
- package/dist/DBSchemaBuilder.d.ts.map +1 -1
- package/dist/DBSchemaBuilder.js +4 -0
- package/dist/DBSchemaBuilder.js.map +1 -1
- package/dist/DboBuilder/QueryBuilder/QueryBuilder.d.ts +149 -0
- package/dist/DboBuilder/QueryBuilder/QueryBuilder.d.ts.map +1 -0
- package/{lib → dist/DboBuilder/QueryBuilder}/QueryBuilder.js +4 -223
- package/dist/DboBuilder/QueryBuilder/QueryBuilder.js.map +1 -0
- package/dist/DboBuilder/QueryBuilder/makeSelectQuery.d.ts +5 -0
- package/dist/DboBuilder/QueryBuilder/makeSelectQuery.d.ts.map +1 -0
- package/dist/DboBuilder/QueryBuilder/makeSelectQuery.js +225 -0
- package/dist/DboBuilder/QueryBuilder/makeSelectQuery.js.map +1 -0
- package/dist/DboBuilder/runSQL.d.ts +2 -1
- package/dist/DboBuilder/runSQL.d.ts.map +1 -1
- package/dist/DboBuilder/runSQL.js +5 -1
- package/dist/DboBuilder/runSQL.js.map +1 -1
- package/dist/DboBuilder.d.ts +1 -1
- package/dist/DboBuilder.d.ts.map +1 -1
- package/dist/DboBuilder.js +3 -3
- package/dist/DboBuilder.js.map +1 -1
- package/dist/FileManager.d.ts.map +1 -1
- package/dist/FileManager.js +16 -12
- package/dist/FileManager.js.map +1 -1
- package/dist/Filtering.d.ts +1 -1
- package/dist/Filtering.d.ts.map +1 -1
- package/dist/TableConfig.d.ts +4 -1
- package/dist/TableConfig.d.ts.map +1 -1
- package/dist/TableConfig.js +8 -6
- package/dist/TableConfig.js.map +1 -1
- package/dist/validation.js +1 -2
- package/dist/validation.js.map +1 -1
- package/lib/DBSchemaBuilder.d.ts.map +1 -1
- package/lib/DBSchemaBuilder.js +4 -0
- package/lib/DBSchemaBuilder.ts +2 -0
- package/lib/{QueryBuilder.d.ts → DboBuilder/QueryBuilder/QueryBuilder.d.ts} +2 -3
- package/lib/DboBuilder/QueryBuilder/QueryBuilder.d.ts.map +1 -0
- package/lib/DboBuilder/QueryBuilder/QueryBuilder.js +1160 -0
- package/lib/{QueryBuilder.ts → DboBuilder/QueryBuilder/QueryBuilder.ts} +3 -282
- package/lib/DboBuilder/QueryBuilder/makeSelectQuery.d.ts +5 -0
- package/lib/DboBuilder/QueryBuilder/makeSelectQuery.d.ts.map +1 -0
- package/lib/DboBuilder/QueryBuilder/makeSelectQuery.js +224 -0
- package/lib/DboBuilder/QueryBuilder/makeSelectQuery.ts +284 -0
- package/lib/DboBuilder/runSQL.d.ts +2 -1
- package/lib/DboBuilder/runSQL.d.ts.map +1 -1
- package/lib/DboBuilder/runSQL.js +5 -1
- package/lib/DboBuilder/runSQL.ts +6 -2
- package/lib/DboBuilder.d.ts +1 -1
- package/lib/DboBuilder.d.ts.map +1 -1
- package/lib/DboBuilder.js +3 -3
- package/lib/DboBuilder.ts +3 -3
- package/lib/FileManager.d.ts.map +1 -1
- package/lib/FileManager.js +16 -12
- package/lib/FileManager.ts +16 -12
- package/lib/Filtering.d.ts +1 -1
- package/lib/Filtering.d.ts.map +1 -1
- package/lib/Filtering.ts +1 -1
- package/lib/TableConfig.d.ts +4 -1
- package/lib/TableConfig.d.ts.map +1 -1
- package/lib/TableConfig.js +8 -6
- package/lib/TableConfig.ts +11 -6
- package/lib/validation.js +1 -2
- package/lib/validation.ts +2 -2
- package/package.json +1 -1
- package/tests/client/PID.txt +1 -1
- package/tests/server/package-lock.json +1 -1
- 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 "
|
|
8
|
-
import { TableRule } from "
|
|
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 "
|
|
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;
|