@aetherframework/database 1.1.0 → 1.1.2
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/examples/mysql-test-pressure.js +1530 -0
- package/examples/test-direct.js +116 -0
- package/examples/transaction_example.js +127 -0
- package/package.json +3 -1
- package/src/DatabaseManager.js +565 -0
- package/src/core/ConnectionManager.js +351 -0
- package/src/core/DatabaseFactory.js +188 -0
- package/src/core/MongoQueryBuilder.js +576 -0
- package/src/core/PluginManager.js +968 -0
- package/src/core/QueryBuilder.js +4394 -0
- package/src/core/TransactionManager.js +40 -0
- package/src/drivers/clickhouse-driver.js +272 -0
- package/src/drivers/index.js +273 -0
- package/src/drivers/mongodb-driver.js +87 -0
- package/src/drivers/mssql-driver.js +117 -0
- package/src/drivers/mysql-driver.js +169 -0
- package/src/drivers/oracle-driver.js +101 -0
- package/src/drivers/postgres-driver.js +234 -0
- package/src/drivers/redis-driver.js +52 -0
- package/src/drivers/sqlite-driver.js +67 -0
- package/src/middleware/connection-pool.js +455 -0
- package/src/middleware/performance-monitor.js +652 -0
- package/src/middleware/query-cache.js +500 -0
- package/src/middleware/query-logger.js +262 -0
- package/src/plugins/AuditPlugin.js +447 -0
- package/src/plugins/BasePlugin.js +418 -0
- package/src/plugins/BatchOperationPlugin.js +165 -0
- package/src/plugins/CachePlugin.js +407 -0
- package/src/plugins/CtePlugin.js +523 -0
- package/src/plugins/DistributedPlugin.js +543 -0
- package/src/plugins/EncryptionPlugin.js +211 -0
- package/src/plugins/FullTextSearchPlugin.js +164 -0
- package/src/plugins/GeospatialPlugin.js +219 -0
- package/src/plugins/GraphQLPlugin.js +162 -0
- package/src/plugins/HookPlugin.js +211 -0
- package/src/plugins/JsonPlugin.js +366 -0
- package/src/plugins/OptimisticLockPlugin.js +374 -0
- package/src/plugins/PerformancePlugin.js +175 -0
- package/src/plugins/ResiliencePlugin.js +114 -0
- package/src/plugins/ShardingPlugin.js +227 -0
- package/src/plugins/SoftDeletePlugin.js +258 -0
- package/src/plugins/SyncPlugin.js +373 -0
- package/src/plugins/VersioningPlugin.js +314 -0
- package/src/plugins/WindowFunctionPlugin.js +343 -0
- package/src/utils/config-loader.js +632 -0
- package/src/utils/error-handler.js +724 -0
- package/src/utils/migration-runner.js +1066 -0
|
@@ -0,0 +1,523 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license MIT
|
|
3
|
+
* Copyright (c) 2026-present AetherFramework Contributors.
|
|
4
|
+
* SPDX-License-Identifier: MIT
|
|
5
|
+
* @module @aetherframework/database/plugin/CtePlugin
|
|
6
|
+
*/
|
|
7
|
+
import { BasePlugin } from "./BasePlugin.js";
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Common Table Expression Plugin - Manages CTE construction and correctly adds WITH clause when generating SQL
|
|
11
|
+
*/
|
|
12
|
+
export class CtePlugin extends BasePlugin {
|
|
13
|
+
constructor(queryBuilder) {
|
|
14
|
+
super(queryBuilder);
|
|
15
|
+
this.pluginName = "CtePlugin";
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
_registerMethods() {
|
|
19
|
+
// Register CTE methods to QueryBuilder
|
|
20
|
+
this.queryBuilder.with = this.with.bind(this);
|
|
21
|
+
this.queryBuilder.withRecursive = this.withRecursive.bind(this);
|
|
22
|
+
this.queryBuilder.fromCte = this.fromCte.bind(this);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Define a Common Table Expression (CTE)
|
|
27
|
+
* @param {string} name - CTE name
|
|
28
|
+
* @param {Function} callback - CTE query callback
|
|
29
|
+
* @param {Array} columns - CTE column names
|
|
30
|
+
* @returns {QueryBuilder} QueryBuilder instance
|
|
31
|
+
*/
|
|
32
|
+
with(name, callback, columns = []) {
|
|
33
|
+
const cteQuery = new this.queryBuilder.constructor("", this.queryBuilder.connection, this.queryBuilder.dialect);
|
|
34
|
+
callback(cteQuery);
|
|
35
|
+
const { sql, bindings } = cteQuery.toSQL();
|
|
36
|
+
|
|
37
|
+
this.queryBuilder.query.cte.push({
|
|
38
|
+
name,
|
|
39
|
+
sql,
|
|
40
|
+
columns,
|
|
41
|
+
recursive: false,
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
this.queryBuilder.bindings.unshift(...bindings);
|
|
45
|
+
return this.queryBuilder;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Define a Recursive Common Table Expression (CTE)
|
|
50
|
+
* @param {string} name - CTE name
|
|
51
|
+
* @param {Function} anchorCallback - Anchor query callback
|
|
52
|
+
* @param {Function} recursiveCallback - Recursive query callback
|
|
53
|
+
* @param {Array} columns - CTE column names
|
|
54
|
+
* @returns {QueryBuilder} QueryBuilder instance
|
|
55
|
+
*/
|
|
56
|
+
withRecursive(name, anchorCallback, recursiveCallback, columns = []) {
|
|
57
|
+
const anchorQuery = new this.queryBuilder.constructor("", this.queryBuilder.connection, this.queryBuilder.dialect);
|
|
58
|
+
anchorCallback(anchorQuery);
|
|
59
|
+
const { sql: anchorSql, bindings: anchorBindings } = anchorQuery.toSQL();
|
|
60
|
+
|
|
61
|
+
const recursiveQuery = new this.queryBuilder.constructor("", this.queryBuilder.connection, this.queryBuilder.dialect);
|
|
62
|
+
recursiveCallback(recursiveQuery);
|
|
63
|
+
const { sql: recursiveSql, bindings: recursiveBindings } = recursiveQuery.toSQL();
|
|
64
|
+
|
|
65
|
+
const cteSql = `${anchorSql} UNION ALL ${recursiveSql}`;
|
|
66
|
+
|
|
67
|
+
this.queryBuilder.query.cte.push({
|
|
68
|
+
name,
|
|
69
|
+
sql: cteSql,
|
|
70
|
+
columns,
|
|
71
|
+
recursive: true,
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
this.queryBuilder.bindings.unshift(...anchorBindings, ...recursiveBindings);
|
|
75
|
+
return this.queryBuilder;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Use CTE in FROM clause
|
|
80
|
+
* @param {string} cteName - CTE name
|
|
81
|
+
* @param {string} alias - Table alias
|
|
82
|
+
* @returns {QueryBuilder} QueryBuilder instance
|
|
83
|
+
*/
|
|
84
|
+
fromCte(cteName, alias = null) {
|
|
85
|
+
const tableName = alias ? `${cteName} as ${alias}` : cteName;
|
|
86
|
+
this.queryBuilder.tableName = tableName;
|
|
87
|
+
return this.queryBuilder;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Build CTE clause
|
|
92
|
+
* @returns {string} CTE SQL fragment
|
|
93
|
+
*/
|
|
94
|
+
buildCteClause() {
|
|
95
|
+
if (this.queryBuilder.query.cte.length === 0) {
|
|
96
|
+
return "";
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
const cteClauses = this.queryBuilder.query.cte.map((cte) => {
|
|
100
|
+
const columns = cte.columns.length > 0 ? `(${cte.columns.join(", ")})` : "";
|
|
101
|
+
const recursive = cte.recursive ? "RECURSIVE " : "";
|
|
102
|
+
return `${recursive}${cte.name}${columns} AS (${cte.sql})`;
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
return `WITH ${cteClauses.join(", ")} `;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Check if database supports CTE
|
|
110
|
+
* @returns {boolean} Whether supported
|
|
111
|
+
*/
|
|
112
|
+
isCteSupported() {
|
|
113
|
+
const supportedDialects = [
|
|
114
|
+
"postgresql",
|
|
115
|
+
"postgres",
|
|
116
|
+
"pg",
|
|
117
|
+
"mysql", // MySQL 8.0+
|
|
118
|
+
"mariadb", // MariaDB 10.2+
|
|
119
|
+
"sqlite", // SQLite 3.8.3+
|
|
120
|
+
"mssql", // SQL Server 2005+
|
|
121
|
+
"sqlserver",
|
|
122
|
+
"cockroachdb",
|
|
123
|
+
"cockroach",
|
|
124
|
+
];
|
|
125
|
+
|
|
126
|
+
return supportedDialects.includes(this.queryBuilder.dialect);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Check if database supports recursive CTE
|
|
131
|
+
* @returns {boolean} Whether supported
|
|
132
|
+
*/
|
|
133
|
+
isRecursiveCteSupported() {
|
|
134
|
+
const supportedDialects = [
|
|
135
|
+
"postgresql",
|
|
136
|
+
"postgres",
|
|
137
|
+
"pg",
|
|
138
|
+
"mysql", // MySQL 8.0+
|
|
139
|
+
"mariadb", // MariaDB 10.2+
|
|
140
|
+
"sqlite", // SQLite 3.8.3+
|
|
141
|
+
"mssql", // SQL Server 2005+
|
|
142
|
+
"sqlserver",
|
|
143
|
+
"cockroachdb",
|
|
144
|
+
"cockroach",
|
|
145
|
+
];
|
|
146
|
+
|
|
147
|
+
return supportedDialects.includes(this.queryBuilder.dialect);
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* Create hierarchical query (common use case for recursive CTE)
|
|
152
|
+
* @param {string} idColumn - ID column name
|
|
153
|
+
* @param {string} parentColumn - Parent ID column name
|
|
154
|
+
* @param {string} levelColumn - Level column name (default: 'level')
|
|
155
|
+
* @param {string} pathColumn - Path column name (default: 'path')
|
|
156
|
+
* @returns {QueryBuilder} QueryBuilder instance
|
|
157
|
+
*/
|
|
158
|
+
hierarchical(idColumn = "id", parentColumn = "parent_id", levelColumn = "level", pathColumn = "path") {
|
|
159
|
+
if (!this.isRecursiveCteSupported()) {
|
|
160
|
+
throw new Error("Hierarchical queries are only supported in databases that support recursive CTEs");
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
const cteName = "hierarchy";
|
|
164
|
+
|
|
165
|
+
return this.withRecursive(
|
|
166
|
+
cteName,
|
|
167
|
+
(anchor) => {
|
|
168
|
+
anchor
|
|
169
|
+
.select(
|
|
170
|
+
`${idColumn}`,
|
|
171
|
+
`${parentColumn}`,
|
|
172
|
+
`1 as ${levelColumn}`,
|
|
173
|
+
`${idColumn}::text as ${pathColumn}`
|
|
174
|
+
)
|
|
175
|
+
.whereNull(parentColumn);
|
|
176
|
+
},
|
|
177
|
+
(recursive) => {
|
|
178
|
+
recursive
|
|
179
|
+
.select(
|
|
180
|
+
`t.${idColumn}`,
|
|
181
|
+
`t.${parentColumn}`,
|
|
182
|
+
`h.${levelColumn} + 1 as ${levelColumn}`,
|
|
183
|
+
`h.${pathColumn} || '.' || t.${idColumn}::text as ${pathColumn}`
|
|
184
|
+
)
|
|
185
|
+
.from(`${this.queryBuilder.tableName} as t`)
|
|
186
|
+
.join(`${cteName} as h`, `t.${parentColumn}`, "=", `h.${idColumn}`);
|
|
187
|
+
},
|
|
188
|
+
[idColumn, parentColumn, levelColumn, pathColumn]
|
|
189
|
+
).fromCte(cteName);
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
/**
|
|
193
|
+
* Create materialized path query
|
|
194
|
+
* @param {string} idColumn - ID column name
|
|
195
|
+
* @param {string} parentColumn - Parent ID column name
|
|
196
|
+
* @param {string} nameColumn - Name column name
|
|
197
|
+
* @param {string} separator - Path separator (default: '/')
|
|
198
|
+
* @returns {QueryBuilder} QueryBuilder instance
|
|
199
|
+
*/
|
|
200
|
+
materializedPath(idColumn = "id", parentColumn = "parent_id", nameColumn = "name", separator = "/") {
|
|
201
|
+
if (!this.isRecursiveCteSupported()) {
|
|
202
|
+
throw new Error("Materialized path queries are only supported in databases that support recursive CTEs");
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
const cteName = "materialized_path";
|
|
206
|
+
|
|
207
|
+
return this.withRecursive(
|
|
208
|
+
cteName,
|
|
209
|
+
(anchor) => {
|
|
210
|
+
anchor
|
|
211
|
+
.select(
|
|
212
|
+
`${idColumn}`,
|
|
213
|
+
`${parentColumn}`,
|
|
214
|
+
`${nameColumn}`,
|
|
215
|
+
`CAST(${nameColumn} AS VARCHAR(1000)) as path`,
|
|
216
|
+
`1 as depth`
|
|
217
|
+
)
|
|
218
|
+
.whereNull(parentColumn);
|
|
219
|
+
},
|
|
220
|
+
(recursive) => {
|
|
221
|
+
recursive
|
|
222
|
+
.select(
|
|
223
|
+
`t.${idColumn}`,
|
|
224
|
+
`t.${parentColumn}`,
|
|
225
|
+
`t.${nameColumn}`,
|
|
226
|
+
`CONCAT(h.path, '${separator}', t.${nameColumn}) as path`,
|
|
227
|
+
`h.depth + 1 as depth`
|
|
228
|
+
)
|
|
229
|
+
.from(`${this.queryBuilder.tableName} as t`)
|
|
230
|
+
.join(`${cteName} as h`, `t.${parentColumn}`, "=", `h.${idColumn}`);
|
|
231
|
+
},
|
|
232
|
+
[idColumn, parentColumn, nameColumn, "path", "depth"]
|
|
233
|
+
).fromCte(cteName);
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
/**
|
|
237
|
+
* Create organizational chart query
|
|
238
|
+
* @param {string} idColumn - ID column name
|
|
239
|
+
* @param {string} parentColumn - Parent ID column name
|
|
240
|
+
* @param {string} nameColumn - Name column name
|
|
241
|
+
* @param {string} titleColumn - Title column name
|
|
242
|
+
* @returns {QueryBuilder} QueryBuilder instance
|
|
243
|
+
*/
|
|
244
|
+
orgChart(idColumn = "id", parentColumn = "parent_id", nameColumn = "name", titleColumn = "title") {
|
|
245
|
+
if (!this.isRecursiveCteSupported()) {
|
|
246
|
+
throw new Error("Organizational chart queries are only supported in databases that support recursive CTEs");
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
const cteName = "org_chart";
|
|
250
|
+
|
|
251
|
+
return this.withRecursive(
|
|
252
|
+
cteName,
|
|
253
|
+
(anchor) => {
|
|
254
|
+
anchor
|
|
255
|
+
.select(
|
|
256
|
+
`${idColumn}`,
|
|
257
|
+
`${parentColumn}`,
|
|
258
|
+
`${nameColumn}`,
|
|
259
|
+
`${titleColumn}`,
|
|
260
|
+
`1 as level`,
|
|
261
|
+
`CAST(${nameColumn} AS VARCHAR(1000)) as hierarchy_path`
|
|
262
|
+
)
|
|
263
|
+
.whereNull(parentColumn);
|
|
264
|
+
},
|
|
265
|
+
(recursive) => {
|
|
266
|
+
recursive
|
|
267
|
+
.select(
|
|
268
|
+
`t.${idColumn}`,
|
|
269
|
+
`t.${parentColumn}`,
|
|
270
|
+
`t.${nameColumn}`,
|
|
271
|
+
`t.${titleColumn}`,
|
|
272
|
+
`h.level + 1 as level`,
|
|
273
|
+
`CONCAT(h.hierarchy_path, ' > ', t.${nameColumn}) as hierarchy_path`
|
|
274
|
+
)
|
|
275
|
+
.from(`${this.queryBuilder.tableName} as t`)
|
|
276
|
+
.join(`${cteName} as h`, `t.${parentColumn}`, "=", `h.${idColumn}`);
|
|
277
|
+
},
|
|
278
|
+
[idColumn, parentColumn, nameColumn, titleColumn, "level", "hierarchy_path"]
|
|
279
|
+
).fromCte(cteName);
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
/**
|
|
283
|
+
* Create bill of materials (BOM) query
|
|
284
|
+
* @param {string} componentIdColumn - Component ID column name
|
|
285
|
+
* @param {string} parentComponentIdColumn - Parent component ID column name
|
|
286
|
+
* @param {string} quantityColumn - Quantity column name
|
|
287
|
+
* @param {string} costColumn - Cost column name
|
|
288
|
+
* @returns {QueryBuilder} QueryBuilder instance
|
|
289
|
+
*/
|
|
290
|
+
billOfMaterials(componentIdColumn = "component_id", parentComponentIdColumn = "parent_component_id", quantityColumn = "quantity", costColumn = "cost") {
|
|
291
|
+
if (!this.isRecursiveCteSupported()) {
|
|
292
|
+
throw new Error("Bill of materials queries are only supported in databases that support recursive CTEs");
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
const cteName = "bom";
|
|
296
|
+
|
|
297
|
+
return this.withRecursive(
|
|
298
|
+
cteName,
|
|
299
|
+
(anchor) => {
|
|
300
|
+
anchor
|
|
301
|
+
.select(
|
|
302
|
+
`${componentIdColumn}`,
|
|
303
|
+
`${parentComponentIdColumn}`,
|
|
304
|
+
`${quantityColumn}`,
|
|
305
|
+
`${costColumn}`,
|
|
306
|
+
`${quantityColumn} as total_quantity`,
|
|
307
|
+
`${costColumn} as total_cost`,
|
|
308
|
+
`1 as level`
|
|
309
|
+
)
|
|
310
|
+
.whereNull(parentComponentIdColumn);
|
|
311
|
+
},
|
|
312
|
+
(recursive) => {
|
|
313
|
+
recursive
|
|
314
|
+
.select(
|
|
315
|
+
`t.${componentIdColumn}`,
|
|
316
|
+
`t.${parentComponentIdColumn}`,
|
|
317
|
+
`t.${quantityColumn}`,
|
|
318
|
+
`t.${costColumn}`,
|
|
319
|
+
`h.total_quantity * t.${quantityColumn} as total_quantity`,
|
|
320
|
+
`h.total_cost + (h.total_quantity * t.${costColumn}) as total_cost`,
|
|
321
|
+
`h.level + 1 as level`
|
|
322
|
+
)
|
|
323
|
+
.from(`${this.queryBuilder.tableName} as t`)
|
|
324
|
+
.join(`${cteName} as h`, `t.${parentComponentIdColumn}`, "=", `h.${componentIdColumn}`);
|
|
325
|
+
},
|
|
326
|
+
[componentIdColumn, parentComponentIdColumn, quantityColumn, costColumn, "total_quantity", "total_cost", "level"]
|
|
327
|
+
).fromCte(cteName);
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
/**
|
|
331
|
+
* Create category tree query
|
|
332
|
+
* @param {string} categoryIdColumn - Category ID column name
|
|
333
|
+
* @param {string} parentCategoryIdColumn - Parent category ID column name
|
|
334
|
+
* @param {string} categoryNameColumn - Category name column name
|
|
335
|
+
* @param {string} sortOrderColumn - Sort order column name
|
|
336
|
+
* @returns {QueryBuilder} QueryBuilder instance
|
|
337
|
+
*/
|
|
338
|
+
categoryTree(categoryIdColumn = "category_id", parentCategoryIdColumn = "parent_category_id", categoryNameColumn = "category_name", sortOrderColumn = "sort_order") {
|
|
339
|
+
if (!this.isRecursiveCteSupported()) {
|
|
340
|
+
throw new Error("Category tree queries are only supported in databases that support recursive CTEs");
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
const cteName = "category_tree";
|
|
344
|
+
|
|
345
|
+
return this.withRecursive(
|
|
346
|
+
cteName,
|
|
347
|
+
(anchor) => {
|
|
348
|
+
anchor
|
|
349
|
+
.select(
|
|
350
|
+
`${categoryIdColumn}`,
|
|
351
|
+
`${parentCategoryIdColumn}`,
|
|
352
|
+
`${categoryNameColumn}`,
|
|
353
|
+
`${sortOrderColumn}`,
|
|
354
|
+
`CAST(${categoryNameColumn} AS VARCHAR(1000)) as full_path`,
|
|
355
|
+
`1 as depth`
|
|
356
|
+
)
|
|
357
|
+
.whereNull(parentCategoryIdColumn)
|
|
358
|
+
.orderBy(sortOrderColumn);
|
|
359
|
+
},
|
|
360
|
+
(recursive) => {
|
|
361
|
+
recursive
|
|
362
|
+
.select(
|
|
363
|
+
`t.${categoryIdColumn}`,
|
|
364
|
+
`t.${parentCategoryIdColumn}`,
|
|
365
|
+
`t.${categoryNameColumn}`,
|
|
366
|
+
`t.${sortOrderColumn}`,
|
|
367
|
+
`CONCAT(h.full_path, ' > ', t.${categoryNameColumn}) as full_path`,
|
|
368
|
+
`h.depth + 1 as depth`
|
|
369
|
+
)
|
|
370
|
+
.from(`${this.queryBuilder.tableName} as t`)
|
|
371
|
+
.join(`${cteName} as h`, `t.${parentCategoryIdColumn}`, "=", `h.${categoryIdColumn}`)
|
|
372
|
+
.orderBy(sortOrderColumn);
|
|
373
|
+
},
|
|
374
|
+
[categoryIdColumn, parentCategoryIdColumn, categoryNameColumn, sortOrderColumn, "full_path", "depth"]
|
|
375
|
+
).fromCte(cteName);
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
/**
|
|
379
|
+
* Create graph traversal query (for adjacency lists)
|
|
380
|
+
* @param {string} nodeIdColumn - Node ID column name
|
|
381
|
+
* @param {string} edgeColumn - Edge column name
|
|
382
|
+
* @param {string} costColumn - Cost column name (optional)
|
|
383
|
+
* @returns {QueryBuilder} QueryBuilder instance
|
|
384
|
+
*/
|
|
385
|
+
graphTraversal(nodeIdColumn = "node_id", edgeColumn = "edge_to", costColumn = null) {
|
|
386
|
+
if (!this.isRecursiveCteSupported()) {
|
|
387
|
+
throw new Error("Graph traversal queries are only supported in databases that support recursive CTEs");
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
const cteName = "graph_traversal";
|
|
391
|
+
|
|
392
|
+
const anchorSelect = [
|
|
393
|
+
`${nodeIdColumn}`,
|
|
394
|
+
`${edgeColumn}`,
|
|
395
|
+
`0 as distance`,
|
|
396
|
+
`CAST(${nodeIdColumn} AS VARCHAR(1000)) as path`
|
|
397
|
+
];
|
|
398
|
+
|
|
399
|
+
const recursiveSelect = [
|
|
400
|
+
`t.${nodeIdColumn}`,
|
|
401
|
+
`t.${edgeColumn}`,
|
|
402
|
+
`h.distance + 1 as distance`,
|
|
403
|
+
`CONCAT(h.path, ' -> ', t.${nodeIdColumn}) as path`
|
|
404
|
+
];
|
|
405
|
+
|
|
406
|
+
if (costColumn) {
|
|
407
|
+
anchorSelect.push(`${costColumn} as total_cost`);
|
|
408
|
+
recursiveSelect.push(`h.total_cost + t.${costColumn} as total_cost`);
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
return this.withRecursive(
|
|
412
|
+
cteName,
|
|
413
|
+
(anchor) => {
|
|
414
|
+
anchor
|
|
415
|
+
.select(...anchorSelect)
|
|
416
|
+
.where(`${edgeColumn}`, "=", this.queryBuilder.bindings || null);
|
|
417
|
+
},
|
|
418
|
+
(recursive) => {
|
|
419
|
+
recursive
|
|
420
|
+
.select(...recursiveSelect)
|
|
421
|
+
.from(`${this.queryBuilder.tableName} as t`)
|
|
422
|
+
.join(`${cteName} as h`, `t.${nodeIdColumn}`, "=", `h.${edgeColumn}`);
|
|
423
|
+
},
|
|
424
|
+
costColumn
|
|
425
|
+
? [nodeIdColumn, edgeColumn, "distance", "path", "total_cost"]
|
|
426
|
+
: [nodeIdColumn, edgeColumn, "distance", "path"]
|
|
427
|
+
).fromCte(cteName);
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
/**
|
|
431
|
+
* Create number sequence CTE
|
|
432
|
+
* @param {number} start - Start number
|
|
433
|
+
* @param {number} end - End number
|
|
434
|
+
* @param {number} step - Step size (default: 1)
|
|
435
|
+
* @param {string} columnName - Column name for sequence (default: 'n')
|
|
436
|
+
* @returns {QueryBuilder} QueryBuilder instance
|
|
437
|
+
*/
|
|
438
|
+
numberSequence(start = 1, end = 10, step = 1, columnName = "n") {
|
|
439
|
+
if (!this.isRecursiveCteSupported()) {
|
|
440
|
+
throw new Error("Number sequence queries are only supported in databases that support recursive CTEs");
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
const cteName = "numbers";
|
|
444
|
+
|
|
445
|
+
return this.withRecursive(
|
|
446
|
+
cteName,
|
|
447
|
+
(anchor) => {
|
|
448
|
+
anchor.selectRaw(`${start} as ${columnName}`);
|
|
449
|
+
},
|
|
450
|
+
(recursive) => {
|
|
451
|
+
recursive
|
|
452
|
+
.selectRaw(`${columnName} + ${step} as ${columnName}`)
|
|
453
|
+
.fromCte(cteName)
|
|
454
|
+
.where(columnName, "<", end);
|
|
455
|
+
},
|
|
456
|
+
[columnName]
|
|
457
|
+
).fromCte(cteName);
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
/**
|
|
461
|
+
* Create date range CTE
|
|
462
|
+
* @param {string} startDate - Start date (YYYY-MM-DD)
|
|
463
|
+
* @param {string} endDate - End date (YYYY-MM-DD)
|
|
464
|
+
* @param {string} dateColumn - Date column name (default: 'date')
|
|
465
|
+
* @returns {QueryBuilder} QueryBuilder instance
|
|
466
|
+
*/
|
|
467
|
+
dateRange(startDate, endDate, dateColumn = "date") {
|
|
468
|
+
if (!this.isRecursiveCteSupported()) {
|
|
469
|
+
throw new Error("Date range queries are only supported in databases that support recursive CTEs");
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
const cteName = "dates";
|
|
473
|
+
|
|
474
|
+
return this.withRecursive(
|
|
475
|
+
cteName,
|
|
476
|
+
(anchor) => {
|
|
477
|
+
anchor.selectRaw(`'${startDate}'::date as ${dateColumn}`);
|
|
478
|
+
},
|
|
479
|
+
(recursive) => {
|
|
480
|
+
recursive
|
|
481
|
+
.selectRaw(`${dateColumn} + INTERVAL '1 day' as ${dateColumn}`)
|
|
482
|
+
.fromCte(cteName)
|
|
483
|
+
.where(dateColumn, "<", endDate);
|
|
484
|
+
},
|
|
485
|
+
[dateColumn]
|
|
486
|
+
).fromCte(cteName);
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
/**
|
|
490
|
+
* Create calendar CTE
|
|
491
|
+
* @param {string} startDate - Start date (YYYY-MM-DD)
|
|
492
|
+
* @param {string} endDate - End date (YYYY-MM-DD)
|
|
493
|
+
* @returns {QueryBuilder} QueryBuilder instance
|
|
494
|
+
*/
|
|
495
|
+
calendar(startDate, endDate) {
|
|
496
|
+
if (!this.isRecursiveCteSupported()) {
|
|
497
|
+
throw new Error("Calendar queries are only supported in databases that support recursive CTEs");
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
const cteName = "calendar";
|
|
501
|
+
|
|
502
|
+
return this.withRecursive(
|
|
503
|
+
cteName,
|
|
504
|
+
(anchor) => {
|
|
505
|
+
anchor.selectRaw(`'${startDate}'::date as date`);
|
|
506
|
+
},
|
|
507
|
+
(recursive) => {
|
|
508
|
+
recursive
|
|
509
|
+
.selectRaw("date + INTERVAL '1 day' as date")
|
|
510
|
+
.fromCte(cteName)
|
|
511
|
+
.where("date", "<", endDate);
|
|
512
|
+
},
|
|
513
|
+
["date"]
|
|
514
|
+
).fromCte(cteName)
|
|
515
|
+
.selectRaw("date")
|
|
516
|
+
.selectRaw("EXTRACT(YEAR FROM date) as year")
|
|
517
|
+
.selectRaw("EXTRACT(MONTH FROM date) as month")
|
|
518
|
+
.selectRaw("EXTRACT(DAY FROM date) as day")
|
|
519
|
+
.selectRaw("EXTRACT(DOW FROM date) as day_of_week")
|
|
520
|
+
.selectRaw("EXTRACT(WEEK FROM date) as week")
|
|
521
|
+
.selectRaw("EXTRACT(QUARTER FROM date) as quarter");
|
|
522
|
+
}
|
|
523
|
+
}
|