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.
Files changed (153) hide show
  1. package/README.md +341 -146
  2. package/dist/decorators/index.cjs +2564 -0
  3. package/dist/decorators/index.cjs.map +1 -0
  4. package/dist/decorators/index.d.cts +53 -0
  5. package/dist/decorators/index.d.ts +53 -0
  6. package/dist/decorators/index.js +2530 -0
  7. package/dist/decorators/index.js.map +1 -0
  8. package/dist/index.cjs +4227 -0
  9. package/dist/index.cjs.map +1 -0
  10. package/dist/index.d.cts +701 -0
  11. package/dist/index.d.ts +701 -0
  12. package/dist/index.js +4131 -0
  13. package/dist/index.js.map +1 -0
  14. package/dist/select-654m4qy8.d.cts +1522 -0
  15. package/dist/select-654m4qy8.d.ts +1522 -0
  16. package/package.json +27 -20
  17. package/src/codegen/typescript.ts +405 -393
  18. package/src/core/ast/aggregate-functions.ts +30 -0
  19. package/src/core/ast/builders.ts +43 -0
  20. package/src/core/ast/expression-builders.ts +310 -0
  21. package/src/core/ast/expression-nodes.ts +211 -0
  22. package/src/core/ast/expression-visitor.ts +99 -0
  23. package/src/core/ast/expression.ts +5 -0
  24. package/src/{utils → core/ast}/join-node.ts +20 -20
  25. package/src/{ast → core/ast}/join.ts +18 -18
  26. package/src/{ast → core/ast}/query.ts +113 -113
  27. package/src/core/ast/window-functions.ts +140 -0
  28. package/src/{dialect → core/dialect}/abstract.ts +94 -94
  29. package/src/{dialect → core/dialect}/mssql/index.ts +31 -31
  30. package/src/{dialect → core/dialect}/mysql/index.ts +31 -31
  31. package/src/{dialect → core/dialect}/postgres/index.ts +45 -45
  32. package/src/{dialect → core/dialect}/sqlite/index.ts +45 -45
  33. package/src/{constants → core/sql}/sql-operator-config.ts +39 -39
  34. package/src/decorators/bootstrap.ts +126 -0
  35. package/src/decorators/column.ts +78 -0
  36. package/src/decorators/entity.ts +36 -0
  37. package/src/decorators/index.ts +4 -0
  38. package/src/decorators/relations.ts +107 -0
  39. package/src/global.d.ts +1 -0
  40. package/src/index.ts +22 -22
  41. package/src/orm/db-executor.ts +11 -0
  42. package/src/orm/domain-event-bus.ts +52 -0
  43. package/src/{runtime → orm}/entity-meta.ts +52 -52
  44. package/src/orm/entity-metadata.ts +140 -0
  45. package/src/{runtime → orm}/entity.ts +252 -252
  46. package/src/{runtime → orm}/execute.ts +36 -36
  47. package/src/{runtime → orm}/hydration.ts +103 -103
  48. package/src/orm/identity-map.ts +37 -0
  49. package/src/{runtime → orm}/lazy-batch.ts +205 -205
  50. package/src/orm/orm-context.ts +154 -0
  51. package/src/orm/relation-change-processor.ts +140 -0
  52. package/src/{runtime → orm}/relations/belongs-to.ts +92 -92
  53. package/src/{runtime → orm}/relations/has-many.ts +111 -111
  54. package/src/{runtime → orm}/relations/many-to-many.ts +149 -149
  55. package/src/orm/runtime-types.ts +39 -0
  56. package/src/orm/transaction-runner.ts +17 -0
  57. package/src/orm/unit-of-work.ts +232 -0
  58. package/src/{builder/operations → query-builder}/column-selector.ts +78 -78
  59. package/src/{builder → query-builder}/delete-query-state.ts +38 -42
  60. package/src/{builder → query-builder}/delete.ts +46 -57
  61. package/src/{builder → query-builder}/hydration-manager.ts +87 -87
  62. package/src/{builder → query-builder}/hydration-planner.ts +182 -182
  63. package/src/{builder → query-builder}/insert-query-state.ts +51 -62
  64. package/src/{builder → query-builder}/insert.ts +48 -59
  65. package/src/{builder → query-builder}/query-ast-service.ts +208 -226
  66. package/src/{utils → query-builder}/raw-column-parser.ts +32 -32
  67. package/src/{builder → query-builder}/relation-conditions.ts +112 -112
  68. package/src/{builder/operations → query-builder}/relation-manager.ts +82 -82
  69. package/src/{builder → query-builder}/relation-projection-helper.ts +101 -101
  70. package/src/{builder → query-builder}/relation-service.ts +284 -284
  71. package/src/{builder → query-builder}/relation-types.ts +21 -21
  72. package/src/{builder → query-builder}/relation-utils.ts +12 -12
  73. package/src/{builder → query-builder}/select-query-builder-deps.ts +112 -94
  74. package/src/{builder → query-builder}/select-query-state.ts +179 -179
  75. package/src/{builder → query-builder}/select.ts +78 -69
  76. package/src/{builder → query-builder}/update-query-state.ts +55 -59
  77. package/src/{builder → query-builder}/update.ts +50 -61
  78. package/src/schema/column.ts +25 -25
  79. package/src/schema/relation.ts +116 -116
  80. package/src/schema/table.ts +34 -34
  81. package/src/schema/types.ts +76 -76
  82. package/.github/workflows/publish-metal-orm.yml +0 -38
  83. package/ROADMAP.md +0 -125
  84. package/docs/CHANGES.md +0 -104
  85. package/docs/advanced-features.md +0 -176
  86. package/docs/api-reference.md +0 -31
  87. package/docs/dml-operations.md +0 -156
  88. package/docs/getting-started.md +0 -171
  89. package/docs/hydration.md +0 -115
  90. package/docs/index.md +0 -36
  91. package/docs/multi-dialect-support.md +0 -59
  92. package/docs/query-builder.md +0 -135
  93. package/docs/runtime.md +0 -105
  94. package/docs/schema-definition.md +0 -112
  95. package/metadata.json +0 -5
  96. package/playground/api/playground-api.ts +0 -94
  97. package/playground/index.html +0 -15
  98. package/playground/src/App.css +0 -1
  99. package/playground/src/App.tsx +0 -114
  100. package/playground/src/components/CodeDisplay.tsx +0 -43
  101. package/playground/src/components/QueryExecutor.tsx +0 -189
  102. package/playground/src/components/ResultsTable.tsx +0 -67
  103. package/playground/src/components/ResultsTabs.tsx +0 -105
  104. package/playground/src/components/ScenarioList.tsx +0 -56
  105. package/playground/src/components/logo.svg +0 -45
  106. package/playground/src/data/scenarios.ts +0 -2
  107. package/playground/src/main.tsx +0 -9
  108. package/playground/src/services/PlaygroundApiService.ts +0 -60
  109. package/postcss.config.cjs +0 -5
  110. package/sql_sql-ansi-cheatsheet-2025.md +0 -264
  111. package/src/ast/expression.ts +0 -658
  112. package/src/builder/operations/cte-manager.ts +0 -34
  113. package/src/builder/operations/filter-manager.ts +0 -68
  114. package/src/builder/operations/join-manager.ts +0 -36
  115. package/src/builder/operations/pagination-manager.ts +0 -36
  116. package/src/playground/features/playground/api/types.ts +0 -16
  117. package/src/playground/features/playground/clients/MockClient.ts +0 -17
  118. package/src/playground/features/playground/clients/SqliteClient.ts +0 -57
  119. package/src/playground/features/playground/common/IDatabaseClient.ts +0 -10
  120. package/src/playground/features/playground/data/scenarios/aggregation.ts +0 -36
  121. package/src/playground/features/playground/data/scenarios/basics.ts +0 -25
  122. package/src/playground/features/playground/data/scenarios/edge_cases.ts +0 -57
  123. package/src/playground/features/playground/data/scenarios/filtering.ts +0 -94
  124. package/src/playground/features/playground/data/scenarios/hydration.ts +0 -27
  125. package/src/playground/features/playground/data/scenarios/index.ts +0 -29
  126. package/src/playground/features/playground/data/scenarios/ordering.ts +0 -25
  127. package/src/playground/features/playground/data/scenarios/pagination.ts +0 -16
  128. package/src/playground/features/playground/data/scenarios/relationships.ts +0 -75
  129. package/src/playground/features/playground/data/scenarios/types.ts +0 -70
  130. package/src/playground/features/playground/data/schema.ts +0 -91
  131. package/src/playground/features/playground/data/seed.ts +0 -104
  132. package/src/playground/features/playground/services/QueryExecutionService.ts +0 -121
  133. package/src/runtime/orm-context.ts +0 -539
  134. package/tests/belongs-to-many.test.ts +0 -57
  135. package/tests/between.test.ts +0 -43
  136. package/tests/case-expression.test.ts +0 -58
  137. package/tests/complex-exists.test.ts +0 -230
  138. package/tests/cte.test.ts +0 -118
  139. package/tests/dml.test.ts +0 -206
  140. package/tests/exists.test.ts +0 -127
  141. package/tests/like.test.ts +0 -33
  142. package/tests/orm-runtime.test.ts +0 -254
  143. package/tests/postgres.test.ts +0 -30
  144. package/tests/right-join.test.ts +0 -89
  145. package/tests/subquery-having.test.ts +0 -193
  146. package/tests/window-function.test.ts +0 -151
  147. package/tsconfig.json +0 -30
  148. package/tsup.config.ts +0 -10
  149. package/vite.config.ts +0 -22
  150. package/vitest.config.ts +0 -14
  151. /package/src/{constants → core/sql}/sql.ts +0 -0
  152. /package/src/{runtime → orm}/als.ts +0 -0
  153. /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
-
@@ -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
- });