relq 1.0.29 → 1.0.31

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.
@@ -1,112 +1,384 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.KNOWN_BUILDER_FUNCTIONS = void 0;
3
+ exports.CHAINABLE_FUNCTIONS = exports.KNOWN_BUILDER_FUNCTIONS = void 0;
4
4
  exports.mapFunctionToBuilder = mapFunctionToBuilder;
5
+ exports.isChainableFunction = isChainableFunction;
5
6
  exports.astToBuilder = astToBuilder;
6
7
  exports.formatGeneratedExpression = formatGeneratedExpression;
7
8
  const utils_1 = require("./utils.cjs");
8
9
  exports.KNOWN_BUILDER_FUNCTIONS = {
9
- 'setweight': 'setweight',
10
- 'to_tsvector': 'toTsvector',
11
- 'to_tsquery': 'toTsquery',
12
- 'plainto_tsquery': 'plainToTsquery',
13
- 'phraseto_tsquery': 'phraseToTsquery',
14
- 'websearch_to_tsquery': 'websearchToTsquery',
15
- 'ts_headline': 'tsHeadline',
16
- 'ts_rank': 'tsRank',
17
- 'ts_rank_cd': 'tsRankCd',
18
10
  'coalesce': 'coalesce',
19
11
  'nullif': 'nullif',
20
- 'greatest': 'greatest',
21
- 'least': 'least',
22
- 'concat': 'concat',
23
- 'concat_ws': 'concatWs',
24
12
  'lower': 'lower',
25
13
  'upper': 'upper',
26
14
  'trim': 'trim',
27
15
  'ltrim': 'ltrim',
28
16
  'rtrim': 'rtrim',
17
+ 'btrim': 'btrim',
18
+ 'concat': 'concat',
19
+ 'concat_ws': 'concatWs',
29
20
  'length': 'length',
21
+ 'char_length': 'length',
22
+ 'character_length': 'length',
23
+ 'octet_length': 'octetLength',
24
+ 'bit_length': 'bitLength',
30
25
  'substring': 'substring',
26
+ 'substr': 'substring',
31
27
  'replace': 'replace',
32
- 'regexp_replace': 'regexpReplace',
28
+ 'lpad': 'lpad',
29
+ 'rpad': 'rpad',
30
+ 'left': 'left',
31
+ 'right': 'right',
32
+ 'reverse': 'reverse',
33
+ 'repeat': 'repeat',
34
+ 'initcap': 'initcap',
35
+ 'ascii': 'ascii',
36
+ 'chr': 'chr',
37
+ 'position': 'position',
38
+ 'strpos': 'position',
39
+ 'overlay': 'overlay',
40
+ 'translate': 'translate',
33
41
  'split_part': 'splitPart',
42
+ 'regexp_replace': 'regexpReplace',
43
+ 'regexp_match': 'regexpMatch',
44
+ 'regexp_matches': 'regexpMatches',
45
+ 'format': 'format',
46
+ 'quote_ident': 'quoteIdent',
47
+ 'quote_literal': 'quoteLiteral',
48
+ 'quote_nullable': 'quoteNullable',
49
+ 'encode': 'encode',
50
+ 'decode': 'decode',
51
+ 'md5': 'md5',
52
+ 'sha256': 'sha256',
53
+ 'sha512': 'sha512',
54
+ 'digest': 'digest',
55
+ 'abs': 'abs',
56
+ 'ceil': 'ceil',
57
+ 'ceiling': 'ceil',
58
+ 'floor': 'floor',
59
+ 'round': 'round',
60
+ 'trunc': 'trunc',
61
+ 'truncate': 'trunc',
62
+ 'sign': 'sign',
63
+ 'sqrt': 'sqrt',
64
+ 'cbrt': 'cbrt',
65
+ 'exp': 'exp',
66
+ 'ln': 'ln',
67
+ 'log': 'log',
68
+ 'log10': 'log10',
69
+ 'power': 'power',
70
+ 'pow': 'power',
71
+ 'mod': 'mod',
72
+ 'degrees': 'degrees',
73
+ 'radians': 'radians',
74
+ 'pi': 'pi',
75
+ 'sin': 'sin',
76
+ 'cos': 'cos',
77
+ 'tan': 'tan',
78
+ 'asin': 'asin',
79
+ 'acos': 'acos',
80
+ 'atan': 'atan',
81
+ 'atan2': 'atan2',
82
+ 'sinh': 'sinh',
83
+ 'cosh': 'cosh',
84
+ 'tanh': 'tanh',
85
+ 'asinh': 'asinh',
86
+ 'acosh': 'acosh',
87
+ 'atanh': 'atanh',
88
+ 'factorial': 'factorial',
89
+ 'gcd': 'gcd',
90
+ 'lcm': 'lcm',
91
+ 'width_bucket': 'widthBucket',
92
+ 'random': 'random',
93
+ 'setseed': 'setseed',
94
+ 'greatest': 'greatest',
95
+ 'least': 'least',
96
+ 'json_typeof': 'jsonTypeof',
97
+ 'jsonb_typeof': 'jsonbTypeof',
98
+ 'json_array_length': 'jsonArrayLength',
99
+ 'jsonb_array_length': 'jsonbArrayLength',
100
+ 'jsonb_object_keys': 'jsonbKeys',
101
+ 'json_object_keys': 'jsonKeys',
102
+ 'jsonb_pretty': 'jsonbPretty',
103
+ 'jsonb_strip_nulls': 'jsonbStripNulls',
104
+ 'to_json': 'toJson',
105
+ 'to_jsonb': 'toJsonb',
106
+ 'row_to_json': 'rowToJson',
107
+ 'json_build_object': 'jsonBuildObject',
108
+ 'jsonb_build_object': 'jsonbBuildObject',
109
+ 'json_build_array': 'jsonBuildArray',
110
+ 'jsonb_build_array': 'jsonbBuildArray',
111
+ 'json_agg': 'jsonAgg',
112
+ 'jsonb_agg': 'jsonbAgg',
113
+ 'json_object_agg': 'jsonObjectAgg',
114
+ 'jsonb_object_agg': 'jsonbObjectAgg',
115
+ 'jsonb_set': 'jsonbSet',
116
+ 'jsonb_insert': 'jsonbInsert',
117
+ 'jsonb_path_query': 'jsonbPathQuery',
118
+ 'jsonb_path_query_array': 'jsonbPathQueryArray',
119
+ 'jsonb_path_query_first': 'jsonbPathQueryFirst',
120
+ 'jsonb_path_exists': 'jsonbPathExists',
121
+ 'array_length': 'arrayLength',
122
+ 'array_position': 'arrayPosition',
123
+ 'array_positions': 'arrayPositions',
124
+ 'array_dims': 'arrayDims',
125
+ 'array_lower': 'arrayLower',
126
+ 'array_upper': 'arrayUpper',
127
+ 'array_ndims': 'arrayNDims',
128
+ 'array_to_string': 'arrayToString',
129
+ 'string_to_array': 'stringToArray',
130
+ 'array_append': 'arrayAppend',
131
+ 'array_prepend': 'arrayPrepend',
132
+ 'array_cat': 'arrayCat',
133
+ 'array_remove': 'arrayRemove',
134
+ 'array_replace': 'arrayReplace',
135
+ 'unnest': 'unnest',
136
+ 'cardinality': 'cardinality',
137
+ 'array_agg': 'arrayAgg',
138
+ 'to_tsvector': 'toTsvector',
139
+ 'setweight': 'setWeight',
140
+ 'to_tsquery': 'toTsquery',
141
+ 'plainto_tsquery': 'plainToTsquery',
142
+ 'phraseto_tsquery': 'phraseToTsquery',
143
+ 'websearch_to_tsquery': 'websearchToTsquery',
144
+ 'strip': 'tsStrip',
145
+ 'numnode': 'numNode',
146
+ 'querytree': 'queryTree',
147
+ 'ts_rank': 'tsRank',
148
+ 'ts_rank_cd': 'tsRankCd',
149
+ 'ts_headline': 'tsHeadline',
150
+ 'ts_rewrite': 'tsRewrite',
151
+ 'ts_filter': 'tsFilter',
152
+ 'ts_delete': 'tsDelete',
153
+ 'tsvector_to_array': 'tsvectorToArray',
154
+ 'extract': 'extract',
155
+ 'date_part': 'datePart',
156
+ 'date_trunc': 'dateTrunc',
157
+ 'age': 'age',
158
+ 'isfinite': 'isfinite',
159
+ 'make_date': 'makeDate',
160
+ 'make_time': 'makeTime',
161
+ 'make_timestamp': 'makeTimestamp',
162
+ 'make_timestamptz': 'makeTimestamptz',
163
+ 'make_interval': 'makeInterval',
34
164
  'now': 'now',
35
165
  'current_timestamp': 'currentTimestamp',
36
166
  'current_date': 'currentDate',
37
167
  'current_time': 'currentTime',
38
- 'date_trunc': 'dateTrunc',
39
- 'date_part': 'datePart',
40
- 'extract': 'extract',
41
- 'age': 'age',
168
+ 'localtime': 'localtime',
169
+ 'localtimestamp': 'localtimestamp',
170
+ 'clock_timestamp': 'clockTimestamp',
171
+ 'statement_timestamp': 'statementTimestamp',
172
+ 'transaction_timestamp': 'transactionTimestamp',
173
+ 'timeofday': 'timeofday',
174
+ 'to_char': 'toChar',
175
+ 'to_date': 'toDate',
176
+ 'to_timestamp': 'toTimestamp',
42
177
  'gen_random_uuid': 'genRandomUuid',
43
178
  'uuid_generate_v4': 'uuidGenerateV4',
44
- 'jsonb_build_object': 'jsonbBuildObject',
45
- 'jsonb_agg': 'jsonbAgg',
46
- 'json_agg': 'jsonAgg',
47
- 'array_agg': 'arrayAgg',
48
- 'string_agg': 'stringAgg',
179
+ 'uuid_generate_v1': 'uuidGenerateV1',
49
180
  'count': 'count',
50
181
  'sum': 'sum',
51
182
  'avg': 'avg',
52
183
  'min': 'min',
53
184
  'max': 'max',
54
- 'abs': 'abs',
55
- 'ceil': 'ceil',
56
- 'floor': 'floor',
57
- 'round': 'round',
58
- 'sqrt': 'sqrt',
59
- 'power': 'power',
60
- 'mod': 'mod',
61
- 'random': 'random',
185
+ 'string_agg': 'stringAgg',
186
+ 'bool_and': 'boolAnd',
187
+ 'bool_or': 'boolOr',
188
+ 'every': 'every',
189
+ 'bit_and': 'bitAnd',
190
+ 'bit_or': 'bitOr',
191
+ 'inet_client_addr': 'inetClientAddr',
192
+ 'inet_server_addr': 'inetServerAddr',
193
+ 'host': 'host',
194
+ 'hostmask': 'hostmask',
195
+ 'netmask': 'netmask',
196
+ 'network': 'network',
197
+ 'broadcast': 'broadcast',
198
+ 'masklen': 'masklen',
199
+ 'family': 'family',
200
+ 'area': 'area',
201
+ 'center': 'center',
202
+ 'diameter': 'diameter',
203
+ 'height': 'height',
204
+ 'width': 'width',
205
+ 'isclosed': 'isclosed',
206
+ 'isopen': 'isopen',
207
+ 'npoints': 'npoints',
208
+ 'pclose': 'pclose',
209
+ 'popen': 'popen',
210
+ 'radius': 'radius',
211
+ 'lower_bound': 'lowerBound',
212
+ 'upper_bound': 'upperBound',
213
+ 'isempty': 'isempty',
214
+ 'lower_inc': 'lowerInc',
215
+ 'upper_inc': 'upperInc',
216
+ 'lower_inf': 'lowerInf',
217
+ 'upper_inf': 'upperInf',
218
+ 'range_merge': 'rangeMerge',
62
219
  };
63
220
  function mapFunctionToBuilder(funcName) {
64
221
  return exports.KNOWN_BUILDER_FUNCTIONS[funcName] || null;
65
222
  }
223
+ exports.CHAINABLE_FUNCTIONS = new Set([
224
+ 'lower', 'upper', 'trim', 'ltrim', 'rtrim', 'btrim',
225
+ 'length', 'char_length', 'character_length',
226
+ 'left', 'right', 'reverse', 'repeat', 'initcap',
227
+ 'substring', 'substr', 'replace', 'translate',
228
+ 'lpad', 'rpad', 'ascii', 'md5', 'sha256', 'sha512',
229
+ 'encode', 'decode', 'quote_ident', 'quote_literal', 'quote_nullable',
230
+ 'coalesce', 'nullif',
231
+ 'json_typeof', 'jsonb_typeof',
232
+ 'json_array_length', 'jsonb_array_length',
233
+ 'jsonb_object_keys', 'json_object_keys',
234
+ 'jsonb_pretty', 'jsonb_strip_nulls',
235
+ 'to_json', 'to_jsonb',
236
+ 'array_length', 'array_position', 'array_positions',
237
+ 'array_dims', 'array_lower', 'array_upper', 'array_ndims',
238
+ 'array_to_string', 'array_append', 'array_prepend',
239
+ 'array_cat', 'array_remove', 'array_replace',
240
+ 'unnest', 'cardinality',
241
+ 'to_tsvector', 'setweight', 'strip',
242
+ 'to_tsquery', 'plainto_tsquery', 'phraseto_tsquery', 'websearch_to_tsquery',
243
+ 'ts_rank', 'ts_rank_cd', 'ts_headline',
244
+ 'numnode', 'querytree', 'ts_rewrite', 'ts_filter', 'ts_delete',
245
+ 'abs', 'ceil', 'ceiling', 'floor', 'round', 'trunc', 'truncate', 'sign',
246
+ 'sqrt', 'cbrt', 'exp', 'ln', 'log', 'log10',
247
+ 'sin', 'cos', 'tan', 'asin', 'acos', 'atan',
248
+ 'sinh', 'cosh', 'tanh', 'degrees', 'radians',
249
+ 'factorial',
250
+ 'extract', 'date_part', 'date_trunc', 'age', 'isfinite',
251
+ ]);
252
+ function isChainableFunction(funcName) {
253
+ return exports.CHAINABLE_FUNCTIONS.has(funcName.toLowerCase());
254
+ }
255
+ function isChainableNode(node) {
256
+ if (!node)
257
+ return false;
258
+ if (node.ColumnRef)
259
+ return true;
260
+ if (node.FuncCall)
261
+ return true;
262
+ if (node.CoalesceExpr)
263
+ return true;
264
+ if (node.TypeCast)
265
+ return isChainableNode(node.TypeCast.arg);
266
+ if (node.A_Expr)
267
+ return isChainableNode(node.A_Expr.lexpr) || isChainableNode(node.A_Expr.rexpr);
268
+ if (node.CaseExpr)
269
+ return true;
270
+ if (node.A_Const)
271
+ return false;
272
+ return false;
273
+ }
274
+ const SECOND_ARG_CHAINABLE_FUNCTIONS = new Set([
275
+ 'to_tsvector',
276
+ 'to_tsquery',
277
+ 'plainto_tsquery',
278
+ 'phraseto_tsquery',
279
+ 'websearch_to_tsquery',
280
+ 'setweight',
281
+ ]);
66
282
  function astToBuilder(node, prefixOrOptions = 'g') {
67
283
  const opts = typeof prefixOrOptions === 'string'
68
284
  ? { prefix: prefixOrOptions }
69
285
  : prefixOrOptions;
70
- const { prefix = 'g', useCamelCase = false, useTableRef = false } = opts;
286
+ const { prefix = 'g', useCamelCase = false, useTableRef = false, chainable = false } = opts;
71
287
  if (!node)
72
288
  return "''";
73
289
  if (node.FuncCall) {
74
290
  const func = node.FuncCall;
75
291
  const funcName = func.funcname?.map((n) => n.String?.sval).filter(Boolean).join('.') || '';
76
- const args = (func.args || []).map((a) => astToBuilder(a, opts));
77
- const builderMethod = mapFunctionToBuilder(funcName.toLowerCase());
292
+ const funcNameLower = funcName.toLowerCase();
293
+ const rawArgs = func.args || [];
294
+ const args = rawArgs.map((a) => astToBuilder(a, opts));
295
+ const builderMethod = mapFunctionToBuilder(funcNameLower);
78
296
  if (builderMethod) {
297
+ if (chainable && isChainableFunction(funcName) && args.length > 0) {
298
+ if (SECOND_ARG_CHAINABLE_FUNCTIONS.has(funcNameLower) && args.length >= 2) {
299
+ const firstArgIsChainable = isChainableNode(rawArgs[0]);
300
+ const secondArgIsChainable = isChainableNode(rawArgs[1]);
301
+ if (!firstArgIsChainable && secondArgIsChainable) {
302
+ const [configArg, baseArg, ...restArgs] = args;
303
+ if (restArgs.length > 0) {
304
+ return `${baseArg}.${builderMethod}(${configArg}, ${restArgs.join(', ')})`;
305
+ }
306
+ return `${baseArg}.${builderMethod}(${configArg})`;
307
+ }
308
+ }
309
+ const firstArgIsChainable = isChainableNode(rawArgs[0]);
310
+ if (firstArgIsChainable) {
311
+ const [firstArg, ...restArgs] = args;
312
+ if (restArgs.length > 0) {
313
+ return `${firstArg}.${builderMethod}(${restArgs.join(', ')})`;
314
+ }
315
+ return `${firstArg}.${builderMethod}()`;
316
+ }
317
+ }
79
318
  return `${prefix}.${builderMethod}(${args.join(', ')})`;
80
319
  }
81
- throw new Error(`Unsupported function in generated expression: "${funcName}". Add this function to KNOWN_BUILDER_FUNCTIONS in astToBuilder.`);
320
+ throw new Error(`Unsupported function in generated expression: "${funcName}". Add this function to KNOWN_BUILDER_FUNCTIONS in src/cli/utils/ast/codegen/builder.ts`);
82
321
  }
83
322
  if (node.CoalesceExpr) {
84
- const args = (node.CoalesceExpr.args || []).map((a) => astToBuilder(a, opts));
323
+ const rawArgs = node.CoalesceExpr.args || [];
324
+ const args = rawArgs.map((a) => astToBuilder(a, opts));
325
+ if (chainable && args.length > 0 && isChainableNode(rawArgs[0])) {
326
+ const [firstArg, ...restArgs] = args;
327
+ return `${firstArg}.coalesce(${restArgs.join(', ')})`;
328
+ }
85
329
  return `${prefix}.coalesce(${args.join(', ')})`;
86
330
  }
87
331
  if (node.A_Expr) {
88
332
  const expr = node.A_Expr;
89
333
  const op = expr.name?.[0]?.String?.sval || '';
334
+ const leftIsChainable = isChainableNode(expr.lexpr);
90
335
  const left = astToBuilder(expr.lexpr, opts);
91
336
  const right = astToBuilder(expr.rexpr, opts);
92
337
  switch (op) {
93
338
  case '||':
339
+ if (chainable && leftIsChainable) {
340
+ return `${left}.concat(${right})`;
341
+ }
94
342
  return `__CONCAT__[${left}, ${right}]`;
95
343
  case '->>':
344
+ if (chainable && leftIsChainable) {
345
+ return `${left}.jsonbExtractText(${right})`;
346
+ }
96
347
  return `${prefix}.jsonbExtractText(${left}, ${right})`;
97
348
  case '->':
349
+ if (chainable && leftIsChainable) {
350
+ return `${left}.jsonbExtract(${right})`;
351
+ }
98
352
  return `${prefix}.jsonbExtract(${left}, ${right})`;
99
353
  case '@@':
354
+ if (chainable && leftIsChainable) {
355
+ return `${left}.tsMatch(${right})`;
356
+ }
100
357
  return `${prefix}.tsMatch(${left}, ${right})`;
101
358
  case '+':
359
+ if (chainable && leftIsChainable) {
360
+ return `${left}.add(${right})`;
361
+ }
102
362
  return `${prefix}.add(${left}, ${right})`;
103
363
  case '-':
364
+ if (chainable && leftIsChainable) {
365
+ return `${left}.subtract(${right})`;
366
+ }
104
367
  return `${prefix}.subtract(${left}, ${right})`;
105
368
  case '*':
369
+ if (chainable && leftIsChainable) {
370
+ return `${left}.multiply(${right})`;
371
+ }
106
372
  return `${prefix}.multiply(${left}, ${right})`;
107
373
  case '/':
374
+ if (chainable && leftIsChainable) {
375
+ return `${left}.divide(${right})`;
376
+ }
108
377
  return `${prefix}.divide(${left}, ${right})`;
109
378
  case '%':
379
+ if (chainable && leftIsChainable) {
380
+ return `${left}.mod(${right})`;
381
+ }
110
382
  return `${prefix}.mod(${left}, ${right})`;
111
383
  case '=':
112
384
  case '<>':
@@ -115,9 +387,9 @@ function astToBuilder(node, prefixOrOptions = 'g') {
115
387
  case '>':
116
388
  case '<=':
117
389
  case '>=':
118
- if (useTableRef) {
390
+ if ((useTableRef || chainable) && leftIsChainable) {
119
391
  const methodMap = {
120
- '=': 'eq', '<>': 'ne', '!=': 'neq',
392
+ '=': 'eq', '<>': 'ne', '!=': 'ne',
121
393
  '<': 'lt', '>': 'gt', '<=': 'lte', '>=': 'gte'
122
394
  };
123
395
  return `${left}.${methodMap[op]}(${right})`;
@@ -127,6 +399,14 @@ function astToBuilder(node, prefixOrOptions = 'g') {
127
399
  case '~*':
128
400
  case '!~':
129
401
  case '!~*':
402
+ if (chainable && leftIsChainable) {
403
+ const flags = op.includes('*') ? 'i' : '';
404
+ const negated = op.startsWith('!');
405
+ if (negated) {
406
+ return `${left}.matches(${right}${flags ? `, '${flags}'` : ''}).not()`;
407
+ }
408
+ return `${left}.matches(${right}${flags ? `, '${flags}'` : ''})`;
409
+ }
130
410
  return `${prefix}.regex(${left}, '${op}', ${right})`;
131
411
  default:
132
412
  throw new Error(`Unsupported operator in generated expression: "${op}". Add explicit handling for this operator in astToBuilder.`);
@@ -138,22 +418,19 @@ function astToBuilder(node, prefixOrOptions = 'g') {
138
418
  .filter(Boolean);
139
419
  if (fields.length === 1) {
140
420
  const colName = useCamelCase ? (0, utils_1.toCamelCase)(fields[0]) : fields[0];
141
- return `table.${colName}`;
421
+ return `${prefix}.${colName}`;
142
422
  }
143
423
  else if (fields.length === 2) {
144
424
  const colName = useCamelCase ? (0, utils_1.toCamelCase)(fields[1]) : fields[1];
145
- return `table.${colName}`;
425
+ return `${prefix}.${colName}`;
146
426
  }
147
427
  const colName = useCamelCase ? (0, utils_1.toCamelCase)(fields[fields.length - 1]) : fields[fields.length - 1];
148
- return `table.${colName}`;
428
+ return `${prefix}.${colName}`;
149
429
  }
150
430
  if (node.A_Const) {
151
431
  const val = node.A_Const;
152
432
  if (val.sval !== undefined) {
153
433
  const s = val.sval?.sval ?? val.sval;
154
- if (s === '' || s === ' ') {
155
- return s === '' ? `${prefix}.asText()` : `${prefix}.asText(' ')`;
156
- }
157
434
  return `'${(0, utils_1.escapeString)(String(s))}'`;
158
435
  }
159
436
  else if (val.ival !== undefined) {
@@ -167,9 +444,11 @@ function astToBuilder(node, prefixOrOptions = 'g') {
167
444
  else if (val.boolval !== undefined) {
168
445
  return val.boolval?.boolval ? 'true' : 'false';
169
446
  }
170
- return `${prefix}.asText()`;
447
+ return "''";
171
448
  }
172
449
  if (node.TypeCast) {
450
+ const argIsChainable = isChainableNode(node.TypeCast.arg);
451
+ const argIsLiteral = node.TypeCast.arg?.A_Const !== undefined;
173
452
  const arg = astToBuilder(node.TypeCast.arg, opts);
174
453
  const typeName = node.TypeCast.typeName?.names
175
454
  ?.map((n) => n.String?.sval)
@@ -179,6 +458,12 @@ function astToBuilder(node, prefixOrOptions = 'g') {
179
458
  return arg;
180
459
  }
181
460
  if (typeName === 'text' || typeName === 'pg_catalog.text') {
461
+ if (argIsLiteral) {
462
+ return arg;
463
+ }
464
+ if (chainable && argIsChainable) {
465
+ return `${arg}.asText()`;
466
+ }
182
467
  return `${prefix}.asText(${arg})`;
183
468
  }
184
469
  if (typeName === 'varchar' || typeName === 'pg_catalog.varchar' || typeName.includes('character varying')) {
@@ -193,11 +478,18 @@ function astToBuilder(node, prefixOrOptions = 'g') {
193
478
  if (typeName === 'int4' || typeName === 'integer' || typeName === 'pg_catalog.int4') {
194
479
  return arg;
195
480
  }
481
+ if (chainable && argIsChainable) {
482
+ return `${arg}.cast('${typeName}')`;
483
+ }
196
484
  return `${prefix}.cast(${arg}, '${typeName}')`;
197
485
  }
198
486
  if (node.NullTest) {
487
+ const argIsChainable = isChainableNode(node.NullTest.arg);
199
488
  const arg = astToBuilder(node.NullTest.arg, opts);
200
489
  const isNull = node.NullTest.nulltesttype === 'IS_NULL';
490
+ if (chainable && argIsChainable) {
491
+ return isNull ? `${arg}.isNull()` : `${arg}.isNotNull()`;
492
+ }
201
493
  return isNull ? `${prefix}.isNull(${arg})` : `${prefix}.isNotNull(${arg})`;
202
494
  }
203
495
  if (node.BoolExpr) {
@@ -20,6 +20,52 @@ function generateTrackingId(prefix) {
20
20
  const base = (Date.now().toString(36) + trackingIdCounter.toString(36)).slice(-5);
21
21
  return prefix + base.padStart(5, '0');
22
22
  }
23
+ function formatGeneratedExpr(expr, indent) {
24
+ if (expr.length <= 100)
25
+ return expr;
26
+ const indentStr = ' '.repeat(indent);
27
+ const breakMethods = ['setWeight', 'toTsvector', 'concat'];
28
+ let result = '';
29
+ let i = 0;
30
+ let depth = 0;
31
+ while (i < expr.length) {
32
+ const char = expr[i];
33
+ if (char === '(')
34
+ depth++;
35
+ else if (char === ')')
36
+ depth--;
37
+ if (char === '.' && depth === 0 && i > 0) {
38
+ for (const method of breakMethods) {
39
+ if (expr.substring(i + 1, i + 1 + method.length + 1).startsWith(method + '(')) {
40
+ let parenDepth = 0;
41
+ let j = i + 1 + method.length;
42
+ while (j < expr.length) {
43
+ if (expr[j] === '(')
44
+ parenDepth++;
45
+ else if (expr[j] === ')') {
46
+ parenDepth--;
47
+ if (parenDepth === 0) {
48
+ result += expr.substring(i, j + 1);
49
+ i = j + 1;
50
+ if (i < expr.length && expr[i] === '.') {
51
+ result += '\n' + indentStr;
52
+ }
53
+ break;
54
+ }
55
+ }
56
+ j++;
57
+ }
58
+ break;
59
+ }
60
+ }
61
+ }
62
+ if (i < expr.length) {
63
+ result += expr[i];
64
+ i++;
65
+ }
66
+ }
67
+ return result || expr;
68
+ }
23
69
  function resetTrackingIdCounter() {
24
70
  trackingIdCounter = 0;
25
71
  }
@@ -173,10 +219,6 @@ function generateColumnCode(col, useCamelCase, enumNames, domainNames, checkOver
173
219
  const defaultVal = (0, defaults_1.formatDefaultValue)(col.defaultValue, col.type);
174
220
  line += `.default(${defaultVal})`;
175
221
  }
176
- if (col.isGenerated && col.generatedExpression) {
177
- const escapedExpr = col.generatedExpression.replace(/`/g, '\\`');
178
- line += `.generatedAlwaysAs(F => F.raw(\`${escapedExpr}\`))`;
179
- }
180
222
  if (!col.isNullable && !col.isPrimaryKey) {
181
223
  line += '.notNull()';
182
224
  }
@@ -488,6 +530,38 @@ function generateTableCode(table, useCamelCase, enumNames, domainNames) {
488
530
  if (checkConstraintsOption) {
489
531
  optionParts.push(checkConstraintsOption);
490
532
  }
533
+ const generatedCols = table.columns.filter(col => col.isGenerated && col.generatedExpression);
534
+ if (generatedCols.length > 0) {
535
+ const generatedLines = [];
536
+ for (const col of generatedCols) {
537
+ const colName = useCamelCase ? (0, utils_1.toCamelCase)(col.name) : col.name;
538
+ const ast = col.generatedExpressionAst;
539
+ if (ast) {
540
+ try {
541
+ const builderExpr = (0, builder_1.astToBuilder)(ast, {
542
+ prefix: 't',
543
+ useCamelCase,
544
+ useTableRef: true,
545
+ chainable: true,
546
+ });
547
+ const formattedExpr = formatGeneratedExpr(builderExpr, 16);
548
+ generatedLines.push(` As.on(t.${colName}).as(${formattedExpr})`);
549
+ }
550
+ catch (err) {
551
+ throw new Error(`Failed to generate typed expression for generated column "${col.name}": ${err instanceof Error ? err.message : String(err)}\n` +
552
+ `SQL expression: ${col.generatedExpression}\n` +
553
+ `Hint: Add missing function to KNOWN_BUILDER_FUNCTIONS in src/cli/utils/ast/codegen/builder.ts`);
554
+ }
555
+ }
556
+ else {
557
+ throw new Error(`Missing generatedExpressionAst for column "${col.name}". ` +
558
+ `The SQL expression "${col.generatedExpression}" cannot be converted to typed builder.`);
559
+ }
560
+ }
561
+ if (generatedLines.length > 0) {
562
+ optionParts.push(` generatedAs: (t, As) => [\n${generatedLines.join(',\n')},\n ]`);
563
+ }
564
+ }
491
565
  if (table.indexes.length > 0) {
492
566
  const indexLines = table.indexes.map(idx => generateIndexCode(idx, useCamelCase));
493
567
  optionParts.push(` indexes: (table, index) => [\n${indexLines.join(',\n')},\n ]`);
@@ -304,8 +304,8 @@ function columnToAST(col, schemaKey) {
304
304
  isUnique: config.$unique || false,
305
305
  hasDefault: config.$default !== undefined,
306
306
  defaultValue: config.$default?.toString(),
307
- isGenerated: !!config.$generatedAlwaysAs,
308
- generatedExpression: config.$generatedAlwaysAs?.toString(),
307
+ isGenerated: !!config.$generated,
308
+ generatedExpression: config.$generated?.expression,
309
309
  isArray: config.$isArray || false,
310
310
  arrayDimensions: config.$arrayDimensions,
311
311
  comment: config.$comment,