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.
@@ -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
- const filteredDiff = (0, schema_diff_1.filterDiff)(diff, rawPatterns);
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) || !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
- const filteredDiff = (0, schema_diff_1.filterDiff)(diff, ignorePatterns.map(pat => pat.raw));
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) || !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
- return value && typeof value === 'object' &&
90
- value.$type === 'relations';
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 upperType = type.toUpperCase();
182
- return typeMap[upperType] || type.toLowerCase();
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, callback) {
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
- (0, select_joins_1.executeTypeSafeJoinMany)(this.joinCtx, 'JOIN', tableOrAlias, callback);
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, callback) {
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
- (0, select_joins_1.executeTypeSafeJoinMany)(this.joinCtx, 'LEFT JOIN', tableOrAlias, callback);
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
- const sqlName = columnDef?.$columnName || prop;
126
- return `"${alias}"."${sqlName}" AS "${prop}"`;
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(columns) {
102
- if (columns === '*') {
103
- this.selectedColumns = undefined;
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 = columns;
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(table, callback) {
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 { proxy: innerProxy, tableName } = this.proxyCreator(table, table);
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: table, onClause });
49
+ this.innerJoins.push({ type: 'JOIN', table: tableName, alias, onClause });
47
50
  return this;
48
51
  }
49
- leftInnerJoin(table, callback) {
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 { proxy: innerProxy, tableName } = this.proxyCreator(table, table);
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: table, onClause });
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(columns) {
70
- if (columns === '*') {
71
- this.selectedColumns = undefined;
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 = columns;
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
- query += ' GROUP BY ' + this.groupByColumns.map(col => pg_format_1.default.ident(col)).join(', ');
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
- query += ' GROUP BY ' + this.groupByColumns.map(col => pg_format_1.default.ident(col)).join(', ');
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;