relq 1.0.87 → 1.0.89
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/cjs/cli/commands/generate.cjs +10 -2
- package/dist/cjs/cli/commands/push.cjs +10 -2
- package/dist/cjs/cli/utils/migration-generator.cjs +34 -1
- package/dist/cjs/cli/utils/schema-diff.cjs +38 -0
- package/dist/cjs/cli/utils/schema-to-ast.cjs +64 -5
- package/dist/cjs/core/helpers/ConnectedSelectBuilder.cjs +38 -4
- package/dist/cjs/core/helpers/select-joins.cjs +97 -3
- package/dist/cjs/select/join-condition-builder.cjs +4 -4
- package/dist/cjs/select/join-many-condition-builder.cjs +16 -10
- package/dist/cjs/select/select-builder.cjs +24 -2
- package/dist/cjs/select/sql-expression.cjs +38 -0
- package/dist/cjs/utils/fk-resolver.cjs +16 -12
- package/dist/esm/cli/commands/generate.js +11 -3
- package/dist/esm/cli/commands/push.js +11 -3
- package/dist/esm/cli/utils/migration-generator.js +1 -1
- package/dist/esm/cli/utils/schema-diff.js +37 -0
- package/dist/esm/cli/utils/schema-to-ast.js +64 -5
- package/dist/esm/core/helpers/ConnectedSelectBuilder.js +39 -5
- package/dist/esm/core/helpers/select-joins.js +96 -3
- package/dist/esm/select/join-condition-builder.js +4 -4
- package/dist/esm/select/join-many-condition-builder.js +16 -10
- package/dist/esm/select/select-builder.js +24 -2
- package/dist/esm/select/sql-expression.js +33 -0
- package/dist/esm/utils/fk-resolver.js +16 -12
- package/dist/index.d.ts +269 -38
- package/package.json +1 -1
|
@@ -148,7 +148,7 @@ exports.default = (0, citty_1.defineCommand)({
|
|
|
148
148
|
spin.start('Computing diff...');
|
|
149
149
|
const rawPatterns = ignorePatterns.map(pat => pat.raw);
|
|
150
150
|
const diff = (0, schema_diff_1.diffSchemas)((0, schema_hash_1.normalizeSchema)(dbSchema), (0, schema_hash_1.normalizeSchema)(desiredSchema));
|
|
151
|
-
|
|
151
|
+
let filteredDiff = (0, schema_diff_1.filterDiff)(diff, rawPatterns);
|
|
152
152
|
spin.stop('Diff computed');
|
|
153
153
|
if (!filteredDiff.hasChanges) {
|
|
154
154
|
console.log('');
|
|
@@ -184,9 +184,17 @@ exports.default = (0, citty_1.defineCommand)({
|
|
|
184
184
|
message: 'Include destructive changes?',
|
|
185
185
|
initialValue: false,
|
|
186
186
|
});
|
|
187
|
-
if (p.isCancel(proceed)
|
|
187
|
+
if (p.isCancel(proceed)) {
|
|
188
188
|
(0, ui_1.fatal)('Operation cancelled by user');
|
|
189
189
|
}
|
|
190
|
+
if (!proceed) {
|
|
191
|
+
filteredDiff = (0, schema_diff_1.stripDestructiveChanges)(filteredDiff);
|
|
192
|
+
if (!filteredDiff.hasChanges) {
|
|
193
|
+
p.log.info('No non-destructive changes to generate.');
|
|
194
|
+
process.exit(0);
|
|
195
|
+
}
|
|
196
|
+
p.log.info('Destructive changes excluded. Continuing with safe changes only.');
|
|
197
|
+
}
|
|
190
198
|
}
|
|
191
199
|
}
|
|
192
200
|
if (!migrationName) {
|
|
@@ -158,7 +158,7 @@ async function runPush(config, projectRoot, opts = {}) {
|
|
|
158
158
|
spin.stop(`Database: ${colors_1.colors.cyan(String(dbSchema.tables.length))} table(s) found`);
|
|
159
159
|
spin.start('Computing diff...');
|
|
160
160
|
const diff = (0, schema_diff_1.diffSchemas)((0, schema_hash_1.normalizeSchema)(dbSchema), (0, schema_hash_1.normalizeSchema)(desiredSchema));
|
|
161
|
-
|
|
161
|
+
let filteredDiff = (0, schema_diff_1.filterDiff)(diff, ignorePatterns.map(pat => pat.raw));
|
|
162
162
|
spin.stop('Diff computed');
|
|
163
163
|
if (!filteredDiff.hasChanges) {
|
|
164
164
|
console.log('');
|
|
@@ -215,9 +215,17 @@ async function runPush(config, projectRoot, opts = {}) {
|
|
|
215
215
|
message: 'Include destructive changes?',
|
|
216
216
|
initialValue: false,
|
|
217
217
|
});
|
|
218
|
-
if (p.isCancel(proceed)
|
|
218
|
+
if (p.isCancel(proceed)) {
|
|
219
219
|
(0, ui_1.fatal)('Operation cancelled by user');
|
|
220
220
|
}
|
|
221
|
+
if (!proceed) {
|
|
222
|
+
filteredDiff = (0, schema_diff_1.stripDestructiveChanges)(filteredDiff);
|
|
223
|
+
if (!filteredDiff.hasChanges) {
|
|
224
|
+
p.log.info('No non-destructive changes to push.');
|
|
225
|
+
process.exit(0);
|
|
226
|
+
}
|
|
227
|
+
p.log.info('Destructive changes excluded. Continuing with safe changes only.');
|
|
228
|
+
}
|
|
221
229
|
}
|
|
222
230
|
}
|
|
223
231
|
spin.start('Generating SQL...');
|
|
@@ -1,4 +1,37 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
2
35
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
36
|
exports.generateMigration = generateMigration;
|
|
4
37
|
exports.generateMigrationFromComparison = generateMigrationFromComparison;
|
|
@@ -6,6 +39,7 @@ exports.generateMigrationFile = generateMigrationFile;
|
|
|
6
39
|
exports.getNextMigrationNumber = getNextMigrationNumber;
|
|
7
40
|
exports.generateTimestampedName = generateTimestampedName;
|
|
8
41
|
exports.generateMigrationName = generateMigrationName;
|
|
42
|
+
const fs = __importStar(require("node:fs"));
|
|
9
43
|
function generateMigration(diff, options = {}) {
|
|
10
44
|
const { includeDown = true } = options;
|
|
11
45
|
const up = [];
|
|
@@ -580,7 +614,6 @@ function mapDataType(col) {
|
|
|
580
614
|
return typeMap[type] || type;
|
|
581
615
|
}
|
|
582
616
|
function getNextMigrationNumber(migrationsDir) {
|
|
583
|
-
const fs = require('fs');
|
|
584
617
|
if (!fs.existsSync(migrationsDir)) {
|
|
585
618
|
return '001';
|
|
586
619
|
}
|
|
@@ -5,6 +5,7 @@ exports.formatDiff = formatDiff;
|
|
|
5
5
|
exports.formatSummary = formatSummary;
|
|
6
6
|
exports.filterDiff = filterDiff;
|
|
7
7
|
exports.hasDestructiveChanges = hasDestructiveChanges;
|
|
8
|
+
exports.stripDestructiveChanges = stripDestructiveChanges;
|
|
8
9
|
exports.getDestructiveTables = getDestructiveTables;
|
|
9
10
|
exports.compareSchemas = compareSchemas;
|
|
10
11
|
const colors_1 = require("./colors.cjs");
|
|
@@ -493,6 +494,43 @@ function hasDestructiveChanges(diff) {
|
|
|
493
494
|
}
|
|
494
495
|
return false;
|
|
495
496
|
}
|
|
497
|
+
function stripDestructiveChanges(diff) {
|
|
498
|
+
const safeTables = diff.tables
|
|
499
|
+
.filter(t => t.type !== 'removed')
|
|
500
|
+
.map(t => {
|
|
501
|
+
if (t.type !== 'modified')
|
|
502
|
+
return t;
|
|
503
|
+
const safeColumns = t.columns?.filter(c => c.type !== 'removed');
|
|
504
|
+
return {
|
|
505
|
+
...t,
|
|
506
|
+
columns: safeColumns,
|
|
507
|
+
};
|
|
508
|
+
})
|
|
509
|
+
.filter(t => {
|
|
510
|
+
if (t.type !== 'modified')
|
|
511
|
+
return true;
|
|
512
|
+
return (t.columns?.length || 0) > 0 || (t.indexes?.length || 0) > 0 || (t.constraints?.length || 0) > 0;
|
|
513
|
+
});
|
|
514
|
+
let columnsRemoved = 0;
|
|
515
|
+
let tablesRemoved = 0;
|
|
516
|
+
for (const t of diff.tables) {
|
|
517
|
+
if (t.type === 'removed')
|
|
518
|
+
tablesRemoved++;
|
|
519
|
+
if (t.columns)
|
|
520
|
+
columnsRemoved += t.columns.filter(c => c.type === 'removed').length;
|
|
521
|
+
}
|
|
522
|
+
return {
|
|
523
|
+
...diff,
|
|
524
|
+
tables: safeTables,
|
|
525
|
+
hasChanges: safeTables.length > 0 || diff.extensions.length > 0,
|
|
526
|
+
summary: {
|
|
527
|
+
...diff.summary,
|
|
528
|
+
tablesRemoved: 0,
|
|
529
|
+
columnsRemoved: 0,
|
|
530
|
+
tablesModified: safeTables.filter(t => t.type === 'modified').length,
|
|
531
|
+
},
|
|
532
|
+
};
|
|
533
|
+
}
|
|
496
534
|
function getDestructiveTables(diff) {
|
|
497
535
|
const tables = [];
|
|
498
536
|
for (const table of diff.tables) {
|
|
@@ -86,8 +86,14 @@ function isExtensionsConfig(value) {
|
|
|
86
86
|
typeof value.toSQL === 'function';
|
|
87
87
|
}
|
|
88
88
|
function isRelationsConfig(value) {
|
|
89
|
-
|
|
90
|
-
|
|
89
|
+
if (!value || typeof value !== 'object' || Array.isArray(value))
|
|
90
|
+
return false;
|
|
91
|
+
for (const val of Object.values(value)) {
|
|
92
|
+
if (Array.isArray(val) && val.length > 0 && val[0]?.$type === 'foreignKey') {
|
|
93
|
+
return true;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
return false;
|
|
91
97
|
}
|
|
92
98
|
function generateTrackingId() {
|
|
93
99
|
const chars = 'abcdefghijklmnopqrstuvwxyz0123456789';
|
|
@@ -178,8 +184,9 @@ function mapColumnTypeToPostgres(type) {
|
|
|
178
184
|
'CUBE': 'cube',
|
|
179
185
|
'VECTOR': 'vector',
|
|
180
186
|
};
|
|
181
|
-
const
|
|
182
|
-
|
|
187
|
+
const baseType = type.replace(/\(.*\)$/, '');
|
|
188
|
+
const upperType = baseType.toUpperCase().trim();
|
|
189
|
+
return typeMap[upperType] || baseType.toLowerCase().trim();
|
|
183
190
|
}
|
|
184
191
|
function schemaToAST(schema) {
|
|
185
192
|
const result = {
|
|
@@ -254,9 +261,61 @@ function schemaToAST(schema) {
|
|
|
254
261
|
result.extensions.push(...value.extensions);
|
|
255
262
|
continue;
|
|
256
263
|
}
|
|
264
|
+
if (isRelationsConfig(value)) {
|
|
265
|
+
mergeRelationsIntoAST(result, value);
|
|
266
|
+
continue;
|
|
267
|
+
}
|
|
257
268
|
}
|
|
258
269
|
return result;
|
|
259
270
|
}
|
|
271
|
+
function mergeRelationsIntoAST(result, relations) {
|
|
272
|
+
for (const [_tableKey, fkDefs] of Object.entries(relations)) {
|
|
273
|
+
if (!Array.isArray(fkDefs))
|
|
274
|
+
continue;
|
|
275
|
+
for (const fk of fkDefs) {
|
|
276
|
+
if (!fk || fk.$type !== 'foreignKey')
|
|
277
|
+
continue;
|
|
278
|
+
const sourceTableSqlName = fk.$columns?.[0]?.table;
|
|
279
|
+
if (!sourceTableSqlName)
|
|
280
|
+
continue;
|
|
281
|
+
const table = result.tables.find(t => t.name === sourceTableSqlName);
|
|
282
|
+
if (!table)
|
|
283
|
+
continue;
|
|
284
|
+
const sourceColumns = fk.$columns.map(c => c.column);
|
|
285
|
+
const targetColumns = fk.$references
|
|
286
|
+
? fk.$references.map(r => r.column)
|
|
287
|
+
: [];
|
|
288
|
+
const srcColList = sourceColumns.map(c => `"${c}"`).join(', ');
|
|
289
|
+
let definition = `FOREIGN KEY (${srcColList}) REFERENCES "${fk.$targetTable}"`;
|
|
290
|
+
if (targetColumns.length > 0) {
|
|
291
|
+
const tgtColList = targetColumns.map(c => `"${c}"`).join(', ');
|
|
292
|
+
definition += ` (${tgtColList})`;
|
|
293
|
+
}
|
|
294
|
+
if (fk.$onDelete && fk.$onDelete !== 'NO ACTION') {
|
|
295
|
+
definition += ` ON DELETE ${fk.$onDelete}`;
|
|
296
|
+
}
|
|
297
|
+
if (fk.$onUpdate && fk.$onUpdate !== 'NO ACTION') {
|
|
298
|
+
definition += ` ON UPDATE ${fk.$onUpdate}`;
|
|
299
|
+
}
|
|
300
|
+
const constraint = {
|
|
301
|
+
name: '',
|
|
302
|
+
type: 'FOREIGN KEY',
|
|
303
|
+
columns: sourceColumns,
|
|
304
|
+
references: {
|
|
305
|
+
table: fk.$targetTable,
|
|
306
|
+
columns: targetColumns.length > 0 ? targetColumns : sourceColumns,
|
|
307
|
+
onDelete: fk.$onDelete,
|
|
308
|
+
onUpdate: fk.$onUpdate,
|
|
309
|
+
match: fk.$match,
|
|
310
|
+
deferrable: fk.$deferrable,
|
|
311
|
+
initiallyDeferred: fk.$initiallyDeferred,
|
|
312
|
+
},
|
|
313
|
+
trackingId: fk.$trackingId,
|
|
314
|
+
};
|
|
315
|
+
table.constraints.push(constraint);
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
}
|
|
260
319
|
function tableToAST(table) {
|
|
261
320
|
if (!isTableDefinition(table))
|
|
262
321
|
return null;
|
|
@@ -631,7 +690,7 @@ function triggerToAST(trigger) {
|
|
|
631
690
|
function parsedColumnToColumnInfo(col) {
|
|
632
691
|
return {
|
|
633
692
|
name: col.name,
|
|
634
|
-
dataType: col.type,
|
|
693
|
+
dataType: col.isArray ? `${col.type}[]` : col.type,
|
|
635
694
|
isNullable: col.isNullable,
|
|
636
695
|
defaultValue: col.defaultValue || null,
|
|
637
696
|
isPrimaryKey: col.isPrimaryKey,
|
|
@@ -5,6 +5,8 @@ const methods_1 = require("./methods.cjs");
|
|
|
5
5
|
const select_joins_1 = require("./select-joins.cjs");
|
|
6
6
|
const select_pagination_1 = require("./select-pagination.cjs");
|
|
7
7
|
const capability_guard_1 = require("./capability-guard.cjs");
|
|
8
|
+
const sql_expression_1 = require("../../select/sql-expression.cjs");
|
|
9
|
+
const table_proxy_1 = require("../../select/table-proxy.cjs");
|
|
8
10
|
class ConnectedSelectBuilder {
|
|
9
11
|
builder;
|
|
10
12
|
relq;
|
|
@@ -149,6 +151,28 @@ class ConnectedSelectBuilder {
|
|
|
149
151
|
this.builder.having(callback);
|
|
150
152
|
return this;
|
|
151
153
|
}
|
|
154
|
+
include(callback) {
|
|
155
|
+
const agg = new sql_expression_1.AggregateFunctions();
|
|
156
|
+
const internal = this.relq[methods_1.INTERNAL];
|
|
157
|
+
const schema = internal.getSchema() || {};
|
|
158
|
+
const sourceSchemaKey = this.schemaKey || this.tableName;
|
|
159
|
+
const sourceAlias = this.builder.getTableIdentifier();
|
|
160
|
+
const tableProxies = new Proxy({}, {
|
|
161
|
+
get(_, tableKey) {
|
|
162
|
+
if (typeof tableKey === 'symbol')
|
|
163
|
+
return undefined;
|
|
164
|
+
const tableDef = schema[tableKey];
|
|
165
|
+
const tableName = tableDef?.$name || tableKey;
|
|
166
|
+
const alias = tableKey === sourceSchemaKey ? sourceAlias : tableKey;
|
|
167
|
+
return (0, table_proxy_1.createTableProxy)(tableName, alias, tableDef);
|
|
168
|
+
}
|
|
169
|
+
});
|
|
170
|
+
const expressions = callback(agg, tableProxies);
|
|
171
|
+
for (const [alias, expr] of Object.entries(expressions)) {
|
|
172
|
+
this.builder.addIncludeExpression(alias, expr.sql);
|
|
173
|
+
}
|
|
174
|
+
return this;
|
|
175
|
+
}
|
|
152
176
|
join(tableOrAlias, callback) {
|
|
153
177
|
(0, select_joins_1.executeTypeSafeJoin)(this.joinCtx, 'JOIN', tableOrAlias, callback);
|
|
154
178
|
return this;
|
|
@@ -175,14 +199,24 @@ class ConnectedSelectBuilder {
|
|
|
175
199
|
this.builder.addRawJoin(`LEFT JOIN (${subquerySQL}) AS "${alias}" ON ${onClause}`);
|
|
176
200
|
return this;
|
|
177
201
|
}
|
|
178
|
-
joinMany(tableOrAlias,
|
|
202
|
+
joinMany(tableOrAlias, callbackOrOptions, throughCallback) {
|
|
179
203
|
(0, capability_guard_1.requireCapability)(this.relq, 'lateral', 'LATERAL JOIN (joinMany)', 'Use separate queries for one-to-many relationships');
|
|
180
|
-
|
|
204
|
+
if (callbackOrOptions && typeof callbackOrOptions === 'object' && 'through' in callbackOrOptions) {
|
|
205
|
+
(0, select_joins_1.executeTypeSafeJoinManyThrough)(this.joinCtx, 'JOIN', tableOrAlias, callbackOrOptions.through, throughCallback);
|
|
206
|
+
}
|
|
207
|
+
else {
|
|
208
|
+
(0, select_joins_1.executeTypeSafeJoinMany)(this.joinCtx, 'JOIN', tableOrAlias, callbackOrOptions);
|
|
209
|
+
}
|
|
181
210
|
return this;
|
|
182
211
|
}
|
|
183
|
-
leftJoinMany(tableOrAlias,
|
|
212
|
+
leftJoinMany(tableOrAlias, callbackOrOptions, throughCallback) {
|
|
184
213
|
(0, capability_guard_1.requireCapability)(this.relq, 'lateral', 'LATERAL JOIN (leftJoinMany)', 'Use separate queries for one-to-many relationships');
|
|
185
|
-
|
|
214
|
+
if (callbackOrOptions && typeof callbackOrOptions === 'object' && 'through' in callbackOrOptions) {
|
|
215
|
+
(0, select_joins_1.executeTypeSafeJoinManyThrough)(this.joinCtx, 'LEFT JOIN', tableOrAlias, callbackOrOptions.through, throughCallback);
|
|
216
|
+
}
|
|
217
|
+
else {
|
|
218
|
+
(0, select_joins_1.executeTypeSafeJoinMany)(this.joinCtx, 'LEFT JOIN', tableOrAlias, callbackOrOptions);
|
|
219
|
+
}
|
|
186
220
|
return this;
|
|
187
221
|
}
|
|
188
222
|
distinct() {
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.executeTypeSafeJoin = executeTypeSafeJoin;
|
|
4
4
|
exports.executeTypeSafeJoinMany = executeTypeSafeJoinMany;
|
|
5
|
+
exports.executeTypeSafeJoinManyThrough = executeTypeSafeJoinManyThrough;
|
|
5
6
|
const table_proxy_1 = require("../../select/table-proxy.cjs");
|
|
6
7
|
const join_condition_builder_1 = require("../../select/join-condition-builder.cjs");
|
|
7
8
|
const join_internals_1 = require("../../select/join-internals.cjs");
|
|
@@ -118,12 +119,21 @@ function buildLateralSubquery(tableName, alias, builder, tableDef) {
|
|
|
118
119
|
const internals = builder[join_internals_1.JOIN_INTERNAL];
|
|
119
120
|
parts.push('SELECT');
|
|
120
121
|
const selectedProps = internals.getSelectedColumns();
|
|
122
|
+
const innerJoins = internals.getInnerJoins();
|
|
123
|
+
const hasInnerJoins = innerJoins.length > 0;
|
|
124
|
+
const toSnake = (s) => s.replace(/[A-Z]/g, l => `_${l.toLowerCase()}`);
|
|
121
125
|
if (selectedProps && selectedProps.length > 0) {
|
|
122
126
|
const tableColumns = tableDef?.$columns || tableDef;
|
|
123
127
|
const selectCols = selectedProps.map(prop => {
|
|
124
128
|
const columnDef = tableColumns?.[prop];
|
|
125
|
-
|
|
126
|
-
|
|
129
|
+
if (columnDef || !hasInnerJoins) {
|
|
130
|
+
const sqlName = columnDef?.$columnName || toSnake(prop);
|
|
131
|
+
return `"${alias}"."${sqlName}" AS "${prop}"`;
|
|
132
|
+
}
|
|
133
|
+
else {
|
|
134
|
+
const sqlName = toSnake(prop);
|
|
135
|
+
return `"${sqlName}" AS "${prop}"`;
|
|
136
|
+
}
|
|
127
137
|
}).join(', ');
|
|
128
138
|
parts.push(selectCols);
|
|
129
139
|
}
|
|
@@ -131,7 +141,6 @@ function buildLateralSubquery(tableName, alias, builder, tableDef) {
|
|
|
131
141
|
parts.push(`"${alias}".*`);
|
|
132
142
|
}
|
|
133
143
|
parts.push(`FROM "${tableName}" AS "${alias}"`);
|
|
134
|
-
const innerJoins = internals.getInnerJoins();
|
|
135
144
|
for (const join of innerJoins) {
|
|
136
145
|
parts.push(`${join.type} "${join.table}" AS "${join.alias}" ON ${join.onClause}`);
|
|
137
146
|
}
|
|
@@ -155,3 +164,88 @@ function buildLateralSubquery(tableName, alias, builder, tableDef) {
|
|
|
155
164
|
parts.push(`) AS "${alias}_lateral"`);
|
|
156
165
|
return parts.join(' ');
|
|
157
166
|
}
|
|
167
|
+
function executeTypeSafeJoinManyThrough(ctx, joinType, targetTableOrAlias, junctionTableKey, callback) {
|
|
168
|
+
const [targetKey, targetAlias] = Array.isArray(targetTableOrAlias)
|
|
169
|
+
? targetTableOrAlias
|
|
170
|
+
: [targetTableOrAlias, targetTableOrAlias];
|
|
171
|
+
const internal = ctx.relq[methods_1.INTERNAL];
|
|
172
|
+
const schema = internal.getSchema();
|
|
173
|
+
const relations = internal.getRelations();
|
|
174
|
+
if (!schema || !relations) {
|
|
175
|
+
throw new relq_errors_1.RelqQueryError(`Cannot use { through } without schema and relations config.`, { hint: `Define relations in your schema to use the { through } pattern, or use the callback form of joinMany instead.` });
|
|
176
|
+
}
|
|
177
|
+
const leftTableDef = internal.getTableDef(ctx.schemaKey || ctx.tableName);
|
|
178
|
+
const junctionTableDef = internal.getTableDef(junctionTableKey);
|
|
179
|
+
const targetTableDef = internal.getTableDef(targetKey);
|
|
180
|
+
const leftTableName = leftTableDef?.$name || ctx.tableName;
|
|
181
|
+
const junctionTableName = junctionTableDef?.$name || junctionTableKey;
|
|
182
|
+
const targetTableName = targetTableDef?.$name || targetKey;
|
|
183
|
+
const leftAlias = ctx.builder.getTableIdentifier();
|
|
184
|
+
const leftToJunction = (0, fk_resolver_1.resolveForeignKey)(relations, schema, ctx.schemaKey || ctx.tableName, junctionTableKey);
|
|
185
|
+
if (!leftToJunction) {
|
|
186
|
+
throw new relq_errors_1.RelqQueryError(`Cannot resolve FK between "${ctx.schemaKey || ctx.tableName}" and junction table "${junctionTableKey}".`, { hint: `Define a foreign key relationship between these tables in your relations config, or use the callback form of joinMany instead.` });
|
|
187
|
+
}
|
|
188
|
+
const junctionToTarget = (0, fk_resolver_1.resolveForeignKey)(relations, schema, junctionTableKey, targetKey);
|
|
189
|
+
if (!junctionToTarget) {
|
|
190
|
+
throw new relq_errors_1.RelqQueryError(`Cannot resolve FK between junction table "${junctionTableKey}" and target table "${targetKey}".`, { hint: `Define a foreign key relationship between these tables in your relations config, or use the callback form of joinMany instead.` });
|
|
191
|
+
}
|
|
192
|
+
const conditionBuilder = new join_many_condition_builder_1.JoinManyConditionBuilder();
|
|
193
|
+
if (callback) {
|
|
194
|
+
callback(conditionBuilder);
|
|
195
|
+
}
|
|
196
|
+
const lateralSQL = buildThroughLateralSubquery(junctionTableName, junctionTableKey, targetTableName, targetAlias, leftAlias, leftToJunction, junctionToTarget, conditionBuilder, targetTableDef);
|
|
197
|
+
const lateralJoinType = joinType === 'LEFT JOIN' ? 'LEFT JOIN LATERAL' : 'JOIN LATERAL';
|
|
198
|
+
const joinClause = {
|
|
199
|
+
type: lateralJoinType,
|
|
200
|
+
table: targetTableName,
|
|
201
|
+
alias: targetAlias,
|
|
202
|
+
schemaKey: targetKey,
|
|
203
|
+
lateralSubquery: lateralSQL
|
|
204
|
+
};
|
|
205
|
+
ctx.builder.addStructuredJoin(joinClause);
|
|
206
|
+
}
|
|
207
|
+
function buildThroughLateralSubquery(junctionTableName, junctionAlias, targetTableName, targetAlias, leftAlias, leftToJunction, junctionToTarget, builder, targetTableDef) {
|
|
208
|
+
const parts = [];
|
|
209
|
+
const internals = builder[join_internals_1.JOIN_INTERNAL];
|
|
210
|
+
const toSnake = (s) => s.replace(/[A-Z]/g, l => `_${l.toLowerCase()}`);
|
|
211
|
+
parts.push('(SELECT');
|
|
212
|
+
parts.push(`COALESCE(jsonb_agg(row_to_json(sub.*)), '[]'::jsonb) AS ${targetAlias}`);
|
|
213
|
+
parts.push('FROM (');
|
|
214
|
+
parts.push('SELECT');
|
|
215
|
+
const selectedProps = internals.getSelectedColumns();
|
|
216
|
+
if (selectedProps && selectedProps.length > 0) {
|
|
217
|
+
const tableColumns = targetTableDef?.$columns || targetTableDef;
|
|
218
|
+
const selectCols = selectedProps.map(prop => {
|
|
219
|
+
const columnDef = tableColumns?.[prop];
|
|
220
|
+
const sqlName = columnDef?.$columnName || toSnake(prop);
|
|
221
|
+
return `"${targetAlias}"."${sqlName}" AS "${prop}"`;
|
|
222
|
+
}).join(', ');
|
|
223
|
+
parts.push(selectCols);
|
|
224
|
+
}
|
|
225
|
+
else {
|
|
226
|
+
parts.push(`"${targetAlias}".*`);
|
|
227
|
+
}
|
|
228
|
+
parts.push(`FROM "${junctionTableName}" AS "${junctionAlias}"`);
|
|
229
|
+
parts.push(`JOIN "${targetTableName}" AS "${targetAlias}" ON "${junctionAlias}"."${junctionToTarget.fromColumn}" = "${targetAlias}"."${junctionToTarget.toColumn}"`);
|
|
230
|
+
let whereSQL = `"${junctionAlias}"."${leftToJunction.toColumn}" = "${leftAlias}"."${leftToJunction.fromColumn}"`;
|
|
231
|
+
const userWhereSQL = internals.toWhereSQL();
|
|
232
|
+
if (userWhereSQL) {
|
|
233
|
+
whereSQL += ` AND ${userWhereSQL}`;
|
|
234
|
+
}
|
|
235
|
+
parts.push(`WHERE ${whereSQL}`);
|
|
236
|
+
const orderBySQL = internals.toOrderBySQL();
|
|
237
|
+
if (orderBySQL) {
|
|
238
|
+
parts.push(`ORDER BY ${orderBySQL}`);
|
|
239
|
+
}
|
|
240
|
+
const limitSQL = internals.toLimitSQL();
|
|
241
|
+
if (limitSQL) {
|
|
242
|
+
parts.push(limitSQL);
|
|
243
|
+
}
|
|
244
|
+
const offsetSQL = internals.toOffsetSQL();
|
|
245
|
+
if (offsetSQL) {
|
|
246
|
+
parts.push(offsetSQL);
|
|
247
|
+
}
|
|
248
|
+
parts.push(') sub');
|
|
249
|
+
parts.push(`) AS "${targetAlias}_lateral"`);
|
|
250
|
+
return parts.join(' ');
|
|
251
|
+
}
|
|
@@ -98,12 +98,12 @@ class JoinConditionBuilder {
|
|
|
98
98
|
this.whereConditions.push(...collector.getConditions());
|
|
99
99
|
return this;
|
|
100
100
|
}
|
|
101
|
-
select(
|
|
102
|
-
if (
|
|
103
|
-
this.selectedColumns =
|
|
101
|
+
select(...args) {
|
|
102
|
+
if (args.length === 1 && Array.isArray(args[0])) {
|
|
103
|
+
this.selectedColumns = args[0];
|
|
104
104
|
}
|
|
105
105
|
else {
|
|
106
|
-
this.selectedColumns =
|
|
106
|
+
this.selectedColumns = args;
|
|
107
107
|
}
|
|
108
108
|
return this;
|
|
109
109
|
}
|
|
@@ -34,28 +34,34 @@ class JoinManyConditionBuilder extends join_condition_builder_1.JoinConditionBui
|
|
|
34
34
|
this.rightProxy = rightProxy;
|
|
35
35
|
};
|
|
36
36
|
}
|
|
37
|
-
innerJoin(
|
|
37
|
+
innerJoin(tableOrAlias, callback) {
|
|
38
38
|
if (!this.proxyCreator) {
|
|
39
39
|
throw new Error('innerJoin requires proxy creator - use raw innerJoinRaw() instead');
|
|
40
40
|
}
|
|
41
|
-
const
|
|
41
|
+
const [tableKey, alias] = Array.isArray(tableOrAlias)
|
|
42
|
+
? tableOrAlias
|
|
43
|
+
: [tableOrAlias, tableOrAlias];
|
|
44
|
+
const { proxy: innerProxy, tableName } = this.proxyCreator(tableKey, alias);
|
|
42
45
|
const conditionBuilder = new join_condition_builder_1.JoinConditionBuilder();
|
|
43
46
|
callback(conditionBuilder, innerProxy);
|
|
44
47
|
const internals = conditionBuilder[join_internals_1.JOIN_INTERNAL];
|
|
45
48
|
const onClause = internals.toSQL();
|
|
46
|
-
this.innerJoins.push({ type: 'JOIN', table: tableName, alias
|
|
49
|
+
this.innerJoins.push({ type: 'JOIN', table: tableName, alias, onClause });
|
|
47
50
|
return this;
|
|
48
51
|
}
|
|
49
|
-
leftInnerJoin(
|
|
52
|
+
leftInnerJoin(tableOrAlias, callback) {
|
|
50
53
|
if (!this.proxyCreator) {
|
|
51
54
|
throw new Error('leftInnerJoin requires proxy creator - use raw leftInnerJoinRaw() instead');
|
|
52
55
|
}
|
|
53
|
-
const
|
|
56
|
+
const [tableKey, alias] = Array.isArray(tableOrAlias)
|
|
57
|
+
? tableOrAlias
|
|
58
|
+
: [tableOrAlias, tableOrAlias];
|
|
59
|
+
const { proxy: innerProxy, tableName } = this.proxyCreator(tableKey, alias);
|
|
54
60
|
const conditionBuilder = new join_condition_builder_1.JoinConditionBuilder();
|
|
55
61
|
callback(conditionBuilder, innerProxy);
|
|
56
62
|
const internals = conditionBuilder[join_internals_1.JOIN_INTERNAL];
|
|
57
63
|
const onClause = internals.toSQL();
|
|
58
|
-
this.innerJoins.push({ type: 'LEFT JOIN', table: tableName, alias
|
|
64
|
+
this.innerJoins.push({ type: 'LEFT JOIN', table: tableName, alias, onClause });
|
|
59
65
|
return this;
|
|
60
66
|
}
|
|
61
67
|
innerJoinRaw(table, alias, onClause) {
|
|
@@ -66,12 +72,12 @@ class JoinManyConditionBuilder extends join_condition_builder_1.JoinConditionBui
|
|
|
66
72
|
this.innerJoins.push({ type: 'LEFT JOIN', table, alias, onClause });
|
|
67
73
|
return this;
|
|
68
74
|
}
|
|
69
|
-
select(
|
|
70
|
-
if (
|
|
71
|
-
this.selectedColumns =
|
|
75
|
+
select(...args) {
|
|
76
|
+
if (args.length === 1 && Array.isArray(args[0])) {
|
|
77
|
+
this.selectedColumns = args[0];
|
|
72
78
|
}
|
|
73
79
|
else {
|
|
74
|
-
this.selectedColumns =
|
|
80
|
+
this.selectedColumns = args;
|
|
75
81
|
}
|
|
76
82
|
return this;
|
|
77
83
|
}
|
|
@@ -22,6 +22,7 @@ class SelectBuilder {
|
|
|
22
22
|
isDistinct = false;
|
|
23
23
|
lockingClause;
|
|
24
24
|
unionQueries = [];
|
|
25
|
+
includeExpressions = [];
|
|
25
26
|
columnResolver;
|
|
26
27
|
constructor(tableName, columns) {
|
|
27
28
|
this.tableName = tableName;
|
|
@@ -208,6 +209,10 @@ class SelectBuilder {
|
|
|
208
209
|
getStructuredJoins() {
|
|
209
210
|
return [...this.structuredJoins];
|
|
210
211
|
}
|
|
212
|
+
addIncludeExpression(alias, sql) {
|
|
213
|
+
this.includeExpressions.push({ alias, sql });
|
|
214
|
+
return this;
|
|
215
|
+
}
|
|
211
216
|
qualifyWhereConditions(conditions, tableRef) {
|
|
212
217
|
return conditions.map(cond => {
|
|
213
218
|
if (!cond.column) {
|
|
@@ -286,6 +291,9 @@ class SelectBuilder {
|
|
|
286
291
|
columns.push(`row_to_json(${pg_format_1.default.ident(join.alias)}.*) AS ${pg_format_1.default.ident(join.alias)}`);
|
|
287
292
|
}
|
|
288
293
|
}
|
|
294
|
+
for (const expr of this.includeExpressions) {
|
|
295
|
+
columns.push(`${expr.sql} AS ${pg_format_1.default.ident(expr.alias)}`);
|
|
296
|
+
}
|
|
289
297
|
const columnsSQL = columns.join(', ');
|
|
290
298
|
let query = 'SELECT';
|
|
291
299
|
if (this.distinctOnColumns.length > 0) {
|
|
@@ -318,7 +326,14 @@ class SelectBuilder {
|
|
|
318
326
|
query += ' WHERE ' + (0, condition_collector_1.buildConditionsSQL)(conditions);
|
|
319
327
|
}
|
|
320
328
|
if (this.groupByColumns.length > 0) {
|
|
321
|
-
|
|
329
|
+
const groupBySQL = this.groupByColumns.map(col => {
|
|
330
|
+
if (col.includes('.'))
|
|
331
|
+
return col;
|
|
332
|
+
if (hasJoins)
|
|
333
|
+
return (0, pg_format_1.default)('%I.%I', tableRef, col);
|
|
334
|
+
return pg_format_1.default.ident(col);
|
|
335
|
+
}).join(', ');
|
|
336
|
+
query += ' GROUP BY ' + groupBySQL;
|
|
322
337
|
}
|
|
323
338
|
if (this.havingConditions.length > 0) {
|
|
324
339
|
let havingConds = this.transformConditionColumns(this.havingConditions);
|
|
@@ -389,7 +404,14 @@ class SelectBuilder {
|
|
|
389
404
|
query += ' WHERE ' + (0, condition_collector_1.buildConditionsSQL)(conditions);
|
|
390
405
|
}
|
|
391
406
|
if (this.groupByColumns.length > 0) {
|
|
392
|
-
|
|
407
|
+
const groupBySQL = this.groupByColumns.map(col => {
|
|
408
|
+
if (col.includes('.'))
|
|
409
|
+
return col;
|
|
410
|
+
if (hasJoins)
|
|
411
|
+
return (0, pg_format_1.default)('%I.%I', tableRef, col);
|
|
412
|
+
return pg_format_1.default.ident(col);
|
|
413
|
+
}).join(', ');
|
|
414
|
+
query += ' GROUP BY ' + groupBySQL;
|
|
393
415
|
}
|
|
394
416
|
if (this.havingConditions.length > 0) {
|
|
395
417
|
let havingConds = this.transformConditionColumns(this.havingConditions);
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.AggregateFunctions = exports.SqlExpression = void 0;
|
|
4
|
+
const table_proxy_1 = require("./table-proxy.cjs");
|
|
5
|
+
class SqlExpression {
|
|
6
|
+
sql;
|
|
7
|
+
_isSqlExpression = true;
|
|
8
|
+
constructor(sql) {
|
|
9
|
+
this.sql = sql;
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
exports.SqlExpression = SqlExpression;
|
|
13
|
+
class AggregateFunctions {
|
|
14
|
+
count(ref) {
|
|
15
|
+
if (!ref)
|
|
16
|
+
return new SqlExpression('COUNT(*)');
|
|
17
|
+
return new SqlExpression(`COUNT(${(0, table_proxy_1.columnRefToSQL)(ref)})`);
|
|
18
|
+
}
|
|
19
|
+
countDistinct(ref) {
|
|
20
|
+
return new SqlExpression(`COUNT(DISTINCT ${(0, table_proxy_1.columnRefToSQL)(ref)})`);
|
|
21
|
+
}
|
|
22
|
+
sum(ref) {
|
|
23
|
+
return new SqlExpression(`SUM(${(0, table_proxy_1.columnRefToSQL)(ref)})`);
|
|
24
|
+
}
|
|
25
|
+
avg(ref) {
|
|
26
|
+
return new SqlExpression(`AVG(${(0, table_proxy_1.columnRefToSQL)(ref)})`);
|
|
27
|
+
}
|
|
28
|
+
min(ref) {
|
|
29
|
+
return new SqlExpression(`MIN(${(0, table_proxy_1.columnRefToSQL)(ref)})`);
|
|
30
|
+
}
|
|
31
|
+
max(ref) {
|
|
32
|
+
return new SqlExpression(`MAX(${(0, table_proxy_1.columnRefToSQL)(ref)})`);
|
|
33
|
+
}
|
|
34
|
+
raw(sql) {
|
|
35
|
+
return new SqlExpression(sql);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
exports.AggregateFunctions = AggregateFunctions;
|