@loomcore/api 0.1.111 → 0.1.113
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/databases/postgres/commands/postgres-batch-update.command.js +1 -1
- package/dist/databases/postgres/queries/postgres-get-all.query.js +1 -1
- package/dist/databases/postgres/queries/postgres-get-by-id.query.js +1 -1
- package/dist/databases/postgres/queries/postgres-get.query.js +1 -1
- package/dist/databases/postgres/utils/build-join-clauses.d.ts +1 -1
- package/dist/databases/postgres/utils/build-join-clauses.js +6 -13
- package/dist/databases/postgres/utils/build-select-clause.d.ts +1 -1
- package/dist/databases/postgres/utils/build-select-clause.js +3 -69
- package/dist/databases/postgres/utils/transform-join-results.d.ts +1 -1
- package/dist/databases/postgres/utils/transform-join-results.js +96 -70
- package/package.json +1 -1
|
@@ -44,7 +44,7 @@ export async function batchUpdate(client, entities, operations, queryObject, plu
|
|
|
44
44
|
queryObject.filters._id = { in: entityIds };
|
|
45
45
|
const { whereClause, values } = buildWhereClause(queryObject, [], tablePrefix);
|
|
46
46
|
const selectClause = hasJoins
|
|
47
|
-
? await buildSelectClause(client, pluralResourceName,
|
|
47
|
+
? await buildSelectClause(client, pluralResourceName, operations)
|
|
48
48
|
: '*';
|
|
49
49
|
const selectQuery = `
|
|
50
50
|
SELECT ${selectClause} FROM "${pluralResourceName}" ${joinClauses}
|
|
@@ -8,7 +8,7 @@ export async function getAll(client, operations, pluralResourceName) {
|
|
|
8
8
|
const joinClauses = buildJoinClauses(operations, pluralResourceName);
|
|
9
9
|
const hasJoins = operations.some(op => op instanceof LeftJoin || op instanceof InnerJoin || op instanceof LeftJoinMany);
|
|
10
10
|
const selectClause = hasJoins
|
|
11
|
-
? await buildSelectClause(client, pluralResourceName,
|
|
11
|
+
? await buildSelectClause(client, pluralResourceName, operations)
|
|
12
12
|
: '*';
|
|
13
13
|
const query = `SELECT ${selectClause} FROM "${pluralResourceName}" ${joinClauses}`;
|
|
14
14
|
const result = await client.query(query);
|
|
@@ -12,7 +12,7 @@ export async function getById(client, operations, queryObject, id, pluralResourc
|
|
|
12
12
|
const joinClauses = buildJoinClauses(operations, pluralResourceName);
|
|
13
13
|
const hasJoins = operations.some(op => op instanceof LeftJoin || op instanceof InnerJoin || op instanceof LeftJoinMany);
|
|
14
14
|
const selectClause = hasJoins
|
|
15
|
-
? await buildSelectClause(client, pluralResourceName,
|
|
15
|
+
? await buildSelectClause(client, pluralResourceName, operations)
|
|
16
16
|
: '*';
|
|
17
17
|
queryObject.filters || (queryObject.filters = {});
|
|
18
18
|
queryObject.filters._id = { eq: id };
|
|
@@ -15,7 +15,7 @@ export async function get(client, operations, queryOptions, pluralResourceName)
|
|
|
15
15
|
const paginationClause = buildPaginationClause(queryOptions);
|
|
16
16
|
const hasJoins = operations.some(op => op instanceof LeftJoin || op instanceof InnerJoin || op instanceof LeftJoinMany);
|
|
17
17
|
const selectClause = hasJoins
|
|
18
|
-
? await buildSelectClause(client, pluralResourceName,
|
|
18
|
+
? await buildSelectClause(client, pluralResourceName, operations)
|
|
19
19
|
: '*';
|
|
20
20
|
const tablePrefix = hasJoins ? pluralResourceName : undefined;
|
|
21
21
|
const { whereClause, values } = buildWhereClause(queryOptions, [], tablePrefix);
|
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
import { Operation } from "../../operations/operation.js";
|
|
2
|
-
export declare function buildJoinClauses(operations: Operation[], mainTableName
|
|
2
|
+
export declare function buildJoinClauses(operations: Operation[], mainTableName?: string): string;
|
|
@@ -7,24 +7,17 @@ function convertFieldToSnakeCase(field) {
|
|
|
7
7
|
}
|
|
8
8
|
return toSnakeCase(field);
|
|
9
9
|
}
|
|
10
|
-
function resolveLocalField(localField, mainTableName) {
|
|
11
|
-
if (!localField.includes(".")) {
|
|
12
|
-
const snake = convertFieldToSnakeCase(localField);
|
|
13
|
-
return `"${mainTableName}"."${snake}"`;
|
|
14
|
-
}
|
|
15
|
-
const [alias, field] = localField.split(".");
|
|
16
|
-
const snake = convertFieldToSnakeCase(field);
|
|
17
|
-
return `${alias}."${snake}"`;
|
|
18
|
-
}
|
|
19
10
|
export function buildJoinClauses(operations, mainTableName) {
|
|
20
|
-
let
|
|
11
|
+
let joinClauses = [];
|
|
21
12
|
for (const operation of operations) {
|
|
22
13
|
if (operation instanceof LeftJoin || operation instanceof InnerJoin) {
|
|
23
|
-
const localRef =
|
|
14
|
+
const localRef = convertFieldToSnakeCase(operation.localField);
|
|
24
15
|
const foreignSnake = convertFieldToSnakeCase(operation.foreignField);
|
|
25
16
|
const joinType = operation instanceof InnerJoin ? "INNER JOIN" : "LEFT JOIN";
|
|
26
|
-
|
|
17
|
+
const joinTable = `"${operation.from}" AS "${operation.as}"`;
|
|
18
|
+
const leftSide = mainTableName ? `"${mainTableName}"."${localRef}"` : localRef;
|
|
19
|
+
joinClauses.push(`${joinType} ${joinTable} ON ${leftSide} = ${operation.as}."${foreignSnake}"`);
|
|
27
20
|
}
|
|
28
21
|
}
|
|
29
|
-
return
|
|
22
|
+
return joinClauses.join('\n');
|
|
30
23
|
}
|
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
import { Client } from 'pg';
|
|
2
2
|
import { Operation } from '../../operations/operation.js';
|
|
3
|
-
export declare function buildSelectClause(client: Client, mainTableName: string,
|
|
3
|
+
export declare function buildSelectClause(client: Client, mainTableName: string, operations: Operation[]): Promise<string>;
|
|
@@ -1,29 +1,6 @@
|
|
|
1
1
|
import { LeftJoin } from '../../operations/left-join.operation.js';
|
|
2
2
|
import { InnerJoin } from '../../operations/inner-join.operation.js';
|
|
3
3
|
import { LeftJoinMany } from '../../operations/left-join-many.operation.js';
|
|
4
|
-
import { toSnakeCase } from './convert-keys.util.js';
|
|
5
|
-
function convertFieldToSnakeCase(field) {
|
|
6
|
-
if (field.startsWith('_')) {
|
|
7
|
-
return field;
|
|
8
|
-
}
|
|
9
|
-
return toSnakeCase(field);
|
|
10
|
-
}
|
|
11
|
-
function resolveLocalRef(localField, mainTableName, operations, currentIndex) {
|
|
12
|
-
if (!localField.includes('.')) {
|
|
13
|
-
const snake = convertFieldToSnakeCase(localField);
|
|
14
|
-
return `"${mainTableName}"."${snake}"`;
|
|
15
|
-
}
|
|
16
|
-
const [alias, field] = localField.split('.');
|
|
17
|
-
const snake = convertFieldToSnakeCase(field);
|
|
18
|
-
const priorOps = operations.slice(0, currentIndex);
|
|
19
|
-
const leftJoinMany = priorOps.find((op) => op instanceof LeftJoinMany && op.as === alias);
|
|
20
|
-
if (leftJoinMany) {
|
|
21
|
-
const castType = field === '_id' || snake === '_id' ? '::int' : '::text';
|
|
22
|
-
const elemAlias = `_elem_${alias}`;
|
|
23
|
-
return `(SELECT (${elemAlias}->>'${snake}')${castType} FROM jsonb_array_elements("${alias}") AS ${elemAlias})`;
|
|
24
|
-
}
|
|
25
|
-
return `${alias}."${snake}"`;
|
|
26
|
-
}
|
|
27
4
|
async function getTableColumns(client, tableName) {
|
|
28
5
|
const result = await client.query(`
|
|
29
6
|
SELECT column_name
|
|
@@ -45,62 +22,19 @@ function findEnrichmentTarget(operation, operations) {
|
|
|
45
22
|
}
|
|
46
23
|
return null;
|
|
47
24
|
}
|
|
48
|
-
export async function buildSelectClause(client, mainTableName,
|
|
25
|
+
export async function buildSelectClause(client, mainTableName, operations) {
|
|
49
26
|
const leftJoinOperations = operations.filter(op => op instanceof LeftJoin);
|
|
50
27
|
const innerJoinOperations = operations.filter(op => op instanceof InnerJoin);
|
|
51
28
|
const leftJoinManyOperations = operations.filter(op => op instanceof LeftJoinMany);
|
|
52
29
|
const mainTableColumns = await getTableColumns(client, mainTableName);
|
|
53
30
|
const mainSelects = mainTableColumns.map(col => `"${mainTableName}"."${col}" AS "${col}"`);
|
|
54
31
|
const joinSelects = [];
|
|
55
|
-
for (const join of [...leftJoinOperations, ...innerJoinOperations]) {
|
|
32
|
+
for (const join of [...leftJoinOperations, ...leftJoinManyOperations, ...innerJoinOperations]) {
|
|
56
33
|
const joinColumns = await getTableColumns(client, join.from);
|
|
57
34
|
for (const col of joinColumns) {
|
|
58
35
|
joinSelects.push(`${join.as}."${col}" AS "${join.as}__${col}"`);
|
|
59
36
|
}
|
|
60
37
|
}
|
|
61
|
-
for (let i = 0; i < leftJoinManyOperations.length; i++) {
|
|
62
|
-
const joinMany = leftJoinManyOperations[i];
|
|
63
|
-
const manyColumns = await getTableColumns(client, joinMany.from);
|
|
64
|
-
const foreignSnake = convertFieldToSnakeCase(joinMany.foreignField);
|
|
65
|
-
const currentOpIndex = operations.indexOf(joinMany);
|
|
66
|
-
const localRef = resolveLocalRef(joinMany.localField, mainTableName, operations, currentOpIndex);
|
|
67
|
-
const subAlias = `_sub_${joinMany.as}`;
|
|
68
|
-
const objParts = manyColumns.map(c => `'${c.replace(/'/g, "''")}', ${subAlias}."${c}"`).join(', ');
|
|
69
|
-
const priorOps = operations.slice(0, currentOpIndex);
|
|
70
|
-
const referencedLeftJoinMany = joinMany.localField.includes('.')
|
|
71
|
-
? priorOps.find((op) => op instanceof LeftJoinMany && op.as === joinMany.localField.split('.')[0])
|
|
72
|
-
: null;
|
|
73
|
-
let whereClause;
|
|
74
|
-
if (referencedLeftJoinMany) {
|
|
75
|
-
const [prevAlias, fieldName] = joinMany.localField.split('.');
|
|
76
|
-
const prevFieldSnake = convertFieldToSnakeCase(fieldName);
|
|
77
|
-
const buildNestedInQuery = (refOp, extractField) => {
|
|
78
|
-
const extractFieldSnake = convertFieldToSnakeCase(extractField);
|
|
79
|
-
const refForeignSnake = convertFieldToSnakeCase(refOp.foreignField);
|
|
80
|
-
if (!refOp.localField.includes('.')) {
|
|
81
|
-
const refLocalSnake = convertFieldToSnakeCase(refOp.localField);
|
|
82
|
-
return `(SELECT "${extractFieldSnake}" FROM "${refOp.from}" WHERE "${refOp.from}"."${refForeignSnake}" = "${mainTableName}"."${refLocalSnake}")`;
|
|
83
|
-
}
|
|
84
|
-
const [parentAlias, parentField] = refOp.localField.split('.');
|
|
85
|
-
const parentOpIndex = operations.indexOf(refOp);
|
|
86
|
-
const parentOp = operations.slice(0, parentOpIndex).find((op) => op instanceof LeftJoinMany && op.as === parentAlias);
|
|
87
|
-
if (parentOp) {
|
|
88
|
-
const parentFieldSnake = convertFieldToSnakeCase(parentField);
|
|
89
|
-
const nestedQuery = buildNestedInQuery(parentOp, parentFieldSnake);
|
|
90
|
-
return `(SELECT "${extractFieldSnake}" FROM "${refOp.from}" WHERE "${refOp.from}"."${refForeignSnake}" IN ${nestedQuery})`;
|
|
91
|
-
}
|
|
92
|
-
const parentFieldSnake = convertFieldToSnakeCase(parentField);
|
|
93
|
-
return `(SELECT "${extractFieldSnake}" FROM "${refOp.from}" WHERE "${refOp.from}"."${refForeignSnake}" = ${parentAlias}."${parentFieldSnake}")`;
|
|
94
|
-
};
|
|
95
|
-
const nestedQuery = buildNestedInQuery(referencedLeftJoinMany, prevFieldSnake);
|
|
96
|
-
whereClause = `${subAlias}."${foreignSnake}" IN ${nestedQuery}`;
|
|
97
|
-
}
|
|
98
|
-
else {
|
|
99
|
-
whereClause = `${subAlias}."${foreignSnake}" = ${localRef}`;
|
|
100
|
-
}
|
|
101
|
-
const subquery = `(SELECT COALESCE(jsonb_agg(jsonb_build_object(${objParts})), '[]'::jsonb) FROM "${joinMany.from}" AS ${subAlias} WHERE ${whereClause})`;
|
|
102
|
-
joinSelects.push(`${subquery} AS "${joinMany.as}"`);
|
|
103
|
-
}
|
|
104
38
|
const allSelects = [...mainSelects, ...joinSelects];
|
|
105
|
-
return allSelects.join('
|
|
39
|
+
return allSelects.join(',\n');
|
|
106
40
|
}
|
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
import { Operation } from '../../operations/operation.js';
|
|
2
|
-
export declare function transformJoinResults<T>(rows:
|
|
2
|
+
export declare function transformJoinResults<T>(rows: any[], operations: Operation[]): T[];
|
|
@@ -1,6 +1,30 @@
|
|
|
1
1
|
import { LeftJoin } from '../../operations/left-join.operation.js';
|
|
2
2
|
import { InnerJoin } from '../../operations/inner-join.operation.js';
|
|
3
3
|
import { LeftJoinMany } from '../../operations/left-join-many.operation.js';
|
|
4
|
+
function findNestedObject(obj, alias, path = []) {
|
|
5
|
+
if (obj[alias] !== undefined && obj[alias] !== null) {
|
|
6
|
+
return { obj: obj[alias], path: [...path, alias] };
|
|
7
|
+
}
|
|
8
|
+
for (const key in obj) {
|
|
9
|
+
if (obj[key] && typeof obj[key] === 'object' && !Array.isArray(obj[key])) {
|
|
10
|
+
const found = findNestedObject(obj[key], alias, [...path, key]);
|
|
11
|
+
if (found !== null) {
|
|
12
|
+
return found;
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
else if (Array.isArray(obj[key])) {
|
|
16
|
+
for (const item of obj[key]) {
|
|
17
|
+
if (item && typeof item === 'object') {
|
|
18
|
+
const found = findNestedObject(item, alias, [...path, key]);
|
|
19
|
+
if (found !== null) {
|
|
20
|
+
return found;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
return null;
|
|
27
|
+
}
|
|
4
28
|
function parseJsonValue(value) {
|
|
5
29
|
if (value === null || value === undefined) {
|
|
6
30
|
return null;
|
|
@@ -15,88 +39,90 @@ function parseJsonValue(value) {
|
|
|
15
39
|
}
|
|
16
40
|
return value;
|
|
17
41
|
}
|
|
18
|
-
function getJoinAliasesAndParents(operations) {
|
|
19
|
-
const aliases = new Set();
|
|
20
|
-
const parentByAlias = new Map();
|
|
21
|
-
for (const op of operations) {
|
|
22
|
-
if (op instanceof LeftJoin || op instanceof InnerJoin || op instanceof LeftJoinMany) {
|
|
23
|
-
aliases.add(op.as);
|
|
24
|
-
const parent = op.localField.includes('.') ? op.localField.split('.')[0] : null;
|
|
25
|
-
parentByAlias.set(op.as, parent);
|
|
26
|
-
}
|
|
27
|
-
}
|
|
28
|
-
return { aliases, parentByAlias };
|
|
29
|
-
}
|
|
30
|
-
const PREFIX_SEP = '__';
|
|
31
|
-
function setJoinValue(joinData, alias, value, parentByAlias, operations) {
|
|
32
|
-
const parent = parentByAlias.get(alias) ?? null;
|
|
33
|
-
if (parent) {
|
|
34
|
-
const parentValue = joinData[parent];
|
|
35
|
-
const parentOp = operations.find(op => (op instanceof LeftJoinMany || op instanceof LeftJoin || op instanceof InnerJoin) && op.as === parent);
|
|
36
|
-
const isParentArray = parentOp instanceof LeftJoinMany;
|
|
37
|
-
if (isParentArray && Array.isArray(parentValue)) {
|
|
38
|
-
joinData[alias] = value;
|
|
39
|
-
}
|
|
40
|
-
else {
|
|
41
|
-
let parentObj = parentValue;
|
|
42
|
-
if (parentObj == null || typeof parentObj !== 'object' || Array.isArray(parentObj)) {
|
|
43
|
-
parentObj = {};
|
|
44
|
-
joinData[parent] = parentObj;
|
|
45
|
-
}
|
|
46
|
-
parentObj[alias] = value;
|
|
47
|
-
}
|
|
48
|
-
}
|
|
49
|
-
else {
|
|
50
|
-
joinData[alias] = value;
|
|
51
|
-
}
|
|
52
|
-
}
|
|
53
42
|
export function transformJoinResults(rows, operations) {
|
|
54
|
-
const
|
|
55
|
-
|
|
43
|
+
const leftJoinOperations = operations.filter(op => op instanceof LeftJoin);
|
|
44
|
+
const innerJoinOperations = operations.filter(op => op instanceof InnerJoin);
|
|
45
|
+
const leftJoinManyOperations = operations.filter(op => op instanceof LeftJoinMany);
|
|
46
|
+
const allJoinOperations = [...leftJoinOperations, ...innerJoinOperations, ...leftJoinManyOperations];
|
|
47
|
+
if (allJoinOperations.length === 0) {
|
|
56
48
|
return rows;
|
|
57
49
|
}
|
|
58
|
-
|
|
50
|
+
const allJoinAliases = allJoinOperations.map(j => j.as);
|
|
51
|
+
return rows.map(row => {
|
|
59
52
|
const transformed = {};
|
|
60
|
-
const
|
|
61
|
-
const prefixedByAlias = {};
|
|
53
|
+
const joinData = {};
|
|
62
54
|
for (const key of Object.keys(row)) {
|
|
63
|
-
|
|
64
|
-
|
|
55
|
+
const hasJoinPrefix = allJoinOperations.some(join => key.startsWith(`${join.as}__`));
|
|
56
|
+
const isJoinAlias = allJoinAliases.includes(key);
|
|
57
|
+
if (!hasJoinPrefix && !isJoinAlias) {
|
|
58
|
+
transformed[key] = row[key];
|
|
65
59
|
}
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
const
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
60
|
+
}
|
|
61
|
+
for (const operation of operations) {
|
|
62
|
+
if (operation instanceof LeftJoin || operation instanceof InnerJoin) {
|
|
63
|
+
const prefix = `${operation.as}__`;
|
|
64
|
+
const joinedData = {};
|
|
65
|
+
let hasAnyData = false;
|
|
66
|
+
for (const key of Object.keys(row)) {
|
|
67
|
+
if (key.startsWith(prefix)) {
|
|
68
|
+
const columnName = key.substring(prefix.length);
|
|
69
|
+
const value = row[key];
|
|
70
|
+
joinedData[columnName] = value;
|
|
71
|
+
if (value !== null && value !== undefined) {
|
|
72
|
+
hasAnyData = true;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
if (operation.localField.includes('.')) {
|
|
77
|
+
const [tableAlias] = operation.localField.split('.');
|
|
78
|
+
const relatedJoin = allJoinOperations.find(j => j.as === tableAlias);
|
|
79
|
+
let targetObject = null;
|
|
80
|
+
if (relatedJoin && joinData[relatedJoin.as]) {
|
|
81
|
+
targetObject = joinData[relatedJoin.as];
|
|
82
|
+
}
|
|
83
|
+
else {
|
|
84
|
+
const found = findNestedObject(joinData, tableAlias);
|
|
85
|
+
if (found) {
|
|
86
|
+
targetObject = found.obj;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
if (targetObject) {
|
|
90
|
+
targetObject[operation.as] = hasAnyData ? joinedData : null;
|
|
91
|
+
}
|
|
92
|
+
else {
|
|
93
|
+
joinData[operation.as] = hasAnyData ? joinedData : null;
|
|
94
|
+
}
|
|
74
95
|
}
|
|
75
96
|
else {
|
|
76
|
-
|
|
97
|
+
joinData[operation.as] = hasAnyData ? joinedData : null;
|
|
77
98
|
}
|
|
78
99
|
}
|
|
79
|
-
else {
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
100
|
+
else if (operation instanceof LeftJoinMany) {
|
|
101
|
+
const jsonValue = parseJsonValue(row[operation.as]);
|
|
102
|
+
let parsedValue = Array.isArray(jsonValue) ? jsonValue : (jsonValue ? [jsonValue] : []);
|
|
103
|
+
if (operation.localField.includes('.')) {
|
|
104
|
+
const [tableAlias] = operation.localField.split('.');
|
|
105
|
+
const relatedJoin = allJoinOperations.find(j => j.as === tableAlias);
|
|
106
|
+
const relatedJoinMany = leftJoinManyOperations.find(j => j.as === tableAlias);
|
|
107
|
+
let targetObject = null;
|
|
108
|
+
if (relatedJoin && joinData[relatedJoin.as]) {
|
|
109
|
+
targetObject = joinData[relatedJoin.as];
|
|
110
|
+
}
|
|
111
|
+
else if (relatedJoinMany && joinData[relatedJoinMany.as]) {
|
|
112
|
+
targetObject = joinData[relatedJoinMany.as];
|
|
113
|
+
}
|
|
114
|
+
if (targetObject) {
|
|
115
|
+
targetObject[operation.as] = parsedValue;
|
|
116
|
+
}
|
|
117
|
+
else {
|
|
118
|
+
joinData[operation.as] = parsedValue;
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
else {
|
|
122
|
+
joinData[operation.as] = parsedValue;
|
|
123
|
+
}
|
|
88
124
|
}
|
|
89
125
|
}
|
|
90
|
-
const joinData = {};
|
|
91
|
-
for (const op of operations) {
|
|
92
|
-
if (!(op instanceof LeftJoin || op instanceof InnerJoin || op instanceof LeftJoinMany))
|
|
93
|
-
continue;
|
|
94
|
-
const alias = op.as;
|
|
95
|
-
const value = flatJoinValues[alias];
|
|
96
|
-
if (value === undefined)
|
|
97
|
-
continue;
|
|
98
|
-
setJoinValue(joinData, alias, value, parentByAlias, operations);
|
|
99
|
-
}
|
|
100
126
|
if (Object.keys(joinData).length > 0) {
|
|
101
127
|
transformed._joinData = joinData;
|
|
102
128
|
}
|