relq 1.0.5 → 1.0.6

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.
Files changed (84) hide show
  1. package/dist/cjs/cli/commands/add.cjs +252 -12
  2. package/dist/cjs/cli/commands/commit.cjs +12 -1
  3. package/dist/cjs/cli/commands/export.cjs +25 -19
  4. package/dist/cjs/cli/commands/import.cjs +219 -100
  5. package/dist/cjs/cli/commands/init.cjs +86 -14
  6. package/dist/cjs/cli/commands/pull.cjs +104 -23
  7. package/dist/cjs/cli/commands/push.cjs +38 -3
  8. package/dist/cjs/cli/index.cjs +9 -1
  9. package/dist/cjs/cli/utils/ast/codegen/builder.cjs +297 -0
  10. package/dist/cjs/cli/utils/ast/codegen/constraints.cjs +185 -0
  11. package/dist/cjs/cli/utils/ast/codegen/defaults.cjs +311 -0
  12. package/dist/cjs/cli/utils/ast/codegen/index.cjs +24 -0
  13. package/dist/cjs/cli/utils/ast/codegen/type-map.cjs +116 -0
  14. package/dist/cjs/cli/utils/ast/codegen/utils.cjs +69 -0
  15. package/dist/cjs/cli/utils/ast/index.cjs +19 -0
  16. package/dist/cjs/cli/utils/ast/transformer/helpers.cjs +154 -0
  17. package/dist/cjs/cli/utils/ast/transformer/index.cjs +25 -0
  18. package/dist/cjs/cli/utils/ast/types.cjs +2 -0
  19. package/dist/cjs/cli/utils/ast-codegen.cjs +949 -0
  20. package/dist/cjs/cli/utils/ast-transformer.cjs +916 -0
  21. package/dist/cjs/cli/utils/change-tracker.cjs +50 -1
  22. package/dist/cjs/cli/utils/cli-utils.cjs +151 -0
  23. package/dist/cjs/cli/utils/fast-introspect.cjs +149 -23
  24. package/dist/cjs/cli/utils/pg-parser.cjs +1 -0
  25. package/dist/cjs/cli/utils/repo-manager.cjs +121 -4
  26. package/dist/cjs/cli/utils/schema-comparator.cjs +98 -14
  27. package/dist/cjs/cli/utils/schema-introspect.cjs +56 -19
  28. package/dist/cjs/cli/utils/snapshot-manager.cjs +0 -1
  29. package/dist/cjs/cli/utils/sql-generator.cjs +353 -64
  30. package/dist/cjs/cli/utils/type-generator.cjs +114 -15
  31. package/dist/cjs/core/relq-client.cjs +22 -6
  32. package/dist/cjs/schema-definition/column-types.cjs +149 -13
  33. package/dist/cjs/schema-definition/defaults.cjs +72 -0
  34. package/dist/cjs/schema-definition/index.cjs +15 -1
  35. package/dist/cjs/schema-definition/introspection.cjs +7 -3
  36. package/dist/cjs/schema-definition/pg-relations.cjs +169 -0
  37. package/dist/cjs/schema-definition/pg-view.cjs +30 -0
  38. package/dist/cjs/schema-definition/table-definition.cjs +110 -4
  39. package/dist/cjs/types/config-types.cjs +13 -4
  40. package/dist/cjs/utils/aws-dsql.cjs +177 -0
  41. package/dist/config.d.ts +146 -1
  42. package/dist/esm/cli/commands/add.js +250 -13
  43. package/dist/esm/cli/commands/commit.js +12 -1
  44. package/dist/esm/cli/commands/export.js +25 -19
  45. package/dist/esm/cli/commands/import.js +221 -102
  46. package/dist/esm/cli/commands/init.js +86 -14
  47. package/dist/esm/cli/commands/pull.js +106 -25
  48. package/dist/esm/cli/commands/push.js +39 -4
  49. package/dist/esm/cli/index.js +9 -1
  50. package/dist/esm/cli/utils/ast/codegen/builder.js +291 -0
  51. package/dist/esm/cli/utils/ast/codegen/constraints.js +176 -0
  52. package/dist/esm/cli/utils/ast/codegen/defaults.js +305 -0
  53. package/dist/esm/cli/utils/ast/codegen/index.js +6 -0
  54. package/dist/esm/cli/utils/ast/codegen/type-map.js +111 -0
  55. package/dist/esm/cli/utils/ast/codegen/utils.js +60 -0
  56. package/dist/esm/cli/utils/ast/index.js +3 -0
  57. package/dist/esm/cli/utils/ast/transformer/helpers.js +141 -0
  58. package/dist/esm/cli/utils/ast/transformer/index.js +2 -0
  59. package/dist/esm/cli/utils/ast/types.js +1 -0
  60. package/dist/esm/cli/utils/ast-codegen.js +945 -0
  61. package/dist/esm/cli/utils/ast-transformer.js +907 -0
  62. package/dist/esm/cli/utils/change-tracker.js +50 -1
  63. package/dist/esm/cli/utils/cli-utils.js +147 -0
  64. package/dist/esm/cli/utils/fast-introspect.js +149 -23
  65. package/dist/esm/cli/utils/pg-parser.js +1 -0
  66. package/dist/esm/cli/utils/repo-manager.js +114 -4
  67. package/dist/esm/cli/utils/schema-comparator.js +98 -14
  68. package/dist/esm/cli/utils/schema-introspect.js +56 -19
  69. package/dist/esm/cli/utils/snapshot-manager.js +0 -1
  70. package/dist/esm/cli/utils/sql-generator.js +353 -64
  71. package/dist/esm/cli/utils/type-generator.js +114 -15
  72. package/dist/esm/core/relq-client.js +23 -7
  73. package/dist/esm/schema-definition/column-types.js +146 -12
  74. package/dist/esm/schema-definition/defaults.js +69 -0
  75. package/dist/esm/schema-definition/index.js +3 -0
  76. package/dist/esm/schema-definition/introspection.js +7 -3
  77. package/dist/esm/schema-definition/pg-relations.js +161 -0
  78. package/dist/esm/schema-definition/pg-view.js +24 -0
  79. package/dist/esm/schema-definition/table-definition.js +110 -4
  80. package/dist/esm/types/config-types.js +12 -4
  81. package/dist/esm/utils/aws-dsql.js +139 -0
  82. package/dist/index.d.ts +159 -1
  83. package/dist/schema-builder.d.ts +1314 -32
  84. package/package.json +1 -1
@@ -0,0 +1,291 @@
1
+ import { toCamelCase, escapeString } from "./utils.js";
2
+ export const KNOWN_BUILDER_FUNCTIONS = {
3
+ 'setweight': 'setweight',
4
+ 'to_tsvector': 'toTsvector',
5
+ 'to_tsquery': 'toTsquery',
6
+ 'plainto_tsquery': 'plainToTsquery',
7
+ 'phraseto_tsquery': 'phraseToTsquery',
8
+ 'websearch_to_tsquery': 'websearchToTsquery',
9
+ 'ts_headline': 'tsHeadline',
10
+ 'ts_rank': 'tsRank',
11
+ 'ts_rank_cd': 'tsRankCd',
12
+ 'coalesce': 'coalesce',
13
+ 'nullif': 'nullif',
14
+ 'greatest': 'greatest',
15
+ 'least': 'least',
16
+ 'concat': 'concat',
17
+ 'concat_ws': 'concatWs',
18
+ 'lower': 'lower',
19
+ 'upper': 'upper',
20
+ 'trim': 'trim',
21
+ 'ltrim': 'ltrim',
22
+ 'rtrim': 'rtrim',
23
+ 'length': 'length',
24
+ 'substring': 'substring',
25
+ 'replace': 'replace',
26
+ 'regexp_replace': 'regexpReplace',
27
+ 'split_part': 'splitPart',
28
+ 'now': 'now',
29
+ 'current_timestamp': 'currentTimestamp',
30
+ 'current_date': 'currentDate',
31
+ 'current_time': 'currentTime',
32
+ 'date_trunc': 'dateTrunc',
33
+ 'date_part': 'datePart',
34
+ 'extract': 'extract',
35
+ 'age': 'age',
36
+ 'gen_random_uuid': 'genRandomUuid',
37
+ 'uuid_generate_v4': 'uuidGenerateV4',
38
+ 'jsonb_build_object': 'jsonbBuildObject',
39
+ 'jsonb_agg': 'jsonbAgg',
40
+ 'json_agg': 'jsonAgg',
41
+ 'array_agg': 'arrayAgg',
42
+ 'string_agg': 'stringAgg',
43
+ 'count': 'count',
44
+ 'sum': 'sum',
45
+ 'avg': 'avg',
46
+ 'min': 'min',
47
+ 'max': 'max',
48
+ 'abs': 'abs',
49
+ 'ceil': 'ceil',
50
+ 'floor': 'floor',
51
+ 'round': 'round',
52
+ 'sqrt': 'sqrt',
53
+ 'power': 'power',
54
+ 'mod': 'mod',
55
+ 'random': 'random',
56
+ };
57
+ export function mapFunctionToBuilder(funcName) {
58
+ return KNOWN_BUILDER_FUNCTIONS[funcName] || null;
59
+ }
60
+ export function astToBuilder(node, prefixOrOptions = 'g') {
61
+ const opts = typeof prefixOrOptions === 'string'
62
+ ? { prefix: prefixOrOptions }
63
+ : prefixOrOptions;
64
+ const { prefix = 'g', useCamelCase = false, useTableRef = false } = opts;
65
+ if (!node)
66
+ return "''";
67
+ if (node.FuncCall) {
68
+ const func = node.FuncCall;
69
+ const funcName = func.funcname?.map((n) => n.String?.sval).filter(Boolean).join('.') || '';
70
+ const args = (func.args || []).map((a) => astToBuilder(a, opts));
71
+ const builderMethod = mapFunctionToBuilder(funcName.toLowerCase());
72
+ if (builderMethod) {
73
+ return `${prefix}.${builderMethod}(${args.join(', ')})`;
74
+ }
75
+ throw new Error(`Unsupported function in generated expression: "${funcName}". Add this function to KNOWN_BUILDER_FUNCTIONS in astToBuilder.`);
76
+ }
77
+ if (node.CoalesceExpr) {
78
+ const args = (node.CoalesceExpr.args || []).map((a) => astToBuilder(a, opts));
79
+ return `${prefix}.coalesce(${args.join(', ')})`;
80
+ }
81
+ if (node.A_Expr) {
82
+ const expr = node.A_Expr;
83
+ const op = expr.name?.[0]?.String?.sval || '';
84
+ const left = astToBuilder(expr.lexpr, opts);
85
+ const right = astToBuilder(expr.rexpr, opts);
86
+ switch (op) {
87
+ case '||':
88
+ return `__CONCAT__[${left}, ${right}]`;
89
+ case '->>':
90
+ return `${prefix}.jsonbExtractText(${left}, ${right})`;
91
+ case '->':
92
+ return `${prefix}.jsonbExtract(${left}, ${right})`;
93
+ case '@@':
94
+ return `${prefix}.tsMatch(${left}, ${right})`;
95
+ case '+':
96
+ return `${prefix}.add(${left}, ${right})`;
97
+ case '-':
98
+ return `${prefix}.subtract(${left}, ${right})`;
99
+ case '*':
100
+ return `${prefix}.multiply(${left}, ${right})`;
101
+ case '/':
102
+ return `${prefix}.divide(${left}, ${right})`;
103
+ case '%':
104
+ return `${prefix}.mod(${left}, ${right})`;
105
+ case '=':
106
+ case '<>':
107
+ case '!=':
108
+ case '<':
109
+ case '>':
110
+ case '<=':
111
+ case '>=':
112
+ if (useTableRef) {
113
+ const methodMap = {
114
+ '=': 'eq', '<>': 'ne', '!=': 'neq',
115
+ '<': 'lt', '>': 'gt', '<=': 'lte', '>=': 'gte'
116
+ };
117
+ return `${left}.${methodMap[op]}(${right})`;
118
+ }
119
+ return `${prefix}.compare(${left}, '${op}', ${right})`;
120
+ case '~':
121
+ case '~*':
122
+ case '!~':
123
+ case '!~*':
124
+ return `${prefix}.regex(${left}, '${op}', ${right})`;
125
+ default:
126
+ throw new Error(`Unsupported operator in generated expression: "${op}". Add explicit handling for this operator in astToBuilder.`);
127
+ }
128
+ }
129
+ if (node.ColumnRef) {
130
+ const fields = (node.ColumnRef.fields || [])
131
+ .map((f) => f.String?.sval)
132
+ .filter(Boolean);
133
+ if (fields.length === 1) {
134
+ const colName = useCamelCase ? toCamelCase(fields[0]) : fields[0];
135
+ return `table.${colName}`;
136
+ }
137
+ else if (fields.length === 2) {
138
+ const colName = useCamelCase ? toCamelCase(fields[1]) : fields[1];
139
+ return `table.${colName}`;
140
+ }
141
+ const colName = useCamelCase ? toCamelCase(fields[fields.length - 1]) : fields[fields.length - 1];
142
+ return `table.${colName}`;
143
+ }
144
+ if (node.A_Const) {
145
+ const val = node.A_Const;
146
+ if (val.sval !== undefined) {
147
+ const s = val.sval?.sval ?? val.sval;
148
+ if (s === '' || s === ' ') {
149
+ return s === '' ? `${prefix}.asText()` : `${prefix}.asText(' ')`;
150
+ }
151
+ return `'${escapeString(String(s))}'`;
152
+ }
153
+ else if (val.ival !== undefined) {
154
+ const i = val.ival?.ival ?? val.ival;
155
+ return String(i);
156
+ }
157
+ else if (val.fval !== undefined) {
158
+ const f = val.fval?.fval ?? val.fval;
159
+ return String(f);
160
+ }
161
+ else if (val.boolval !== undefined) {
162
+ return val.boolval?.boolval ? 'true' : 'false';
163
+ }
164
+ return `${prefix}.asText()`;
165
+ }
166
+ if (node.TypeCast) {
167
+ const arg = astToBuilder(node.TypeCast.arg, opts);
168
+ const typeName = node.TypeCast.typeName?.names
169
+ ?.map((n) => n.String?.sval)
170
+ .filter(Boolean)
171
+ .join('.') || '';
172
+ if (typeName === 'regconfig' || typeName === 'pg_catalog.regconfig') {
173
+ return arg;
174
+ }
175
+ if (typeName === 'text' || typeName === 'pg_catalog.text') {
176
+ return `${prefix}.asText(${arg})`;
177
+ }
178
+ if (typeName === 'varchar' || typeName === 'pg_catalog.varchar' || typeName.includes('character varying')) {
179
+ return arg;
180
+ }
181
+ if (typeName === 'char' || typeName === 'bpchar' || typeName === 'pg_catalog.bpchar') {
182
+ return arg;
183
+ }
184
+ if (typeName === 'numeric' || typeName === 'pg_catalog.numeric') {
185
+ return arg;
186
+ }
187
+ if (typeName === 'int4' || typeName === 'integer' || typeName === 'pg_catalog.int4') {
188
+ return arg;
189
+ }
190
+ return `${prefix}.cast(${arg}, '${typeName}')`;
191
+ }
192
+ if (node.NullTest) {
193
+ const arg = astToBuilder(node.NullTest.arg, opts);
194
+ const isNull = node.NullTest.nulltesttype === 'IS_NULL';
195
+ return isNull ? `${prefix}.isNull(${arg})` : `${prefix}.isNotNull(${arg})`;
196
+ }
197
+ if (node.BoolExpr) {
198
+ const boolexpr = node.BoolExpr;
199
+ const args = (boolexpr.args || []).map((a) => astToBuilder(a, opts));
200
+ if (useTableRef && args.length >= 2) {
201
+ switch (boolexpr.boolop) {
202
+ case 'AND_EXPR': return args.reduce((acc, arg) => `${acc}.and(${arg})`);
203
+ case 'OR_EXPR': return args.reduce((acc, arg) => `${acc}.or(${arg})`);
204
+ case 'NOT_EXPR': return `${args[0]}.not()`;
205
+ }
206
+ }
207
+ switch (boolexpr.boolop) {
208
+ case 'AND_EXPR': return `${prefix}.and(${args.join(', ')})`;
209
+ case 'OR_EXPR': return `${prefix}.or(${args.join(', ')})`;
210
+ case 'NOT_EXPR': return `${prefix}.not(${args[0]})`;
211
+ }
212
+ }
213
+ if (node.CaseExpr) {
214
+ const caseExpr = node.CaseExpr;
215
+ let chain = `${prefix}.case()`;
216
+ for (const w of (caseExpr.args || [])) {
217
+ if (w.CaseWhen) {
218
+ const condition = astToBuilder(w.CaseWhen.expr, opts);
219
+ const result = astToBuilder(w.CaseWhen.result, opts);
220
+ chain += `.when(${condition}, ${result})`;
221
+ }
222
+ }
223
+ if (caseExpr.defresult) {
224
+ const defResult = astToBuilder(caseExpr.defresult, opts);
225
+ chain += `.else(${defResult})`;
226
+ }
227
+ else {
228
+ chain += '.end()';
229
+ }
230
+ return chain;
231
+ }
232
+ const nodeType = Object.keys(node)[0];
233
+ throw new Error(`Unsupported AST node type in generated expression: "${nodeType}". Add explicit handling for this node type in astToBuilder.`);
234
+ }
235
+ function extractConcatItems(expr) {
236
+ if (expr.startsWith('__CONCAT__[')) {
237
+ const inner = expr.slice('__CONCAT__['.length, -1);
238
+ const items = [];
239
+ let depth = 0;
240
+ let current = '';
241
+ let inQuote = false;
242
+ for (let i = 0; i < inner.length; i++) {
243
+ const c = inner[i];
244
+ if (c === "'" && inner[i - 1] !== '\\') {
245
+ inQuote = !inQuote;
246
+ }
247
+ if (!inQuote) {
248
+ if (c === '[' || c === '(')
249
+ depth++;
250
+ if (c === ']' || c === ')')
251
+ depth--;
252
+ if (c === ',' && depth === 0) {
253
+ items.push(current.trim());
254
+ current = '';
255
+ continue;
256
+ }
257
+ }
258
+ current += c;
259
+ }
260
+ if (current.trim())
261
+ items.push(current.trim());
262
+ return items.flatMap(item => extractConcatItems(item));
263
+ }
264
+ return [expr];
265
+ }
266
+ function cleanAllConcats(expr) {
267
+ let result = expr;
268
+ let iterations = 0;
269
+ const maxIterations = 50;
270
+ while (result.includes('__CONCAT__[') && iterations < maxIterations) {
271
+ const match = result.match(/__CONCAT__\[([^\[\]]*)\]/);
272
+ if (match) {
273
+ const replacement = `F.concat(${match[1]})`;
274
+ result = result.replace(match[0], replacement);
275
+ }
276
+ else {
277
+ break;
278
+ }
279
+ iterations++;
280
+ }
281
+ return result;
282
+ }
283
+ export function formatGeneratedExpression(expr) {
284
+ if (expr.startsWith('__CONCAT__[')) {
285
+ const items = extractConcatItems(expr);
286
+ const cleanedItems = items.map(item => cleanAllConcats(item));
287
+ return { isArray: true, items: cleanedItems };
288
+ }
289
+ const cleaned = cleanAllConcats(expr);
290
+ return { isArray: false, items: [cleaned] };
291
+ }
@@ -0,0 +1,176 @@
1
+ import { toCamelCase, escapeString, isBalanced, getComparisonMethod } from "./utils.js";
2
+ import { astToBuilder } from "./builder.js";
3
+ let needsSqlImport = false;
4
+ export function resetSqlImportFlag() {
5
+ needsSqlImport = false;
6
+ }
7
+ export function getSqlImportNeeded() {
8
+ return needsSqlImport;
9
+ }
10
+ export function extractEnumValues(expression) {
11
+ const anyMatch = expression.match(/ANY\s*\(\s*\(?(?:ARRAY)?\s*\[([^\]]+)\]/i);
12
+ if (anyMatch) {
13
+ const valuesStr = anyMatch[1];
14
+ const values = valuesStr.match(/'([^']+)'/g)?.map(v => v.replace(/'/g, '').replace(/::.*$/, '')) || [];
15
+ return values.length > 0 ? values : null;
16
+ }
17
+ const inMatch = expression.match(/\bIN\s*\(([^)]+)\)/i);
18
+ if (inMatch) {
19
+ const valuesStr = inMatch[1];
20
+ const values = valuesStr.match(/'([^']+)'/g)?.map(v => v.replace(/'/g, '')) || [];
21
+ return values.length > 0 ? values : null;
22
+ }
23
+ return null;
24
+ }
25
+ export function generateConstraintCode(constraints, useCamelCase) {
26
+ const lines = [];
27
+ for (const c of constraints) {
28
+ const cols = c.columns.map(col => `table.${useCamelCase ? toCamelCase(col) : col}`);
29
+ const commentOpt = c.comment ? `, comment: '${escapeString(c.comment)}'` : '';
30
+ if (c.type === 'PRIMARY KEY' && c.columns.length > 1) {
31
+ const nameOpt = c.name ? `name: '${c.name}', ` : '';
32
+ lines.push(` constraint.primaryKey({ ${nameOpt}columns: [${cols.join(', ')}]${commentOpt} })`);
33
+ }
34
+ else if (c.type === 'UNIQUE' && c.columns.length > 1) {
35
+ const nameOpt = c.name ? `name: '${c.name}', ` : '';
36
+ lines.push(` constraint.unique({ ${nameOpt}columns: [${cols.join(', ')}]${commentOpt} })`);
37
+ }
38
+ else if (c.type === 'EXCLUDE' && c.expression) {
39
+ const escapedExpr = escapeString(c.expression);
40
+ let line = ` constraint.exclude('${c.name}', '${escapedExpr}')`;
41
+ if (c.comment) {
42
+ line += `.comment('${escapeString(c.comment)}')`;
43
+ }
44
+ lines.push(line);
45
+ }
46
+ }
47
+ return lines;
48
+ }
49
+ export function generateForeignKeysOption(constraints) {
50
+ const fks = constraints.filter(c => c.type === 'FOREIGN KEY' && c.references);
51
+ if (fks.length === 0)
52
+ return null;
53
+ const entries = fks.map(fk => {
54
+ const cols = fk.columns.map(c => `'${c}'`).join(', ');
55
+ const refCols = fk.references.columns.map(c => `'${c}'`).join(', ');
56
+ let entry = ` { columns: [${cols}], references: { table: '${fk.references.table}', columns: [${refCols}] }`;
57
+ if (fk.references.onDelete && fk.references.onDelete !== 'NO ACTION') {
58
+ entry += `, onDelete: '${fk.references.onDelete}'`;
59
+ }
60
+ if (fk.references.onUpdate && fk.references.onUpdate !== 'NO ACTION') {
61
+ entry += `, onUpdate: '${fk.references.onUpdate}'`;
62
+ }
63
+ entry += ' }';
64
+ return entry;
65
+ });
66
+ return ` foreignKeys: [\n${entries.join(',\n')},\n ]`;
67
+ }
68
+ export function generateCheckConstraintsOption(constraints, useCamelCase) {
69
+ const checks = constraints.filter(c => c.type === 'CHECK' && c.expression && !extractEnumValues(c.expression));
70
+ if (checks.length === 0)
71
+ return null;
72
+ const lines = checks.map(c => {
73
+ if (c.expressionAst) {
74
+ try {
75
+ const typedExpr = astToBuilder(c.expressionAst, {
76
+ prefix: 'table',
77
+ useCamelCase,
78
+ useTableRef: true
79
+ });
80
+ return ` check.constraint('${c.name}', ${typedExpr})`;
81
+ }
82
+ catch {
83
+ const typedExpr = convertCheckExpressionToTyped(c.expression, useCamelCase);
84
+ return ` check.constraint('${c.name}', ${typedExpr})`;
85
+ }
86
+ }
87
+ const typedExpr = convertCheckExpressionToTyped(c.expression, useCamelCase);
88
+ return ` check.constraint('${c.name}', ${typedExpr})`;
89
+ });
90
+ return ` checkConstraints: (table, check) => [\n${lines.join(',\n')},\n ]`;
91
+ }
92
+ export function convertCheckExpressionToTyped(expression, useCamelCase) {
93
+ const trimmed = expression.trim();
94
+ let expr = trimmed;
95
+ while (expr.startsWith('(') && expr.endsWith(')')) {
96
+ const inner = expr.slice(1, -1);
97
+ if (isBalanced(inner)) {
98
+ expr = inner;
99
+ }
100
+ else {
101
+ break;
102
+ }
103
+ }
104
+ const comparisonMatch = expr.match(/^(\w+)\s*(>=?|<=?|<>|!=|=)\s*\(?\s*(-?\d+(?:\.\d+)?)\s*\)?(?:::[\w\s]+)?$/i);
105
+ if (comparisonMatch) {
106
+ const [, column, op, value] = comparisonMatch;
107
+ const colName = useCamelCase ? toCamelCase(column) : column;
108
+ const method = getComparisonMethod(op);
109
+ return `table.${colName}.${method}(${value})`;
110
+ }
111
+ const isNotNullMatch = expr.match(/^(\w+)\s+IS\s+NOT\s+NULL$/i);
112
+ if (isNotNullMatch) {
113
+ const colName = useCamelCase ? toCamelCase(isNotNullMatch[1]) : isNotNullMatch[1];
114
+ return `table.${colName}.isNotNull()`;
115
+ }
116
+ const isNullMatch = expr.match(/^(\w+)\s+IS\s+NULL$/i);
117
+ if (isNullMatch) {
118
+ const colName = useCamelCase ? toCamelCase(isNullMatch[1]) : isNullMatch[1];
119
+ return `table.${colName}.isNull()`;
120
+ }
121
+ const betweenMatch = expr.match(/^(\w+)\s+BETWEEN\s+(-?\d+(?:\.\d+)?)\s+AND\s+(-?\d+(?:\.\d+)?)$/i);
122
+ if (betweenMatch) {
123
+ const [, column, min, max] = betweenMatch;
124
+ const colName = useCamelCase ? toCamelCase(column) : column;
125
+ return `table.${colName}.between(${min}, ${max})`;
126
+ }
127
+ const stringCompareMatch = expr.match(/^(\w+)\s*(=|<>|!=)\s*'([^']*)'(?:::[\w\s]+)?$/i);
128
+ if (stringCompareMatch) {
129
+ const [, column, op, value] = stringCompareMatch;
130
+ const colName = useCamelCase ? toCamelCase(column) : column;
131
+ const method = getComparisonMethod(op);
132
+ return `table.${colName}.${method}('${value}')`;
133
+ }
134
+ const colCompareMatch = expr.match(/^(\w+)\s*(>=?|<=?|<>|!=|=)\s*(\w+)$/i);
135
+ if (colCompareMatch) {
136
+ const [, col1, op, col2] = colCompareMatch;
137
+ const colName1 = useCamelCase ? toCamelCase(col1) : col1;
138
+ const colName2 = useCamelCase ? toCamelCase(col2) : col2;
139
+ const method = getComparisonMethod(op);
140
+ return `table.${colName1}.${method}(table.${colName2})`;
141
+ }
142
+ const lengthMatch = expr.match(/^length\((\w+)\)\s*(>=?|<=?|<>|!=|=)\s*(\d+)$/i);
143
+ if (lengthMatch) {
144
+ const [, column, op, value] = lengthMatch;
145
+ const colName = useCamelCase ? toCamelCase(column) : column;
146
+ const method = getComparisonMethod(op);
147
+ return `table.${colName}.asText().length().${method}(${value})`;
148
+ }
149
+ const andRangeMatch = expr.match(/^\((\w+)\s*(>=?)\s*(-?\d+(?:\.\d+)?)\)\s+AND\s+\(\1\s*(<=?)\s*(-?\d+(?:\.\d+)?)\)$/i);
150
+ if (andRangeMatch) {
151
+ const [, column, op1, min, op2, max] = andRangeMatch;
152
+ const colName = useCamelCase ? toCamelCase(column) : column;
153
+ const method1 = getComparisonMethod(op1);
154
+ const method2 = getComparisonMethod(op2);
155
+ return `table.${colName}.${method1}(${min}).and(table.${colName}.${method2}(${max}))`;
156
+ }
157
+ const andOpenRangeMatch = expr.match(/^\((\w+)\s*(>)\s*(-?\d+(?:\.\d+)?)\)\s+AND\s+\(\1\s*(<)\s*(-?\d+(?:\.\d+)?)\)$/i);
158
+ if (andOpenRangeMatch) {
159
+ const [, column, op1, min, op2, max] = andOpenRangeMatch;
160
+ const colName = useCamelCase ? toCamelCase(column) : column;
161
+ const method1 = getComparisonMethod(op1);
162
+ const method2 = getComparisonMethod(op2);
163
+ return `table.${colName}.${method1}(${min}).and(table.${colName}.${method2}(${max}))`;
164
+ }
165
+ const andMatch = expr.match(/^\((.+?)\)\s+AND\s+\((.+)\)$/i);
166
+ if (andMatch) {
167
+ const [, cond1, cond2] = andMatch;
168
+ const typed1 = convertCheckExpressionToTyped(`(${cond1})`, useCamelCase);
169
+ const typed2 = convertCheckExpressionToTyped(`(${cond2})`, useCamelCase);
170
+ if (!typed1.startsWith('sql`') && !typed2.startsWith('sql`')) {
171
+ return `${typed1}.and(${typed2})`;
172
+ }
173
+ }
174
+ needsSqlImport = true;
175
+ return `sql\`${escapeString(expression)}\``;
176
+ }