metal-orm 1.0.8 → 1.0.10
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/README.md +341 -146
- package/dist/decorators/index.cjs +2564 -0
- package/dist/decorators/index.cjs.map +1 -0
- package/dist/decorators/index.d.cts +53 -0
- package/dist/decorators/index.d.ts +53 -0
- package/dist/decorators/index.js +2530 -0
- package/dist/decorators/index.js.map +1 -0
- package/dist/index.cjs +4227 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +701 -0
- package/dist/index.d.ts +701 -0
- package/dist/index.js +4131 -0
- package/dist/index.js.map +1 -0
- package/dist/select-654m4qy8.d.cts +1522 -0
- package/dist/select-654m4qy8.d.ts +1522 -0
- package/package.json +27 -20
- package/src/codegen/typescript.ts +405 -393
- package/src/core/ast/aggregate-functions.ts +30 -0
- package/src/core/ast/builders.ts +43 -0
- package/src/core/ast/expression-builders.ts +310 -0
- package/src/core/ast/expression-nodes.ts +211 -0
- package/src/core/ast/expression-visitor.ts +99 -0
- package/src/core/ast/expression.ts +5 -0
- package/src/{utils → core/ast}/join-node.ts +20 -20
- package/src/{ast → core/ast}/join.ts +18 -18
- package/src/{ast → core/ast}/query.ts +113 -113
- package/src/core/ast/window-functions.ts +140 -0
- package/src/{dialect → core/dialect}/abstract.ts +94 -94
- package/src/{dialect → core/dialect}/mssql/index.ts +31 -31
- package/src/{dialect → core/dialect}/mysql/index.ts +31 -31
- package/src/{dialect → core/dialect}/postgres/index.ts +45 -45
- package/src/{dialect → core/dialect}/sqlite/index.ts +45 -45
- package/src/{constants → core/sql}/sql-operator-config.ts +39 -39
- package/src/decorators/bootstrap.ts +126 -0
- package/src/decorators/column.ts +78 -0
- package/src/decorators/entity.ts +36 -0
- package/src/decorators/index.ts +4 -0
- package/src/decorators/relations.ts +107 -0
- package/src/global.d.ts +1 -0
- package/src/index.ts +22 -22
- package/src/orm/db-executor.ts +11 -0
- package/src/orm/domain-event-bus.ts +52 -0
- package/src/{runtime → orm}/entity-meta.ts +52 -52
- package/src/orm/entity-metadata.ts +140 -0
- package/src/{runtime → orm}/entity.ts +252 -252
- package/src/{runtime → orm}/execute.ts +36 -36
- package/src/{runtime → orm}/hydration.ts +103 -103
- package/src/orm/identity-map.ts +37 -0
- package/src/{runtime → orm}/lazy-batch.ts +205 -205
- package/src/orm/orm-context.ts +154 -0
- package/src/orm/relation-change-processor.ts +140 -0
- package/src/{runtime → orm}/relations/belongs-to.ts +92 -92
- package/src/{runtime → orm}/relations/has-many.ts +111 -111
- package/src/{runtime → orm}/relations/many-to-many.ts +149 -149
- package/src/orm/runtime-types.ts +39 -0
- package/src/orm/transaction-runner.ts +17 -0
- package/src/orm/unit-of-work.ts +232 -0
- package/src/{builder/operations → query-builder}/column-selector.ts +78 -78
- package/src/{builder → query-builder}/delete-query-state.ts +38 -42
- package/src/{builder → query-builder}/delete.ts +46 -57
- package/src/{builder → query-builder}/hydration-manager.ts +87 -87
- package/src/{builder → query-builder}/hydration-planner.ts +182 -182
- package/src/{builder → query-builder}/insert-query-state.ts +51 -62
- package/src/{builder → query-builder}/insert.ts +48 -59
- package/src/{builder → query-builder}/query-ast-service.ts +208 -226
- package/src/{utils → query-builder}/raw-column-parser.ts +32 -32
- package/src/{builder → query-builder}/relation-conditions.ts +112 -112
- package/src/{builder/operations → query-builder}/relation-manager.ts +82 -82
- package/src/{builder → query-builder}/relation-projection-helper.ts +101 -101
- package/src/{builder → query-builder}/relation-service.ts +284 -284
- package/src/{builder → query-builder}/relation-types.ts +21 -21
- package/src/{builder → query-builder}/relation-utils.ts +12 -12
- package/src/{builder → query-builder}/select-query-builder-deps.ts +112 -94
- package/src/{builder → query-builder}/select-query-state.ts +179 -179
- package/src/{builder → query-builder}/select.ts +78 -69
- package/src/{builder → query-builder}/update-query-state.ts +55 -59
- package/src/{builder → query-builder}/update.ts +50 -61
- package/src/schema/column.ts +25 -25
- package/src/schema/relation.ts +116 -116
- package/src/schema/table.ts +34 -34
- package/src/schema/types.ts +76 -76
- package/.github/workflows/publish-metal-orm.yml +0 -38
- package/ROADMAP.md +0 -125
- package/docs/CHANGES.md +0 -104
- package/docs/advanced-features.md +0 -176
- package/docs/api-reference.md +0 -31
- package/docs/dml-operations.md +0 -156
- package/docs/getting-started.md +0 -171
- package/docs/hydration.md +0 -115
- package/docs/index.md +0 -36
- package/docs/multi-dialect-support.md +0 -59
- package/docs/query-builder.md +0 -135
- package/docs/runtime.md +0 -105
- package/docs/schema-definition.md +0 -112
- package/metadata.json +0 -5
- package/playground/api/playground-api.ts +0 -94
- package/playground/index.html +0 -15
- package/playground/src/App.css +0 -1
- package/playground/src/App.tsx +0 -114
- package/playground/src/components/CodeDisplay.tsx +0 -43
- package/playground/src/components/QueryExecutor.tsx +0 -189
- package/playground/src/components/ResultsTable.tsx +0 -67
- package/playground/src/components/ResultsTabs.tsx +0 -105
- package/playground/src/components/ScenarioList.tsx +0 -56
- package/playground/src/components/logo.svg +0 -45
- package/playground/src/data/scenarios.ts +0 -2
- package/playground/src/main.tsx +0 -9
- package/playground/src/services/PlaygroundApiService.ts +0 -60
- package/postcss.config.cjs +0 -5
- package/sql_sql-ansi-cheatsheet-2025.md +0 -264
- package/src/ast/expression.ts +0 -658
- package/src/builder/operations/cte-manager.ts +0 -34
- package/src/builder/operations/filter-manager.ts +0 -68
- package/src/builder/operations/join-manager.ts +0 -36
- package/src/builder/operations/pagination-manager.ts +0 -36
- package/src/playground/features/playground/api/types.ts +0 -16
- package/src/playground/features/playground/clients/MockClient.ts +0 -17
- package/src/playground/features/playground/clients/SqliteClient.ts +0 -57
- package/src/playground/features/playground/common/IDatabaseClient.ts +0 -10
- package/src/playground/features/playground/data/scenarios/aggregation.ts +0 -36
- package/src/playground/features/playground/data/scenarios/basics.ts +0 -25
- package/src/playground/features/playground/data/scenarios/edge_cases.ts +0 -57
- package/src/playground/features/playground/data/scenarios/filtering.ts +0 -94
- package/src/playground/features/playground/data/scenarios/hydration.ts +0 -27
- package/src/playground/features/playground/data/scenarios/index.ts +0 -29
- package/src/playground/features/playground/data/scenarios/ordering.ts +0 -25
- package/src/playground/features/playground/data/scenarios/pagination.ts +0 -16
- package/src/playground/features/playground/data/scenarios/relationships.ts +0 -75
- package/src/playground/features/playground/data/scenarios/types.ts +0 -70
- package/src/playground/features/playground/data/schema.ts +0 -91
- package/src/playground/features/playground/data/seed.ts +0 -104
- package/src/playground/features/playground/services/QueryExecutionService.ts +0 -121
- package/src/runtime/orm-context.ts +0 -539
- package/tests/belongs-to-many.test.ts +0 -57
- package/tests/between.test.ts +0 -43
- package/tests/case-expression.test.ts +0 -58
- package/tests/complex-exists.test.ts +0 -230
- package/tests/cte.test.ts +0 -118
- package/tests/dml.test.ts +0 -206
- package/tests/exists.test.ts +0 -127
- package/tests/like.test.ts +0 -33
- package/tests/orm-runtime.test.ts +0 -254
- package/tests/postgres.test.ts +0 -30
- package/tests/right-join.test.ts +0 -89
- package/tests/subquery-having.test.ts +0 -193
- package/tests/window-function.test.ts +0 -151
- package/tsconfig.json +0 -30
- package/tsup.config.ts +0 -10
- package/vite.config.ts +0 -22
- package/vitest.config.ts +0 -14
- /package/src/{constants → core/sql}/sql.ts +0 -0
- /package/src/{runtime → orm}/als.ts +0 -0
- /package/src/{utils → query-builder}/relation-alias.ts +0 -0
|
@@ -1,230 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect } from 'vitest';
|
|
2
|
-
import { SelectQueryBuilder } from '../src/builder/select';
|
|
3
|
-
import { SqliteDialect } from '../src/dialect/sqlite';
|
|
4
|
-
import { TableDef, defineTable } from '../src/schema/table';
|
|
5
|
-
import { col } from '../src/schema/column';
|
|
6
|
-
import { eq, exists, and } from '../src/ast/expression';
|
|
7
|
-
|
|
8
|
-
// Define test schema: Clientes, Pedidos and Fidelidade
|
|
9
|
-
const Clientes = defineTable('clientes', {
|
|
10
|
-
id: col.primaryKey(col.int()),
|
|
11
|
-
nome: col.varchar(255),
|
|
12
|
-
email: col.varchar(255),
|
|
13
|
-
}, {});
|
|
14
|
-
|
|
15
|
-
const Pedidos = defineTable('pedidos', {
|
|
16
|
-
id: col.primaryKey(col.int()),
|
|
17
|
-
cliente_id: col.int(),
|
|
18
|
-
data_pedido: col.varchar(50), // Usando varchar em vez de date
|
|
19
|
-
status: col.varchar(50),
|
|
20
|
-
total: col.int(),
|
|
21
|
-
}, {});
|
|
22
|
-
|
|
23
|
-
const Fidelidade = defineTable('fidelidade', {
|
|
24
|
-
id: col.primaryKey(col.int()),
|
|
25
|
-
cliente_id: col.int(),
|
|
26
|
-
status: col.varchar(50),
|
|
27
|
-
data_inicio: col.varchar(50), // Usando varchar em vez de date
|
|
28
|
-
}, {});
|
|
29
|
-
|
|
30
|
-
// Define relationships
|
|
31
|
-
Clientes.relations = {
|
|
32
|
-
pedidos: {
|
|
33
|
-
type: 'HAS_MANY',
|
|
34
|
-
target: Pedidos,
|
|
35
|
-
foreignKey: 'cliente_id',
|
|
36
|
-
localKey: 'id'
|
|
37
|
-
},
|
|
38
|
-
fidelidade: {
|
|
39
|
-
type: 'HAS_MANY',
|
|
40
|
-
target: Fidelidade,
|
|
41
|
-
foreignKey: 'cliente_id',
|
|
42
|
-
localKey: 'id'
|
|
43
|
-
}
|
|
44
|
-
};
|
|
45
|
-
|
|
46
|
-
Pedidos.relations = {
|
|
47
|
-
cliente: {
|
|
48
|
-
type: 'BELONGS_TO',
|
|
49
|
-
target: Clientes,
|
|
50
|
-
foreignKey: 'cliente_id',
|
|
51
|
-
localKey: 'id'
|
|
52
|
-
}
|
|
53
|
-
};
|
|
54
|
-
|
|
55
|
-
Fidelidade.relations = {
|
|
56
|
-
cliente: {
|
|
57
|
-
type: 'BELONGS_TO',
|
|
58
|
-
target: Clientes,
|
|
59
|
-
foreignKey: 'cliente_id',
|
|
60
|
-
localKey: 'id'
|
|
61
|
-
}
|
|
62
|
-
};
|
|
63
|
-
|
|
64
|
-
const dialect = new SqliteDialect();
|
|
65
|
-
|
|
66
|
-
describe('Complex EXISTS Query Support', () => {
|
|
67
|
-
it('should support EXISTS with date range AND another EXISTS', () => {
|
|
68
|
-
// Construindo a subquery para pedidos em 2024
|
|
69
|
-
const pedidosEm2024 = new SelectQueryBuilder(Pedidos)
|
|
70
|
-
.select({ dummy: col.int() }) // SELECT 1 (dummy)
|
|
71
|
-
.where(and(
|
|
72
|
-
eq(Pedidos.columns.cliente_id, { type: 'Column', table: 'clientes', name: 'id' }),
|
|
73
|
-
// BETWEEN não é suportado diretamente, então usamos >= e <=
|
|
74
|
-
and(
|
|
75
|
-
eq(Pedidos.columns.data_pedido, '2024-01-01'),
|
|
76
|
-
eq(Pedidos.columns.data_pedido, '2024-12-31')
|
|
77
|
-
)
|
|
78
|
-
));
|
|
79
|
-
|
|
80
|
-
// Construindo a subquery para fidelidade ativa
|
|
81
|
-
const fidelidadeAtiva = new SelectQueryBuilder(Fidelidade)
|
|
82
|
-
.select({ dummy: col.int() }) // SELECT 1 (dummy)
|
|
83
|
-
.where(and(
|
|
84
|
-
eq(Fidelidade.columns.cliente_id, { type: 'Column', table: 'clientes', name: 'id' }),
|
|
85
|
-
eq(Fidelidade.columns.status, 'Ativo')
|
|
86
|
-
));
|
|
87
|
-
|
|
88
|
-
// Consulta principal
|
|
89
|
-
const query = new SelectQueryBuilder(Clientes)
|
|
90
|
-
.select({ nome: Clientes.columns.nome })
|
|
91
|
-
.where(and(
|
|
92
|
-
exists(pedidosEm2024.getAST()),
|
|
93
|
-
exists(fidelidadeAtiva.getAST())
|
|
94
|
-
));
|
|
95
|
-
|
|
96
|
-
const compiled = query.compile(dialect);
|
|
97
|
-
const { sql, params } = compiled;
|
|
98
|
-
|
|
99
|
-
console.log('Generated SQL:', sql);
|
|
100
|
-
console.log('Parameters:', params);
|
|
101
|
-
|
|
102
|
-
expect(sql).toContain('EXISTS');
|
|
103
|
-
expect(sql).toContain('SELECT 1 FROM "pedidos"');
|
|
104
|
-
expect(sql).toContain('"pedidos"."cliente_id" = "clientes"."id"');
|
|
105
|
-
expect(sql).toContain('"pedidos"."data_pedido"');
|
|
106
|
-
expect(sql).toContain('EXISTS');
|
|
107
|
-
expect(sql).toContain('SELECT 1 FROM "fidelidade"');
|
|
108
|
-
expect(sql).toContain('"fidelidade"."cliente_id" = "clientes"."id"');
|
|
109
|
-
expect(sql).toContain('"fidelidade"."status" = ?');
|
|
110
|
-
expect(params).toContain('Ativo');
|
|
111
|
-
});
|
|
112
|
-
|
|
113
|
-
it('should support whereHas for both conditions', () => {
|
|
114
|
-
// Usando whereHas para simplificar a construção das subqueries EXISTS
|
|
115
|
-
const query = new SelectQueryBuilder(Clientes)
|
|
116
|
-
.select({ nome: Clientes.columns.nome })
|
|
117
|
-
.whereHas('pedidos', (pedidosQb) =>
|
|
118
|
-
pedidosQb.where(and(
|
|
119
|
-
eq(Pedidos.columns.data_pedido, '2024-01-01'),
|
|
120
|
-
eq(Pedidos.columns.data_pedido, '2024-12-31')
|
|
121
|
-
))
|
|
122
|
-
)
|
|
123
|
-
.whereHas('fidelidade', (fidelidadeQb) =>
|
|
124
|
-
fidelidadeQb.where(eq(Fidelidade.columns.status, 'Ativo'))
|
|
125
|
-
);
|
|
126
|
-
|
|
127
|
-
const compiled = query.compile(dialect);
|
|
128
|
-
const { sql, params } = compiled;
|
|
129
|
-
|
|
130
|
-
console.log('Generated SQL with whereHas:', sql);
|
|
131
|
-
console.log('Parameters:', params);
|
|
132
|
-
|
|
133
|
-
expect(sql).toContain('EXISTS');
|
|
134
|
-
expect(sql).toContain('SELECT 1 FROM "pedidos"');
|
|
135
|
-
expect(sql).toContain('"pedidos"."cliente_id" = "clientes"."id"');
|
|
136
|
-
expect(sql).toContain('"pedidos"."data_pedido"');
|
|
137
|
-
expect(sql).toContain('EXISTS');
|
|
138
|
-
expect(sql).toContain('SELECT 1 FROM "fidelidade"');
|
|
139
|
-
expect(sql).toContain('"fidelidade"."cliente_id" = "clientes"."id"');
|
|
140
|
-
expect(sql).toContain('"fidelidade"."status" = ?');
|
|
141
|
-
expect(params).toContain('Ativo');
|
|
142
|
-
});
|
|
143
|
-
|
|
144
|
-
it('should generate correct SQL structure for complex EXISTS query', () => {
|
|
145
|
-
// Teste para verificar se o SQL gerado tem a estrutura correta
|
|
146
|
-
const query = new SelectQueryBuilder(Clientes)
|
|
147
|
-
.select({ nome: Clientes.columns.nome })
|
|
148
|
-
.where(and(
|
|
149
|
-
exists({
|
|
150
|
-
type: 'SelectQuery',
|
|
151
|
-
from: { type: 'Table', name: 'pedidos' },
|
|
152
|
-
columns: [{ type: 'Column', table: 'pedidos', name: 'id' }],
|
|
153
|
-
joins: [],
|
|
154
|
-
where: {
|
|
155
|
-
type: 'LogicalExpression',
|
|
156
|
-
operator: 'AND',
|
|
157
|
-
operands: [
|
|
158
|
-
{
|
|
159
|
-
type: 'BinaryExpression',
|
|
160
|
-
left: { type: 'Column', table: 'pedidos', name: 'cliente_id' },
|
|
161
|
-
operator: '=',
|
|
162
|
-
right: { type: 'Column', table: 'clientes', name: 'id' }
|
|
163
|
-
},
|
|
164
|
-
{
|
|
165
|
-
type: 'LogicalExpression',
|
|
166
|
-
operator: 'AND',
|
|
167
|
-
operands: [
|
|
168
|
-
{
|
|
169
|
-
type: 'BinaryExpression',
|
|
170
|
-
left: { type: 'Column', table: 'pedidos', name: 'data_pedido' },
|
|
171
|
-
operator: '>=',
|
|
172
|
-
right: { type: 'Literal', value: '2024-01-01' }
|
|
173
|
-
},
|
|
174
|
-
{
|
|
175
|
-
type: 'BinaryExpression',
|
|
176
|
-
left: { type: 'Column', table: 'pedidos', name: 'data_pedido' },
|
|
177
|
-
operator: '<=',
|
|
178
|
-
right: { type: 'Literal', value: '2024-12-31' }
|
|
179
|
-
}
|
|
180
|
-
]
|
|
181
|
-
}
|
|
182
|
-
]
|
|
183
|
-
}
|
|
184
|
-
}),
|
|
185
|
-
exists({
|
|
186
|
-
type: 'SelectQuery',
|
|
187
|
-
from: { type: 'Table', name: 'fidelidade' },
|
|
188
|
-
columns: [{ type: 'Column', table: 'fidelidade', name: 'id' }],
|
|
189
|
-
joins: [],
|
|
190
|
-
where: {
|
|
191
|
-
type: 'LogicalExpression',
|
|
192
|
-
operator: 'AND',
|
|
193
|
-
operands: [
|
|
194
|
-
{
|
|
195
|
-
type: 'BinaryExpression',
|
|
196
|
-
left: { type: 'Column', table: 'fidelidade', name: 'cliente_id' },
|
|
197
|
-
operator: '=',
|
|
198
|
-
right: { type: 'Column', table: 'clientes', name: 'id' }
|
|
199
|
-
},
|
|
200
|
-
{
|
|
201
|
-
type: 'BinaryExpression',
|
|
202
|
-
left: { type: 'Column', table: 'fidelidade', name: 'status' },
|
|
203
|
-
operator: '=',
|
|
204
|
-
right: { type: 'Literal', value: 'Ativo' }
|
|
205
|
-
}
|
|
206
|
-
]
|
|
207
|
-
}
|
|
208
|
-
})
|
|
209
|
-
));
|
|
210
|
-
|
|
211
|
-
const compiled = query.compile(dialect);
|
|
212
|
-
const { sql, params } = compiled;
|
|
213
|
-
|
|
214
|
-
console.log('Generated SQL structure:', sql);
|
|
215
|
-
console.log('Parameters:', params);
|
|
216
|
-
|
|
217
|
-
// Verifica a estrutura geral da consulta
|
|
218
|
-
expect(sql).toContain('SELECT "clientes"."nome" AS "nome" FROM "clientes" WHERE');
|
|
219
|
-
expect(sql).toContain('EXISTS');
|
|
220
|
-
expect(sql).toContain('SELECT 1 FROM "pedidos"');
|
|
221
|
-
expect(sql).toContain('"pedidos"."cliente_id" = "clientes"."id"');
|
|
222
|
-
expect(sql).toContain('"pedidos"."data_pedido" >= ?');
|
|
223
|
-
expect(sql).toContain('"pedidos"."data_pedido" <= ?');
|
|
224
|
-
expect(sql).toContain('EXISTS');
|
|
225
|
-
expect(sql).toContain('SELECT 1 FROM "fidelidade"');
|
|
226
|
-
expect(sql).toContain('"fidelidade"."cliente_id" = "clientes"."id"');
|
|
227
|
-
expect(sql).toContain('"fidelidade"."status" = ?');
|
|
228
|
-
expect(params).toEqual(['2024-01-01', '2024-12-31', 'Ativo']);
|
|
229
|
-
});
|
|
230
|
-
});
|
package/tests/cte.test.ts
DELETED
|
@@ -1,118 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect } from 'vitest';
|
|
2
|
-
import { SelectQueryBuilder } from '../src/builder/select';
|
|
3
|
-
import { SqliteDialect } from '../src/dialect/sqlite';
|
|
4
|
-
import { MySqlDialect } from '../src/dialect/mysql';
|
|
5
|
-
import { SqlServerDialect } from '../src/dialect/mssql';
|
|
6
|
-
import { TableDef } from '../src/schema/table';
|
|
7
|
-
import { eq } from '../src/ast/expression';
|
|
8
|
-
|
|
9
|
-
const table = (name: string): TableDef => ({ name, columns: {}, relations: {} });
|
|
10
|
-
const col = (name: string, table?: string) => ({ type: 'Column', name, table: table || 'unknown' } as any);
|
|
11
|
-
const lit = (value: any) => ({ type: 'Literal', value } as any);
|
|
12
|
-
|
|
13
|
-
describe('CTE Support', () => {
|
|
14
|
-
const sqlite = new SqliteDialect();
|
|
15
|
-
const mysql = new MySqlDialect();
|
|
16
|
-
const mssql = new SqlServerDialect();
|
|
17
|
-
|
|
18
|
-
it('should generate a simple CTE', () => {
|
|
19
|
-
const users = table('users');
|
|
20
|
-
const cte = new SelectQueryBuilder(users)
|
|
21
|
-
.selectRaw('id', 'name')
|
|
22
|
-
.where(eq(col('id', 'users'), lit(1)));
|
|
23
|
-
|
|
24
|
-
const query = new SelectQueryBuilder(table('cte_users'))
|
|
25
|
-
.with('cte_users', cte)
|
|
26
|
-
.selectRaw('id', 'name');
|
|
27
|
-
|
|
28
|
-
expect(query.toSql(sqlite)).toBe('WITH "cte_users" AS (SELECT "users"."id", "users"."name" FROM "users" WHERE "users"."id" = ?) SELECT "cte_users"."id", "cte_users"."name" FROM "cte_users";');
|
|
29
|
-
});
|
|
30
|
-
|
|
31
|
-
it('should generate a recursive CTE', () => {
|
|
32
|
-
const numbers = table('numbers');
|
|
33
|
-
const cte = new SelectQueryBuilder(numbers)
|
|
34
|
-
.selectRaw('n')
|
|
35
|
-
.where(eq(col('n', 'numbers'), lit(1)));
|
|
36
|
-
|
|
37
|
-
// Recursive part usually involves UNION, but our builder might not support UNION yet?
|
|
38
|
-
// The task didn't mention UNION.
|
|
39
|
-
// But we can test the RECURSIVE keyword generation at least.
|
|
40
|
-
|
|
41
|
-
const query = new SelectQueryBuilder(table('cte_numbers'))
|
|
42
|
-
.withRecursive('cte_numbers', cte)
|
|
43
|
-
.selectRaw('n');
|
|
44
|
-
|
|
45
|
-
expect(query.toSql(sqlite)).toBe('WITH RECURSIVE "cte_numbers" AS (SELECT "numbers"."n" FROM "numbers" WHERE "numbers"."n" = ?) SELECT "cte_numbers"."n" FROM "cte_numbers";');
|
|
46
|
-
expect(query.toSql(mysql)).toBe('WITH RECURSIVE `cte_numbers` AS (SELECT `numbers`.`n` FROM `numbers` WHERE `numbers`.`n` = ?) SELECT `cte_numbers`.`n` FROM `cte_numbers`;');
|
|
47
|
-
// MSSQL should NOT have RECURSIVE
|
|
48
|
-
expect(query.toSql(mssql)).toBe('WITH [cte_numbers] AS (SELECT [numbers].[n] FROM [numbers] WHERE [numbers].[n] = @p1) SELECT [cte_numbers].[n] FROM [cte_numbers];');
|
|
49
|
-
});
|
|
50
|
-
|
|
51
|
-
it('should generate CTE with column aliases', () => {
|
|
52
|
-
const users = table('users');
|
|
53
|
-
const cte = new SelectQueryBuilder(users)
|
|
54
|
-
.selectRaw('id', 'name');
|
|
55
|
-
|
|
56
|
-
const query = new SelectQueryBuilder(table('cte_users'))
|
|
57
|
-
.with('cte_users', cte, ['user_id', 'user_name'])
|
|
58
|
-
.selectRaw('user_id');
|
|
59
|
-
|
|
60
|
-
expect(query.toSql(sqlite)).toBe('WITH "cte_users"("user_id", "user_name") AS (SELECT "users"."id", "users"."name" FROM "users") SELECT "cte_users"."user_id" FROM "cte_users";');
|
|
61
|
-
});
|
|
62
|
-
|
|
63
|
-
it('should support multiple CTEs', () => {
|
|
64
|
-
const users = table('users');
|
|
65
|
-
const orders = table('orders');
|
|
66
|
-
|
|
67
|
-
const cte1 = new SelectQueryBuilder(users).selectRaw('id');
|
|
68
|
-
const cte2 = new SelectQueryBuilder(orders).selectRaw('total');
|
|
69
|
-
|
|
70
|
-
const query = new SelectQueryBuilder(table('main'))
|
|
71
|
-
.with('u', cte1)
|
|
72
|
-
.with('o', cte2)
|
|
73
|
-
.selectRaw('u.id', 'o.total');
|
|
74
|
-
|
|
75
|
-
expect(query.toSql(sqlite)).toBe('WITH "u" AS (SELECT "users"."id" FROM "users"), "o" AS (SELECT "orders"."total" FROM "orders") SELECT "main"."u.id", "main"."o.total" FROM "main";');
|
|
76
|
-
});
|
|
77
|
-
|
|
78
|
-
it('should handle mixed recursive and non-recursive CTEs', () => {
|
|
79
|
-
const numbers = table('numbers');
|
|
80
|
-
const users = table('users');
|
|
81
|
-
|
|
82
|
-
const recursiveCte = new SelectQueryBuilder(numbers)
|
|
83
|
-
.selectRaw('n')
|
|
84
|
-
.where(eq(col('n', 'numbers'), lit(1)));
|
|
85
|
-
|
|
86
|
-
const normalCte = new SelectQueryBuilder(users)
|
|
87
|
-
.selectRaw('id', 'name');
|
|
88
|
-
|
|
89
|
-
const query = new SelectQueryBuilder(table('main'))
|
|
90
|
-
.withRecursive('recursive_numbers', recursiveCte)
|
|
91
|
-
.with('normal_users', normalCte)
|
|
92
|
-
.selectRaw('n', 'id');
|
|
93
|
-
|
|
94
|
-
// Should have "WITH RECURSIVE" once at the beginning, not per CTE
|
|
95
|
-
expect(query.toSql(sqlite)).toBe('WITH RECURSIVE "recursive_numbers" AS (SELECT "numbers"."n" FROM "numbers" WHERE "numbers"."n" = ?), "normal_users" AS (SELECT "users"."id", "users"."name" FROM "users") SELECT "main"."n", "main"."id" FROM "main";');
|
|
96
|
-
expect(query.toSql(mysql)).toBe('WITH RECURSIVE `recursive_numbers` AS (SELECT `numbers`.`n` FROM `numbers` WHERE `numbers`.`n` = ?), `normal_users` AS (SELECT `users`.`id`, `users`.`name` FROM `users`) SELECT `main`.`n`, `main`.`id` FROM `main`;');
|
|
97
|
-
|
|
98
|
-
// MSSQL should NOT have RECURSIVE keyword at all
|
|
99
|
-
expect(query.toSql(mssql)).toBe('WITH [recursive_numbers] AS (SELECT [numbers].[n] FROM [numbers] WHERE [numbers].[n] = @p1), [normal_users] AS (SELECT [users].[id], [users].[name] FROM [users]) SELECT [main].[n], [main].[id] FROM [main];');
|
|
100
|
-
});
|
|
101
|
-
|
|
102
|
-
it('should handle multiple non-recursive CTEs without RECURSIVE keyword', () => {
|
|
103
|
-
const users = table('users');
|
|
104
|
-
const orders = table('orders');
|
|
105
|
-
|
|
106
|
-
const cte1 = new SelectQueryBuilder(users).selectRaw('id');
|
|
107
|
-
const cte2 = new SelectQueryBuilder(orders).selectRaw('total');
|
|
108
|
-
|
|
109
|
-
const query = new SelectQueryBuilder(table('main'))
|
|
110
|
-
.with('u', cte1)
|
|
111
|
-
.with('o', cte2)
|
|
112
|
-
.selectRaw('id');
|
|
113
|
-
|
|
114
|
-
// Should NOT have RECURSIVE keyword
|
|
115
|
-
expect(query.toSql(sqlite)).toBe('WITH "u" AS (SELECT "users"."id" FROM "users"), "o" AS (SELECT "orders"."total" FROM "orders") SELECT "main"."id" FROM "main";');
|
|
116
|
-
expect(query.toSql(mysql)).toBe('WITH `u` AS (SELECT `users`.`id` FROM `users`), `o` AS (SELECT `orders`.`total` FROM `orders`) SELECT `main`.`id` FROM `main`;');
|
|
117
|
-
});
|
|
118
|
-
});
|
package/tests/dml.test.ts
DELETED
|
@@ -1,206 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect } from 'vitest';
|
|
2
|
-
import { InsertQueryBuilder, UpdateQueryBuilder, DeleteQueryBuilder } from '../src';
|
|
3
|
-
import { Dialect } from '../src/dialect/abstract';
|
|
4
|
-
import { MySqlDialect } from '../src/dialect/mysql';
|
|
5
|
-
import { PostgresDialect } from '../src/dialect/postgres';
|
|
6
|
-
import { SqliteDialect } from '../src/dialect/sqlite';
|
|
7
|
-
import { SqlServerDialect } from '../src/dialect/mssql';
|
|
8
|
-
import { Users } from '../src/playground/features/playground/data/schema';
|
|
9
|
-
import { eq } from '../src/ast/expression';
|
|
10
|
-
import type { ColumnDef } from '../src/schema/column';
|
|
11
|
-
|
|
12
|
-
type Row = {
|
|
13
|
-
name: string;
|
|
14
|
-
role: string;
|
|
15
|
-
};
|
|
16
|
-
|
|
17
|
-
interface DialectCase {
|
|
18
|
-
name: string;
|
|
19
|
-
dialect: Dialect;
|
|
20
|
-
placeholder: (index: number) => string;
|
|
21
|
-
supportsReturning: boolean;
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
const rowOrder: (keyof Row)[] = ['name', 'role'];
|
|
25
|
-
const insertRows: Row[] = [
|
|
26
|
-
{ name: 'alice', role: 'admin' },
|
|
27
|
-
{ name: 'bob', role: 'user' }
|
|
28
|
-
];
|
|
29
|
-
|
|
30
|
-
const returningColumns: ColumnDef[] = [Users.columns.id, Users.columns.name];
|
|
31
|
-
const columnColumns: ColumnDef[] = [Users.columns.name, Users.columns.role];
|
|
32
|
-
|
|
33
|
-
const dialectCases: DialectCase[] = [
|
|
34
|
-
{
|
|
35
|
-
name: 'MySQL',
|
|
36
|
-
dialect: new MySqlDialect(),
|
|
37
|
-
placeholder: () => '?',
|
|
38
|
-
supportsReturning: false
|
|
39
|
-
},
|
|
40
|
-
{
|
|
41
|
-
name: 'Postgres',
|
|
42
|
-
dialect: new PostgresDialect(),
|
|
43
|
-
placeholder: () => '?',
|
|
44
|
-
supportsReturning: true
|
|
45
|
-
},
|
|
46
|
-
{
|
|
47
|
-
name: 'SQLite',
|
|
48
|
-
dialect: new SqliteDialect(),
|
|
49
|
-
placeholder: () => '?',
|
|
50
|
-
supportsReturning: true
|
|
51
|
-
},
|
|
52
|
-
{
|
|
53
|
-
name: 'SQL Server',
|
|
54
|
-
dialect: new SqlServerDialect(),
|
|
55
|
-
placeholder: index => `@p${index}`,
|
|
56
|
-
supportsReturning: false
|
|
57
|
-
}
|
|
58
|
-
];
|
|
59
|
-
|
|
60
|
-
const qualifyColumn = (dialect: Dialect, column: ColumnDef): string =>
|
|
61
|
-
`${dialect.quoteIdentifier(column.table || Users.name)}.${dialect.quoteIdentifier(column.name)}`;
|
|
62
|
-
|
|
63
|
-
const buildColumnList = (dialect: Dialect, columns: ColumnDef[]): string =>
|
|
64
|
-
columns.map(column => qualifyColumn(dialect, column)).join(', ');
|
|
65
|
-
|
|
66
|
-
const buildReturningClause = (dialect: Dialect, columns: ColumnDef[]): string =>
|
|
67
|
-
columns.length === 0 ? '' : ` RETURNING ${buildColumnList(dialect, columns)}`;
|
|
68
|
-
|
|
69
|
-
const buildValuesClause = (dialectCase: DialectCase, columnCount: number, rowCount: number): string => {
|
|
70
|
-
let index = 1;
|
|
71
|
-
const segments: string[] = [];
|
|
72
|
-
for (let row = 0; row < rowCount; row += 1) {
|
|
73
|
-
const placeholders: string[] = [];
|
|
74
|
-
for (let col = 0; col < columnCount; col += 1) {
|
|
75
|
-
placeholders.push(dialectCase.placeholder(index));
|
|
76
|
-
index += 1;
|
|
77
|
-
}
|
|
78
|
-
segments.push(`(${placeholders.join(', ')})`);
|
|
79
|
-
}
|
|
80
|
-
return segments.join(', ');
|
|
81
|
-
};
|
|
82
|
-
|
|
83
|
-
const buildPlaceholderSequence = (dialectCase: DialectCase, count: number, startIndex = 1): string[] =>
|
|
84
|
-
Array.from({ length: count }, (_, idx) => dialectCase.placeholder(startIndex + idx));
|
|
85
|
-
|
|
86
|
-
const flattenRowValues = (rows: Row[], order: (keyof Row)[]): unknown[] =>
|
|
87
|
-
rows.flatMap(row => order.map(key => row[key]));
|
|
88
|
-
|
|
89
|
-
describe('DML builders', () => {
|
|
90
|
-
dialectCases.forEach(dialectCase => {
|
|
91
|
-
describe(dialectCase.name, () => {
|
|
92
|
-
const dialect = dialectCase.dialect;
|
|
93
|
-
const tableName = Users.name;
|
|
94
|
-
const qualifiedColumns = buildColumnList(dialect, columnColumns);
|
|
95
|
-
const returningSql = buildReturningClause(dialect, returningColumns);
|
|
96
|
-
|
|
97
|
-
it('compiles single-row insert', () => {
|
|
98
|
-
const query = new InsertQueryBuilder(Users).values(insertRows[0]);
|
|
99
|
-
const compiled = query.compile(dialect);
|
|
100
|
-
const valueClause = `(${dialectCase.placeholder(1)}, ${dialectCase.placeholder(2)})`;
|
|
101
|
-
const expectedSql = `INSERT INTO ${dialect.quoteIdentifier(tableName)} (${qualifiedColumns}) VALUES ${valueClause};`;
|
|
102
|
-
expect(compiled.sql).toBe(expectedSql);
|
|
103
|
-
expect(compiled.params).toEqual([insertRows[0].name, insertRows[0].role]);
|
|
104
|
-
});
|
|
105
|
-
|
|
106
|
-
it('compiles multi-row insert with consistent parameter order', () => {
|
|
107
|
-
const query = new InsertQueryBuilder(Users).values(insertRows);
|
|
108
|
-
const compiled = query.compile(dialect);
|
|
109
|
-
const valuesClause = buildValuesClause(dialectCase, columnColumns.length, insertRows.length);
|
|
110
|
-
const expectedSql = `INSERT INTO ${dialect.quoteIdentifier(tableName)} (${qualifiedColumns}) VALUES ${valuesClause};`;
|
|
111
|
-
expect(compiled.sql).toBe(expectedSql);
|
|
112
|
-
expect(compiled.params).toEqual(flattenRowValues(insertRows, rowOrder));
|
|
113
|
-
});
|
|
114
|
-
|
|
115
|
-
if (dialectCase.supportsReturning) {
|
|
116
|
-
it('appends RETURNING for insert when requested', () => {
|
|
117
|
-
const query = new InsertQueryBuilder(Users)
|
|
118
|
-
.values(insertRows[0])
|
|
119
|
-
.returning(Users.columns.id, Users.columns.name);
|
|
120
|
-
const compiled = query.compile(dialect);
|
|
121
|
-
const valueClause = `(${dialectCase.placeholder(1)}, ${dialectCase.placeholder(2)})`;
|
|
122
|
-
const expectedSql = `INSERT INTO ${dialect.quoteIdentifier(tableName)} (${qualifiedColumns}) VALUES ${valueClause}${returningSql};`;
|
|
123
|
-
expect(compiled.sql).toBe(expectedSql);
|
|
124
|
-
expect(compiled.params).toEqual([insertRows[0].name, insertRows[0].role]);
|
|
125
|
-
});
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
it('compiles update with SET values', () => {
|
|
129
|
-
const updateValues = { name: 'ali', role: 'builder' };
|
|
130
|
-
const query = new UpdateQueryBuilder(Users).set(updateValues);
|
|
131
|
-
const compiled = query.compile(dialect);
|
|
132
|
-
const placeholderSeq = buildPlaceholderSequence(dialectCase, columnColumns.length);
|
|
133
|
-
const assignments = columnColumns
|
|
134
|
-
.map((column, idx) => `${qualifyColumn(dialect, column)} = ${placeholderSeq[idx]}`)
|
|
135
|
-
.join(', ');
|
|
136
|
-
const expectedSql = `UPDATE ${dialect.quoteIdentifier(tableName)} SET ${assignments};`;
|
|
137
|
-
expect(compiled.sql).toBe(expectedSql);
|
|
138
|
-
expect(compiled.params).toEqual([updateValues.name, updateValues.role]);
|
|
139
|
-
});
|
|
140
|
-
|
|
141
|
-
it('compiles update with WHERE clause', () => {
|
|
142
|
-
const updateValues = { name: 'gold', role: 'star' };
|
|
143
|
-
const query = new UpdateQueryBuilder(Users)
|
|
144
|
-
.set(updateValues)
|
|
145
|
-
.where(eq(Users.columns.id, 1));
|
|
146
|
-
const compiled = query.compile(dialect);
|
|
147
|
-
const assignmentPlaceholders = buildPlaceholderSequence(dialectCase, columnColumns.length);
|
|
148
|
-
const assignments = columnColumns
|
|
149
|
-
.map((column, idx) => `${qualifyColumn(dialect, column)} = ${assignmentPlaceholders[idx]}`)
|
|
150
|
-
.join(', ');
|
|
151
|
-
const wherePlaceholder = dialectCase.placeholder(columnColumns.length + 1);
|
|
152
|
-
const whereClause = ` WHERE ${qualifyColumn(dialect, Users.columns.id)} = ${wherePlaceholder}`;
|
|
153
|
-
const expectedSql = `UPDATE ${dialect.quoteIdentifier(tableName)} SET ${assignments}${whereClause};`;
|
|
154
|
-
expect(compiled.sql).toBe(expectedSql);
|
|
155
|
-
expect(compiled.params).toEqual([updateValues.name, updateValues.role, 1]);
|
|
156
|
-
});
|
|
157
|
-
|
|
158
|
-
if (dialectCase.supportsReturning) {
|
|
159
|
-
it('appends RETURNING for update when requested', () => {
|
|
160
|
-
const query = new UpdateQueryBuilder(Users)
|
|
161
|
-
.set({ name: 'return' })
|
|
162
|
-
.returning(Users.columns.id, Users.columns.name);
|
|
163
|
-
const compiled = query.compile(dialect);
|
|
164
|
-
const placeholder = dialectCase.placeholder(1);
|
|
165
|
-
const assignment = `${qualifyColumn(dialect, Users.columns.name)} = ${placeholder}`;
|
|
166
|
-
const expectedSql = `UPDATE ${dialect.quoteIdentifier(tableName)} SET ${assignment}${returningSql};`;
|
|
167
|
-
expect(compiled.sql).toBe(expectedSql);
|
|
168
|
-
expect(compiled.params).toEqual(['return']);
|
|
169
|
-
});
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
it('compiles DELETE without WHERE', () => {
|
|
173
|
-
const query = new DeleteQueryBuilder(Users);
|
|
174
|
-
const compiled = query.compile(dialect);
|
|
175
|
-
const expectedSql = `DELETE FROM ${dialect.quoteIdentifier(tableName)};`;
|
|
176
|
-
expect(compiled.sql).toBe(expectedSql);
|
|
177
|
-
expect(compiled.params).toEqual([]);
|
|
178
|
-
});
|
|
179
|
-
|
|
180
|
-
it('compiles DELETE with WHERE clause', () => {
|
|
181
|
-
const query = new DeleteQueryBuilder(Users).where(eq(Users.columns.id, 7));
|
|
182
|
-
const compiled = query.compile(dialect);
|
|
183
|
-
const wherePlaceholder = dialectCase.placeholder(1);
|
|
184
|
-
const whereClause = ` WHERE ${qualifyColumn(dialect, Users.columns.id)} = ${wherePlaceholder}`;
|
|
185
|
-
const expectedSql = `DELETE FROM ${dialect.quoteIdentifier(tableName)}${whereClause};`;
|
|
186
|
-
expect(compiled.sql).toBe(expectedSql);
|
|
187
|
-
expect(compiled.params).toEqual([7]);
|
|
188
|
-
});
|
|
189
|
-
|
|
190
|
-
if (dialectCase.supportsReturning) {
|
|
191
|
-
it('appends RETURNING for delete when requested', () => {
|
|
192
|
-
const query = new DeleteQueryBuilder(Users)
|
|
193
|
-
.where(eq(Users.columns.id, 11))
|
|
194
|
-
.returning(Users.columns.id, Users.columns.name);
|
|
195
|
-
const compiled = query.compile(dialect);
|
|
196
|
-
const wherePlaceholder = dialectCase.placeholder(1);
|
|
197
|
-
const whereClause = ` WHERE ${qualifyColumn(dialect, Users.columns.id)} = ${wherePlaceholder}`;
|
|
198
|
-
const expectedSql = `DELETE FROM ${dialect.quoteIdentifier(tableName)}${whereClause}${returningSql};`;
|
|
199
|
-
expect(compiled.sql).toBe(expectedSql);
|
|
200
|
-
expect(compiled.params).toEqual([11]);
|
|
201
|
-
});
|
|
202
|
-
}
|
|
203
|
-
});
|
|
204
|
-
});
|
|
205
|
-
});
|
|
206
|
-
|
package/tests/exists.test.ts
DELETED
|
@@ -1,127 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect } from 'vitest';
|
|
2
|
-
import { SelectQueryBuilder } from '../src/builder/select';
|
|
3
|
-
import { SqliteDialect } from '../src/dialect/sqlite';
|
|
4
|
-
import { TableDef, defineTable } from '../src/schema/table';
|
|
5
|
-
import { col } from '../src/schema/column';
|
|
6
|
-
import { eq, gt, and } from '../src/ast/expression';
|
|
7
|
-
|
|
8
|
-
// Define test schema: Users and Orders
|
|
9
|
-
const Users = defineTable('users', {
|
|
10
|
-
id: col.primaryKey(col.int()),
|
|
11
|
-
name: col.varchar(255),
|
|
12
|
-
email: col.varchar(255),
|
|
13
|
-
}, {});
|
|
14
|
-
|
|
15
|
-
const Orders = defineTable('orders', {
|
|
16
|
-
id: col.primaryKey(col.int()),
|
|
17
|
-
user_id: col.int(),
|
|
18
|
-
status: col.varchar(50),
|
|
19
|
-
total: col.int(),
|
|
20
|
-
}, {});
|
|
21
|
-
|
|
22
|
-
// Define relationships
|
|
23
|
-
Users.relations = {
|
|
24
|
-
orders: {
|
|
25
|
-
type: 'HAS_MANY',
|
|
26
|
-
target: Orders,
|
|
27
|
-
foreignKey: 'user_id',
|
|
28
|
-
localKey: 'id'
|
|
29
|
-
}
|
|
30
|
-
};
|
|
31
|
-
|
|
32
|
-
Orders.relations = {
|
|
33
|
-
user: {
|
|
34
|
-
type: 'BELONGS_TO',
|
|
35
|
-
target: Users,
|
|
36
|
-
foreignKey: 'user_id',
|
|
37
|
-
localKey: 'id'
|
|
38
|
-
}
|
|
39
|
-
};
|
|
40
|
-
|
|
41
|
-
const dialect = new SqliteDialect();
|
|
42
|
-
|
|
43
|
-
describe('EXISTS Support', () => {
|
|
44
|
-
describe('whereHas - basic functionality', () => {
|
|
45
|
-
it('should generate EXISTS with simple correlation', () => {
|
|
46
|
-
const query = new SelectQueryBuilder(Users)
|
|
47
|
-
.select({ id: Users.columns.id, name: Users.columns.name })
|
|
48
|
-
.whereHas('orders');
|
|
49
|
-
|
|
50
|
-
const compiled = query.compile(dialect);
|
|
51
|
-
const { sql, params } = compiled;
|
|
52
|
-
|
|
53
|
-
expect(sql).toContain('EXISTS');
|
|
54
|
-
expect(sql).toContain('SELECT 1 FROM');
|
|
55
|
-
expect(sql).toContain('"orders"."user_id" = "users"."id"');
|
|
56
|
-
expect(params).toEqual([]);
|
|
57
|
-
});
|
|
58
|
-
|
|
59
|
-
it('should support whereHas with filter callback', () => {
|
|
60
|
-
const query = new SelectQueryBuilder(Users)
|
|
61
|
-
.select({ id: Users.columns.id, name: Users.columns.name })
|
|
62
|
-
.whereHas('orders', (ordersQb) =>
|
|
63
|
-
ordersQb.where(eq(Orders.columns.status, 'completed'))
|
|
64
|
-
);
|
|
65
|
-
|
|
66
|
-
const compiled = query.compile(dialect);
|
|
67
|
-
const { sql, params } = compiled;
|
|
68
|
-
|
|
69
|
-
expect(sql).toContain('EXISTS');
|
|
70
|
-
expect(sql).toContain('"orders"."status" = ?');
|
|
71
|
-
expect(sql).toContain('"orders"."user_id" = "users"."id"');
|
|
72
|
-
expect(params).toEqual(['completed']);
|
|
73
|
-
});
|
|
74
|
-
|
|
75
|
-
it('should support whereHas with multiple filters', () => {
|
|
76
|
-
const query = new SelectQueryBuilder(Users)
|
|
77
|
-
.select({ id: Users.columns.id, name: Users.columns.name })
|
|
78
|
-
.whereHas('orders', (ordersQb) =>
|
|
79
|
-
ordersQb.where(and(
|
|
80
|
-
eq(Orders.columns.status, 'completed'),
|
|
81
|
-
gt(Orders.columns.total, 200)
|
|
82
|
-
))
|
|
83
|
-
);
|
|
84
|
-
|
|
85
|
-
const compiled = query.compile(dialect);
|
|
86
|
-
const { sql, params } = compiled;
|
|
87
|
-
|
|
88
|
-
expect(sql).toContain('EXISTS');
|
|
89
|
-
expect(sql).toContain('"orders"."status" = ?');
|
|
90
|
-
expect(sql).toContain('"orders"."total" > ?');
|
|
91
|
-
expect(sql).toContain('"orders"."user_id" = "users"."id"');
|
|
92
|
-
expect(params).toEqual(['completed', 200]);
|
|
93
|
-
});
|
|
94
|
-
});
|
|
95
|
-
|
|
96
|
-
describe('whereHasNot - basic functionality', () => {
|
|
97
|
-
it('should generate NOT EXISTS with simple correlation', () => {
|
|
98
|
-
const query = new SelectQueryBuilder(Users)
|
|
99
|
-
.select({ id: Users.columns.id, name: Users.columns.name })
|
|
100
|
-
.whereHasNot('orders');
|
|
101
|
-
|
|
102
|
-
const compiled = query.compile(dialect);
|
|
103
|
-
const { sql, params } = compiled;
|
|
104
|
-
|
|
105
|
-
expect(sql).toContain('NOT EXISTS');
|
|
106
|
-
expect(sql).toContain('SELECT 1 FROM');
|
|
107
|
-
expect(sql).toContain('"orders"."user_id" = "users"."id"');
|
|
108
|
-
expect(params).toEqual([]);
|
|
109
|
-
});
|
|
110
|
-
|
|
111
|
-
it('should support whereHasNot with filter callback', () => {
|
|
112
|
-
const query = new SelectQueryBuilder(Users)
|
|
113
|
-
.select({ id: Users.columns.id, name: Users.columns.name })
|
|
114
|
-
.whereHasNot('orders', (ordersQb) =>
|
|
115
|
-
ordersQb.where(eq(Orders.columns.status, 'pending'))
|
|
116
|
-
);
|
|
117
|
-
|
|
118
|
-
const compiled = query.compile(dialect);
|
|
119
|
-
const { sql, params } = compiled;
|
|
120
|
-
|
|
121
|
-
expect(sql).toContain('NOT EXISTS');
|
|
122
|
-
expect(sql).toContain('"orders"."status" = ?');
|
|
123
|
-
expect(sql).toContain('"orders"."user_id" = "users"."id"');
|
|
124
|
-
expect(params).toEqual(['pending']);
|
|
125
|
-
});
|
|
126
|
-
});
|
|
127
|
-
});
|