linkgress-orm 0.0.1
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/LICENSE +21 -0
- package/README.md +196 -0
- package/dist/database/database-client.interface.d.ts +45 -0
- package/dist/database/database-client.interface.d.ts.map +1 -0
- package/dist/database/database-client.interface.js +20 -0
- package/dist/database/database-client.interface.js.map +1 -0
- package/dist/database/index.d.ts +5 -0
- package/dist/database/index.d.ts.map +1 -0
- package/dist/database/index.js +10 -0
- package/dist/database/index.js.map +1 -0
- package/dist/database/pg-client.d.ts +30 -0
- package/dist/database/pg-client.d.ts.map +1 -0
- package/dist/database/pg-client.js +76 -0
- package/dist/database/pg-client.js.map +1 -0
- package/dist/database/postgres-client.d.ts +44 -0
- package/dist/database/postgres-client.d.ts.map +1 -0
- package/dist/database/postgres-client.js +111 -0
- package/dist/database/postgres-client.js.map +1 -0
- package/dist/database/types.d.ts +200 -0
- package/dist/database/types.d.ts.map +1 -0
- package/dist/database/types.js +8 -0
- package/dist/database/types.js.map +1 -0
- package/dist/entity/base-entity.d.ts +21 -0
- package/dist/entity/base-entity.d.ts.map +1 -0
- package/dist/entity/base-entity.js +27 -0
- package/dist/entity/base-entity.js.map +1 -0
- package/dist/entity/db-column.d.ts +61 -0
- package/dist/entity/db-column.d.ts.map +1 -0
- package/dist/entity/db-column.js +35 -0
- package/dist/entity/db-column.js.map +1 -0
- package/dist/entity/db-context.d.ts +665 -0
- package/dist/entity/db-context.d.ts.map +1 -0
- package/dist/entity/db-context.js +1463 -0
- package/dist/entity/db-context.js.map +1 -0
- package/dist/entity/entity-base.d.ts +76 -0
- package/dist/entity/entity-base.d.ts.map +1 -0
- package/dist/entity/entity-base.js +42 -0
- package/dist/entity/entity-base.js.map +1 -0
- package/dist/entity/entity-builder.d.ts +171 -0
- package/dist/entity/entity-builder.d.ts.map +1 -0
- package/dist/entity/entity-builder.js +376 -0
- package/dist/entity/entity-builder.js.map +1 -0
- package/dist/entity/model-config.d.ts +18 -0
- package/dist/entity/model-config.d.ts.map +1 -0
- package/dist/entity/model-config.js +157 -0
- package/dist/entity/model-config.js.map +1 -0
- package/dist/index.d.ts +27 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +142 -0
- package/dist/index.js.map +1 -0
- package/dist/migration/db-schema-manager.d.ts +228 -0
- package/dist/migration/db-schema-manager.d.ts.map +1 -0
- package/dist/migration/db-schema-manager.js +1055 -0
- package/dist/migration/db-schema-manager.js.map +1 -0
- package/dist/migration/enum-migrator.d.ts +29 -0
- package/dist/migration/enum-migrator.d.ts.map +1 -0
- package/dist/migration/enum-migrator.js +137 -0
- package/dist/migration/enum-migrator.js.map +1 -0
- package/dist/query/collection-strategy.factory.d.ts +16 -0
- package/dist/query/collection-strategy.factory.d.ts.map +1 -0
- package/dist/query/collection-strategy.factory.js +37 -0
- package/dist/query/collection-strategy.factory.js.map +1 -0
- package/dist/query/collection-strategy.interface.d.ts +146 -0
- package/dist/query/collection-strategy.interface.d.ts.map +1 -0
- package/dist/query/collection-strategy.interface.js +3 -0
- package/dist/query/collection-strategy.interface.js.map +1 -0
- package/dist/query/conditions.d.ts +222 -0
- package/dist/query/conditions.d.ts.map +1 -0
- package/dist/query/conditions.js +446 -0
- package/dist/query/conditions.js.map +1 -0
- package/dist/query/cte-builder.d.ts +95 -0
- package/dist/query/cte-builder.d.ts.map +1 -0
- package/dist/query/cte-builder.js +172 -0
- package/dist/query/cte-builder.js.map +1 -0
- package/dist/query/grouped-query.d.ts +186 -0
- package/dist/query/grouped-query.d.ts.map +1 -0
- package/dist/query/grouped-query.js +588 -0
- package/dist/query/grouped-query.js.map +1 -0
- package/dist/query/join-builder.d.ts +106 -0
- package/dist/query/join-builder.d.ts.map +1 -0
- package/dist/query/join-builder.js +275 -0
- package/dist/query/join-builder.js.map +1 -0
- package/dist/query/query-builder.d.ts +543 -0
- package/dist/query/query-builder.d.ts.map +1 -0
- package/dist/query/query-builder.js +2649 -0
- package/dist/query/query-builder.js.map +1 -0
- package/dist/query/strategies/jsonb-collection-strategy.d.ts +51 -0
- package/dist/query/strategies/jsonb-collection-strategy.d.ts.map +1 -0
- package/dist/query/strategies/jsonb-collection-strategy.js +210 -0
- package/dist/query/strategies/jsonb-collection-strategy.js.map +1 -0
- package/dist/query/strategies/temptable-collection-strategy.d.ts +95 -0
- package/dist/query/strategies/temptable-collection-strategy.d.ts.map +1 -0
- package/dist/query/strategies/temptable-collection-strategy.js +456 -0
- package/dist/query/strategies/temptable-collection-strategy.js.map +1 -0
- package/dist/query/subquery.d.ts +152 -0
- package/dist/query/subquery.d.ts.map +1 -0
- package/dist/query/subquery.js +206 -0
- package/dist/query/subquery.js.map +1 -0
- package/dist/schema/column-builder.d.ts +127 -0
- package/dist/schema/column-builder.d.ts.map +1 -0
- package/dist/schema/column-builder.js +184 -0
- package/dist/schema/column-builder.js.map +1 -0
- package/dist/schema/inference.d.ts +26 -0
- package/dist/schema/inference.d.ts.map +1 -0
- package/dist/schema/inference.js +3 -0
- package/dist/schema/inference.js.map +1 -0
- package/dist/schema/navigation.d.ts +215 -0
- package/dist/schema/navigation.d.ts.map +1 -0
- package/dist/schema/navigation.js +233 -0
- package/dist/schema/navigation.js.map +1 -0
- package/dist/schema/row-type.d.ts +26 -0
- package/dist/schema/row-type.d.ts.map +1 -0
- package/dist/schema/row-type.js +3 -0
- package/dist/schema/row-type.js.map +1 -0
- package/dist/schema/sequence-builder.d.ts +87 -0
- package/dist/schema/sequence-builder.d.ts.map +1 -0
- package/dist/schema/sequence-builder.js +123 -0
- package/dist/schema/sequence-builder.js.map +1 -0
- package/dist/schema/table-builder.d.ts +122 -0
- package/dist/schema/table-builder.d.ts.map +1 -0
- package/dist/schema/table-builder.js +132 -0
- package/dist/schema/table-builder.js.map +1 -0
- package/dist/schema/typed-schema.d.ts +22 -0
- package/dist/schema/typed-schema.d.ts.map +1 -0
- package/dist/schema/typed-schema.js +28 -0
- package/dist/schema/typed-schema.js.map +1 -0
- package/dist/types/column-types.d.ts +20 -0
- package/dist/types/column-types.d.ts.map +1 -0
- package/dist/types/column-types.js +14 -0
- package/dist/types/column-types.js.map +1 -0
- package/dist/types/custom-types.d.ts +85 -0
- package/dist/types/custom-types.d.ts.map +1 -0
- package/dist/types/custom-types.js +132 -0
- package/dist/types/custom-types.js.map +1 -0
- package/dist/types/enum-builder.d.ts +31 -0
- package/dist/types/enum-builder.d.ts.map +1 -0
- package/dist/types/enum-builder.js +46 -0
- package/dist/types/enum-builder.js.map +1 -0
- package/dist/types/metadata.d.ts +67 -0
- package/dist/types/metadata.d.ts.map +1 -0
- package/dist/types/metadata.js +57 -0
- package/dist/types/metadata.js.map +1 -0
- package/dist/types/type-mapper.d.ts +49 -0
- package/dist/types/type-mapper.d.ts.map +1 -0
- package/dist/types/type-mapper.js +49 -0
- package/dist/types/type-mapper.js.map +1 -0
- package/package.json +77 -0
|
@@ -0,0 +1,1463 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.DatabaseContext = exports.DbEntityTable = exports.EntityInsertBuilder = exports.DataContext = exports.TableAccessor = exports.InsertBuilder = exports.QueryExecutor = void 0;
|
|
4
|
+
const entity_base_1 = require("./entity-base");
|
|
5
|
+
const model_config_1 = require("./model-config");
|
|
6
|
+
const conditions_1 = require("../query/conditions");
|
|
7
|
+
const query_builder_1 = require("../query/query-builder");
|
|
8
|
+
const db_schema_manager_1 = require("../migration/db-schema-manager");
|
|
9
|
+
const sequence_builder_1 = require("../schema/sequence-builder");
|
|
10
|
+
/**
|
|
11
|
+
* Query executor with optional logging
|
|
12
|
+
*/
|
|
13
|
+
class QueryExecutor {
|
|
14
|
+
constructor(client, options = {}) {
|
|
15
|
+
this.client = client;
|
|
16
|
+
this.options = options;
|
|
17
|
+
}
|
|
18
|
+
async query(sql, params) {
|
|
19
|
+
const logger = this.options.logger || console.log;
|
|
20
|
+
const startTime = this.options.logExecutionTime ? performance.now() : 0;
|
|
21
|
+
if (this.options.logQueries) {
|
|
22
|
+
logger(`\n[SQL Query]`);
|
|
23
|
+
logger(sql.trim());
|
|
24
|
+
if (this.options.logParameters && params && params.length > 0) {
|
|
25
|
+
logger(`[Parameters] ${JSON.stringify(params)}`);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
try {
|
|
29
|
+
const result = await this.client.query(sql, params);
|
|
30
|
+
if (this.options.logExecutionTime) {
|
|
31
|
+
const duration = (performance.now() - startTime).toFixed(2);
|
|
32
|
+
logger(`[Execution Time] ${duration}ms`);
|
|
33
|
+
}
|
|
34
|
+
return result;
|
|
35
|
+
}
|
|
36
|
+
catch (error) {
|
|
37
|
+
if (this.options.logQueries) {
|
|
38
|
+
logger(`[SQL Error] ${error instanceof Error ? error.message : String(error)}`);
|
|
39
|
+
}
|
|
40
|
+
throw error;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Execute a multi-statement query using the simple protocol (no parameters)
|
|
45
|
+
* Only available for clients that support it (e.g., PostgresClient)
|
|
46
|
+
*/
|
|
47
|
+
async querySimple(sql) {
|
|
48
|
+
const logger = this.options.logger || console.log;
|
|
49
|
+
const startTime = this.options.logExecutionTime ? performance.now() : 0;
|
|
50
|
+
if (this.options.logQueries) {
|
|
51
|
+
logger(`\n[SQL Query - Multi-Statement]`);
|
|
52
|
+
logger(sql.trim());
|
|
53
|
+
}
|
|
54
|
+
try {
|
|
55
|
+
// Check if client has querySimple method
|
|
56
|
+
if ('querySimple' in this.client && typeof this.client.querySimple === 'function') {
|
|
57
|
+
const result = await this.client.querySimple(sql);
|
|
58
|
+
if (this.options.logExecutionTime) {
|
|
59
|
+
const duration = (performance.now() - startTime).toFixed(2);
|
|
60
|
+
logger(`[Execution Time] ${duration}ms`);
|
|
61
|
+
}
|
|
62
|
+
return result;
|
|
63
|
+
}
|
|
64
|
+
else {
|
|
65
|
+
// Fallback to regular query
|
|
66
|
+
const result = await this.client.query(sql, []);
|
|
67
|
+
if (this.options.logExecutionTime) {
|
|
68
|
+
const duration = (performance.now() - startTime).toFixed(2);
|
|
69
|
+
logger(`[Execution Time] ${duration}ms`);
|
|
70
|
+
}
|
|
71
|
+
return result;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
catch (error) {
|
|
75
|
+
if (this.options.logQueries) {
|
|
76
|
+
logger(`[SQL Error] ${error instanceof Error ? error.message : String(error)}`);
|
|
77
|
+
}
|
|
78
|
+
throw error;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* Execute a multi-statement query and return ALL result sets
|
|
83
|
+
* Only available for PostgresClient
|
|
84
|
+
*/
|
|
85
|
+
async querySimpleMulti(sql) {
|
|
86
|
+
const logger = this.options.logger || console.log;
|
|
87
|
+
const startTime = this.options.logExecutionTime ? performance.now() : 0;
|
|
88
|
+
if (this.options.logQueries) {
|
|
89
|
+
logger(`\n[SQL Query - Fully Optimized Multi-Statement]`);
|
|
90
|
+
logger(sql.trim());
|
|
91
|
+
}
|
|
92
|
+
try {
|
|
93
|
+
// Check if client has querySimpleMulti method
|
|
94
|
+
if ('querySimpleMulti' in this.client && typeof this.client.querySimpleMulti === 'function') {
|
|
95
|
+
const results = await this.client.querySimpleMulti(sql);
|
|
96
|
+
if (this.options.logExecutionTime) {
|
|
97
|
+
const duration = (performance.now() - startTime).toFixed(2);
|
|
98
|
+
logger(`[Execution Time] ${duration}ms`);
|
|
99
|
+
}
|
|
100
|
+
return results;
|
|
101
|
+
}
|
|
102
|
+
else {
|
|
103
|
+
throw new Error('querySimpleMulti not supported by this client');
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
catch (error) {
|
|
107
|
+
if (this.options.logQueries) {
|
|
108
|
+
logger(`[SQL Error] ${error instanceof Error ? error.message : String(error)}`);
|
|
109
|
+
}
|
|
110
|
+
throw error;
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
exports.QueryExecutor = QueryExecutor;
|
|
115
|
+
/**
|
|
116
|
+
* Insert builder for upsert operations
|
|
117
|
+
*/
|
|
118
|
+
class InsertBuilder {
|
|
119
|
+
constructor(schema, client, executor) {
|
|
120
|
+
this.schema = schema;
|
|
121
|
+
this.client = client;
|
|
122
|
+
this.executor = executor;
|
|
123
|
+
this.dataArray = [];
|
|
124
|
+
this.conflictAction = 'nothing';
|
|
125
|
+
this.overridingSystemValue = false;
|
|
126
|
+
}
|
|
127
|
+
/**
|
|
128
|
+
* Get qualified table name with schema prefix if specified
|
|
129
|
+
*/
|
|
130
|
+
getQualifiedTableName() {
|
|
131
|
+
return this.schema.schema
|
|
132
|
+
? `"${this.schema.schema}"."${this.schema.name}"`
|
|
133
|
+
: `"${this.schema.name}"`;
|
|
134
|
+
}
|
|
135
|
+
/**
|
|
136
|
+
* Set the values to insert (single row or multiple rows)
|
|
137
|
+
*/
|
|
138
|
+
values(data) {
|
|
139
|
+
this.dataArray = Array.isArray(data) ? data : [data];
|
|
140
|
+
return this;
|
|
141
|
+
}
|
|
142
|
+
/**
|
|
143
|
+
* Specify conflict target (columns or constraint name)
|
|
144
|
+
*/
|
|
145
|
+
onConflict(target) {
|
|
146
|
+
if (Array.isArray(target)) {
|
|
147
|
+
this.conflictTarget = { columns: target };
|
|
148
|
+
}
|
|
149
|
+
else {
|
|
150
|
+
this.conflictTarget = target;
|
|
151
|
+
}
|
|
152
|
+
return this;
|
|
153
|
+
}
|
|
154
|
+
/**
|
|
155
|
+
* Do nothing on conflict
|
|
156
|
+
*/
|
|
157
|
+
doNothing() {
|
|
158
|
+
this.conflictAction = 'nothing';
|
|
159
|
+
return this;
|
|
160
|
+
}
|
|
161
|
+
/**
|
|
162
|
+
* Update on conflict (upsert)
|
|
163
|
+
*/
|
|
164
|
+
doUpdate(options) {
|
|
165
|
+
this.conflictAction = 'update';
|
|
166
|
+
if (options?.set) {
|
|
167
|
+
this.updateColumns = Object.keys(options.set);
|
|
168
|
+
}
|
|
169
|
+
if (options?.updateColumns) {
|
|
170
|
+
this.updateColumns = options.updateColumns;
|
|
171
|
+
}
|
|
172
|
+
if (options?.updateColumnFilter) {
|
|
173
|
+
this.updateColumnFilter = options.updateColumnFilter;
|
|
174
|
+
}
|
|
175
|
+
if (options?.where) {
|
|
176
|
+
this.setWhereClause = options.where;
|
|
177
|
+
}
|
|
178
|
+
return this;
|
|
179
|
+
}
|
|
180
|
+
/**
|
|
181
|
+
* Set target WHERE clause for ON CONFLICT
|
|
182
|
+
*/
|
|
183
|
+
targetWhere(where) {
|
|
184
|
+
this.targetWhereClause = where;
|
|
185
|
+
return this;
|
|
186
|
+
}
|
|
187
|
+
/**
|
|
188
|
+
* Enable OVERRIDING SYSTEM VALUE
|
|
189
|
+
*/
|
|
190
|
+
setOverridingSystemValue(value = true) {
|
|
191
|
+
this.overridingSystemValue = value;
|
|
192
|
+
return this;
|
|
193
|
+
}
|
|
194
|
+
/**
|
|
195
|
+
* Execute the insert/upsert
|
|
196
|
+
*/
|
|
197
|
+
async execute() {
|
|
198
|
+
if (this.dataArray.length === 0) {
|
|
199
|
+
return [];
|
|
200
|
+
}
|
|
201
|
+
// Extract all unique column names from all data objects
|
|
202
|
+
const columnSet = new Set();
|
|
203
|
+
for (const data of this.dataArray) {
|
|
204
|
+
for (const key of Object.keys(data)) {
|
|
205
|
+
const column = this.schema.columns[key];
|
|
206
|
+
if (column) {
|
|
207
|
+
const config = column.build();
|
|
208
|
+
if (!config.autoIncrement) {
|
|
209
|
+
columnSet.add(key);
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
const columns = Array.from(columnSet);
|
|
215
|
+
const values = [];
|
|
216
|
+
const valuePlaceholders = [];
|
|
217
|
+
let paramIndex = 1;
|
|
218
|
+
// Build placeholders for each row
|
|
219
|
+
for (const data of this.dataArray) {
|
|
220
|
+
const rowPlaceholders = [];
|
|
221
|
+
for (const key of columns) {
|
|
222
|
+
const value = data[key];
|
|
223
|
+
const column = this.schema.columns[key];
|
|
224
|
+
const config = column.build();
|
|
225
|
+
// Apply toDriver mapper if present
|
|
226
|
+
const mappedValue = config.mapper
|
|
227
|
+
? config.mapper.toDriver(value !== undefined ? value : null)
|
|
228
|
+
: (value !== undefined ? value : null);
|
|
229
|
+
values.push(mappedValue);
|
|
230
|
+
rowPlaceholders.push(`$${paramIndex++}`);
|
|
231
|
+
}
|
|
232
|
+
valuePlaceholders.push(`(${rowPlaceholders.join(', ')})`);
|
|
233
|
+
}
|
|
234
|
+
const columnNames = columns.map(key => {
|
|
235
|
+
const column = this.schema.columns[key];
|
|
236
|
+
const config = column.build();
|
|
237
|
+
return `"${config.name}"`;
|
|
238
|
+
});
|
|
239
|
+
const returningColumns = Object.entries(this.schema.columns)
|
|
240
|
+
.map(([_, col]) => `"${col.build().name}"`)
|
|
241
|
+
.join(', ');
|
|
242
|
+
const qualifiedTableName = this.getQualifiedTableName();
|
|
243
|
+
let sql = `INSERT INTO ${qualifiedTableName} (${columnNames.join(', ')})`;
|
|
244
|
+
// Add OVERRIDING SYSTEM VALUE if specified
|
|
245
|
+
if (this.overridingSystemValue) {
|
|
246
|
+
sql += '\n OVERRIDING SYSTEM VALUE';
|
|
247
|
+
}
|
|
248
|
+
sql += `\n VALUES ${valuePlaceholders.join(', ')}`;
|
|
249
|
+
// Add conflict handling
|
|
250
|
+
if (this.conflictTarget || this.conflictAction) {
|
|
251
|
+
sql += '\n ON CONFLICT';
|
|
252
|
+
if (this.conflictTarget) {
|
|
253
|
+
if (this.conflictTarget.columns) {
|
|
254
|
+
const conflictCols = this.conflictTarget.columns.map(c => `"${c}"`).join(', ');
|
|
255
|
+
sql += ` (${conflictCols})`;
|
|
256
|
+
}
|
|
257
|
+
else if (this.conflictTarget.constraint) {
|
|
258
|
+
sql += ` ON CONSTRAINT ${this.conflictTarget.constraint}`;
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
// Add target WHERE clause
|
|
262
|
+
if (this.targetWhereClause) {
|
|
263
|
+
sql += ` WHERE ${this.targetWhereClause}`;
|
|
264
|
+
}
|
|
265
|
+
if (this.conflictAction === 'nothing') {
|
|
266
|
+
sql += ' DO NOTHING';
|
|
267
|
+
}
|
|
268
|
+
else if (this.conflictAction === 'update') {
|
|
269
|
+
sql += ' DO UPDATE SET ';
|
|
270
|
+
// Determine which columns to update
|
|
271
|
+
let columnsToUpdate;
|
|
272
|
+
if (this.updateColumns) {
|
|
273
|
+
// Use explicitly specified columns
|
|
274
|
+
columnsToUpdate = this.updateColumns;
|
|
275
|
+
}
|
|
276
|
+
else if (this.updateColumnFilter) {
|
|
277
|
+
// Use filter function
|
|
278
|
+
columnsToUpdate = columns.filter(this.updateColumnFilter);
|
|
279
|
+
}
|
|
280
|
+
else {
|
|
281
|
+
// Update all non-primary key columns
|
|
282
|
+
columnsToUpdate = columns.filter(key => {
|
|
283
|
+
const column = this.schema.columns[key];
|
|
284
|
+
const config = column.build();
|
|
285
|
+
return !config.primaryKey;
|
|
286
|
+
});
|
|
287
|
+
}
|
|
288
|
+
const updateParts = columnsToUpdate.map(col => {
|
|
289
|
+
const column = this.schema.columns[col];
|
|
290
|
+
const config = column.build();
|
|
291
|
+
return `"${config.name}" = EXCLUDED."${config.name}"`;
|
|
292
|
+
});
|
|
293
|
+
sql += updateParts.join(', ');
|
|
294
|
+
if (this.setWhereClause) {
|
|
295
|
+
sql += ` WHERE ${this.setWhereClause}`;
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
sql += `\n RETURNING ${returningColumns}`;
|
|
300
|
+
const result = this.executor
|
|
301
|
+
? await this.executor.query(sql, values)
|
|
302
|
+
: await this.client.query(sql, values);
|
|
303
|
+
return result.rows;
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
exports.InsertBuilder = InsertBuilder;
|
|
307
|
+
/**
|
|
308
|
+
* Table accessor with query methods
|
|
309
|
+
*/
|
|
310
|
+
class TableAccessor {
|
|
311
|
+
constructor(tableBuilder, client, schemaRegistry, executor, collectionStrategy) {
|
|
312
|
+
this.tableBuilder = tableBuilder;
|
|
313
|
+
this.client = client;
|
|
314
|
+
this.schemaRegistry = schemaRegistry;
|
|
315
|
+
this.executor = executor;
|
|
316
|
+
this.collectionStrategy = collectionStrategy;
|
|
317
|
+
this.schema = tableBuilder.build();
|
|
318
|
+
}
|
|
319
|
+
/**
|
|
320
|
+
* Configure query options for the current query chain
|
|
321
|
+
* Returns a new TableAccessor instance with the specified options
|
|
322
|
+
*
|
|
323
|
+
* @example
|
|
324
|
+
* ```typescript
|
|
325
|
+
* const results = await db.users
|
|
326
|
+
* .withQueryOptions({ logQueries: true, collectionStrategy: 'temptable' })
|
|
327
|
+
* .select(u => ({ id: u.id, name: u.username }))
|
|
328
|
+
* .toList();
|
|
329
|
+
* ```
|
|
330
|
+
*/
|
|
331
|
+
withQueryOptions(options) {
|
|
332
|
+
// Merge options with existing collectionStrategy
|
|
333
|
+
const mergedStrategy = options.collectionStrategy ?? this.collectionStrategy;
|
|
334
|
+
// Create new executor if logging options are provided
|
|
335
|
+
let newExecutor = this.executor;
|
|
336
|
+
if (options.logQueries || options.logExecutionTime) {
|
|
337
|
+
newExecutor = new QueryExecutor(this.client, {
|
|
338
|
+
...options,
|
|
339
|
+
collectionStrategy: mergedStrategy,
|
|
340
|
+
});
|
|
341
|
+
}
|
|
342
|
+
// Return new instance with updated options
|
|
343
|
+
return new TableAccessor(this.tableBuilder, this.client, this.schemaRegistry, newExecutor, mergedStrategy);
|
|
344
|
+
}
|
|
345
|
+
/**
|
|
346
|
+
* Start a select query with automatic type inference
|
|
347
|
+
*/
|
|
348
|
+
select(selector) {
|
|
349
|
+
return new query_builder_1.SelectQueryBuilder(this.schema, this.client, selector, undefined, undefined, undefined, undefined, this.executor, undefined, undefined, undefined, this.schemaRegistry, undefined, this.collectionStrategy);
|
|
350
|
+
}
|
|
351
|
+
/**
|
|
352
|
+
* Add WHERE condition before select
|
|
353
|
+
*/
|
|
354
|
+
where(condition) {
|
|
355
|
+
const qb = new query_builder_1.QueryBuilder(this.schema, this.client, undefined, undefined, undefined, undefined, this.executor, undefined, undefined, this.collectionStrategy);
|
|
356
|
+
return qb.where(condition);
|
|
357
|
+
}
|
|
358
|
+
/**
|
|
359
|
+
* Left join with another table and selector
|
|
360
|
+
*/
|
|
361
|
+
leftJoin(rightTable, condition, selector, alias) {
|
|
362
|
+
const qb = new query_builder_1.QueryBuilder(this.schema, this.client, undefined, undefined, undefined, undefined, this.executor, undefined, undefined, this.collectionStrategy);
|
|
363
|
+
return qb.leftJoin(rightTable, condition, selector, alias);
|
|
364
|
+
}
|
|
365
|
+
/**
|
|
366
|
+
* Inner join with another table or subquery and selector
|
|
367
|
+
*/
|
|
368
|
+
innerJoin(rightTable, condition, selector, alias) {
|
|
369
|
+
const qb = new query_builder_1.QueryBuilder(this.schema, this.client, undefined, undefined, undefined, undefined, this.executor, undefined, undefined, this.collectionStrategy);
|
|
370
|
+
return qb.innerJoin(rightTable, condition, selector, alias);
|
|
371
|
+
}
|
|
372
|
+
/**
|
|
373
|
+
* Get table schema (internal use for joins)
|
|
374
|
+
*/
|
|
375
|
+
_getSchema() {
|
|
376
|
+
return this.schema;
|
|
377
|
+
}
|
|
378
|
+
/**
|
|
379
|
+
* Get table schema
|
|
380
|
+
*/
|
|
381
|
+
getSchema() {
|
|
382
|
+
return this.schema;
|
|
383
|
+
}
|
|
384
|
+
/**
|
|
385
|
+
* Get table name
|
|
386
|
+
*/
|
|
387
|
+
getTableName() {
|
|
388
|
+
return this.schema.name;
|
|
389
|
+
}
|
|
390
|
+
/**
|
|
391
|
+
* Get qualified table name with schema prefix if specified
|
|
392
|
+
*/
|
|
393
|
+
getQualifiedTableName() {
|
|
394
|
+
return this.schema.schema
|
|
395
|
+
? `"${this.schema.schema}"."${this.schema.name}"`
|
|
396
|
+
: `"${this.schema.name}"`;
|
|
397
|
+
}
|
|
398
|
+
/**
|
|
399
|
+
* Insert a row
|
|
400
|
+
*/
|
|
401
|
+
async insert(data) {
|
|
402
|
+
const columns = [];
|
|
403
|
+
const values = [];
|
|
404
|
+
const placeholders = [];
|
|
405
|
+
let paramIndex = 1;
|
|
406
|
+
for (const [key, value] of Object.entries(data)) {
|
|
407
|
+
const column = this.schema.columns[key];
|
|
408
|
+
if (column) {
|
|
409
|
+
const config = column.build();
|
|
410
|
+
if (!config.autoIncrement) {
|
|
411
|
+
columns.push(`"${config.name}"`);
|
|
412
|
+
// Apply toDriver mapper if present
|
|
413
|
+
const mappedValue = config.mapper
|
|
414
|
+
? config.mapper.toDriver(value)
|
|
415
|
+
: value;
|
|
416
|
+
values.push(mappedValue);
|
|
417
|
+
placeholders.push(`$${paramIndex++}`);
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
const returningColumns = Object.entries(this.schema.columns)
|
|
422
|
+
.map(([_, col]) => `"${col.build().name}"`)
|
|
423
|
+
.join(', ');
|
|
424
|
+
const qualifiedTableName = this.getQualifiedTableName();
|
|
425
|
+
const sql = `
|
|
426
|
+
INSERT INTO ${qualifiedTableName} (${columns.join(', ')})
|
|
427
|
+
VALUES (${placeholders.join(', ')})
|
|
428
|
+
RETURNING ${returningColumns}
|
|
429
|
+
`;
|
|
430
|
+
const result = this.executor
|
|
431
|
+
? await this.executor.query(sql, values)
|
|
432
|
+
: await this.client.query(sql, values);
|
|
433
|
+
return result.rows[0];
|
|
434
|
+
}
|
|
435
|
+
/**
|
|
436
|
+
* Bulk insert with advanced configuration
|
|
437
|
+
*/
|
|
438
|
+
async insertBulk(value, insertConfig) {
|
|
439
|
+
const dataArray = Array.isArray(value) ? value : [value];
|
|
440
|
+
if (dataArray.length === 0) {
|
|
441
|
+
return [];
|
|
442
|
+
}
|
|
443
|
+
// Calculate chunk size based on max rows per batch
|
|
444
|
+
let chunkSize = insertConfig?.chunkSize;
|
|
445
|
+
if (chunkSize == null && dataArray.length > 0) {
|
|
446
|
+
const POSTGRES_MAX_PARAMS = 65535; // PostgreSQL parameter limit
|
|
447
|
+
const columnCount = Object.keys(dataArray[0]).length;
|
|
448
|
+
const maxRowsPerBatch = Math.floor(POSTGRES_MAX_PARAMS / columnCount);
|
|
449
|
+
chunkSize = Math.floor(maxRowsPerBatch * 0.6); // Use 60% of max to be safe
|
|
450
|
+
}
|
|
451
|
+
// Check if we need to chunk
|
|
452
|
+
if (chunkSize && dataArray.length > chunkSize) {
|
|
453
|
+
const results = [];
|
|
454
|
+
for (let i = 0; i < dataArray.length; i += chunkSize) {
|
|
455
|
+
const chunk = dataArray.slice(i, i + chunkSize);
|
|
456
|
+
const chunkResults = await this.insertBulkSingle(chunk, insertConfig);
|
|
457
|
+
results.push(...chunkResults);
|
|
458
|
+
}
|
|
459
|
+
return results;
|
|
460
|
+
}
|
|
461
|
+
else {
|
|
462
|
+
return this.insertBulkSingle(dataArray, insertConfig);
|
|
463
|
+
}
|
|
464
|
+
}
|
|
465
|
+
/**
|
|
466
|
+
* Insert a single chunk (internal method)
|
|
467
|
+
*/
|
|
468
|
+
async insertBulkSingle(dataArray, insertConfig) {
|
|
469
|
+
if (dataArray.length === 0) {
|
|
470
|
+
return [];
|
|
471
|
+
}
|
|
472
|
+
// Extract all unique column names from all data objects
|
|
473
|
+
const columnSet = new Set();
|
|
474
|
+
for (const data of dataArray) {
|
|
475
|
+
for (const key of Object.keys(data)) {
|
|
476
|
+
const column = this.schema.columns[key];
|
|
477
|
+
if (column) {
|
|
478
|
+
const config = column.build();
|
|
479
|
+
if (!config.autoIncrement || insertConfig?.overridingSystemValue) {
|
|
480
|
+
columnSet.add(key);
|
|
481
|
+
}
|
|
482
|
+
}
|
|
483
|
+
}
|
|
484
|
+
}
|
|
485
|
+
const columns = Array.from(columnSet);
|
|
486
|
+
const values = [];
|
|
487
|
+
const valuePlaceholders = [];
|
|
488
|
+
let paramIndex = 1;
|
|
489
|
+
// Build placeholders for each row
|
|
490
|
+
for (const data of dataArray) {
|
|
491
|
+
const rowPlaceholders = [];
|
|
492
|
+
for (const key of columns) {
|
|
493
|
+
const value = data[key];
|
|
494
|
+
const column = this.schema.columns[key];
|
|
495
|
+
const config = column.build();
|
|
496
|
+
// Apply toDriver mapper if present
|
|
497
|
+
const mappedValue = config.mapper
|
|
498
|
+
? config.mapper.toDriver(value !== undefined ? value : null)
|
|
499
|
+
: (value !== undefined ? value : null);
|
|
500
|
+
values.push(mappedValue);
|
|
501
|
+
rowPlaceholders.push(`$${paramIndex++}`);
|
|
502
|
+
}
|
|
503
|
+
valuePlaceholders.push(`(${rowPlaceholders.join(', ')})`);
|
|
504
|
+
}
|
|
505
|
+
const columnNames = columns.map(key => {
|
|
506
|
+
const column = this.schema.columns[key];
|
|
507
|
+
const config = column.build();
|
|
508
|
+
return `"${config.name}"`;
|
|
509
|
+
});
|
|
510
|
+
const returningColumns = Object.entries(this.schema.columns)
|
|
511
|
+
.map(([_, col]) => `"${col.build().name}"`)
|
|
512
|
+
.join(', ');
|
|
513
|
+
const qualifiedTableName = this.getQualifiedTableName();
|
|
514
|
+
let sql = `INSERT INTO ${qualifiedTableName} (${columnNames.join(', ')})`;
|
|
515
|
+
if (insertConfig?.overridingSystemValue) {
|
|
516
|
+
sql += '\n OVERRIDING SYSTEM VALUE';
|
|
517
|
+
}
|
|
518
|
+
sql += `\n VALUES ${valuePlaceholders.join(', ')}
|
|
519
|
+
RETURNING ${returningColumns}`;
|
|
520
|
+
const result = this.executor
|
|
521
|
+
? await this.executor.query(sql, values)
|
|
522
|
+
: await this.client.query(sql, values);
|
|
523
|
+
return result.rows;
|
|
524
|
+
}
|
|
525
|
+
/**
|
|
526
|
+
* Upsert with advanced configuration
|
|
527
|
+
*/
|
|
528
|
+
async upsertBulk(values, config) {
|
|
529
|
+
if (values.length === 0) {
|
|
530
|
+
return [];
|
|
531
|
+
}
|
|
532
|
+
const referenceItem = config?.referenceItem || values[0];
|
|
533
|
+
// Determine primary keys
|
|
534
|
+
let primaryKeys = [];
|
|
535
|
+
if (config?.primaryKey) {
|
|
536
|
+
primaryKeys = Array.isArray(config.primaryKey) ? config.primaryKey : [config.primaryKey];
|
|
537
|
+
}
|
|
538
|
+
else {
|
|
539
|
+
// Auto-detect from schema
|
|
540
|
+
for (const [key, colBuilder] of Object.entries(this.schema.columns)) {
|
|
541
|
+
const colConfig = colBuilder.build();
|
|
542
|
+
if (colConfig.primaryKey) {
|
|
543
|
+
primaryKeys.push(key);
|
|
544
|
+
}
|
|
545
|
+
}
|
|
546
|
+
}
|
|
547
|
+
// Auto-detect overridingSystemValue
|
|
548
|
+
let overridingSystemValue = config?.overridingSystemValue;
|
|
549
|
+
if (overridingSystemValue == null) {
|
|
550
|
+
for (const key of Object.keys(referenceItem)) {
|
|
551
|
+
const column = this.schema.columns[key];
|
|
552
|
+
if (column) {
|
|
553
|
+
const colConfig = column.build();
|
|
554
|
+
if (colConfig.primaryKey && colConfig.autoIncrement) {
|
|
555
|
+
overridingSystemValue = true;
|
|
556
|
+
break;
|
|
557
|
+
}
|
|
558
|
+
}
|
|
559
|
+
}
|
|
560
|
+
}
|
|
561
|
+
// Determine which columns to update
|
|
562
|
+
let updateColumnFilter = config?.updateColumnFilter;
|
|
563
|
+
if (updateColumnFilter == null && config?.updateColumns) {
|
|
564
|
+
const updateColSet = new Set(config.updateColumns);
|
|
565
|
+
updateColumnFilter = (colId) => updateColSet.has(colId);
|
|
566
|
+
}
|
|
567
|
+
if (updateColumnFilter == null) {
|
|
568
|
+
updateColumnFilter = (colId) => !primaryKeys.includes(colId);
|
|
569
|
+
}
|
|
570
|
+
// Calculate chunk size based on max rows per batch
|
|
571
|
+
let chunkSize = config?.chunkSize;
|
|
572
|
+
if (chunkSize == null && values.length > 0) {
|
|
573
|
+
const POSTGRES_MAX_PARAMS = 65535;
|
|
574
|
+
const columnCount = Object.keys(values[0]).length;
|
|
575
|
+
const maxRowsPerBatch = Math.floor(POSTGRES_MAX_PARAMS / columnCount);
|
|
576
|
+
chunkSize = Math.floor(maxRowsPerBatch * 0.6); // Use 60% of max to be safe
|
|
577
|
+
}
|
|
578
|
+
// Check if we need to chunk
|
|
579
|
+
if (chunkSize && values.length > chunkSize) {
|
|
580
|
+
const results = [];
|
|
581
|
+
for (let i = 0; i < values.length; i += chunkSize) {
|
|
582
|
+
const chunk = values.slice(i, i + chunkSize);
|
|
583
|
+
const chunkResults = await this.upsertBulkSingle(chunk, primaryKeys, updateColumnFilter, overridingSystemValue || false, config?.targetWhere, config?.setWhere);
|
|
584
|
+
results.push(...chunkResults);
|
|
585
|
+
}
|
|
586
|
+
return results;
|
|
587
|
+
}
|
|
588
|
+
else {
|
|
589
|
+
return this.upsertBulkSingle(values, primaryKeys, updateColumnFilter, overridingSystemValue || false, config?.targetWhere, config?.setWhere);
|
|
590
|
+
}
|
|
591
|
+
}
|
|
592
|
+
/**
|
|
593
|
+
* Upsert a single chunk (internal method)
|
|
594
|
+
*/
|
|
595
|
+
async upsertBulkSingle(values, primaryKeys, updateColumnFilter, overridingSystemValue, targetWhere, setWhere) {
|
|
596
|
+
const builder = new InsertBuilder(this.schema, this.client, this.executor);
|
|
597
|
+
builder.values(values);
|
|
598
|
+
builder.setOverridingSystemValue(overridingSystemValue);
|
|
599
|
+
builder.onConflict({ columns: primaryKeys });
|
|
600
|
+
builder.doUpdate({ updateColumnFilter, where: setWhere });
|
|
601
|
+
if (targetWhere) {
|
|
602
|
+
builder.targetWhere(targetWhere);
|
|
603
|
+
}
|
|
604
|
+
return builder.execute();
|
|
605
|
+
}
|
|
606
|
+
/**
|
|
607
|
+
* Bulk insert multiple rows (simple version, kept for compatibility)
|
|
608
|
+
*/
|
|
609
|
+
async insertMany(dataArray) {
|
|
610
|
+
if (dataArray.length === 0) {
|
|
611
|
+
return [];
|
|
612
|
+
}
|
|
613
|
+
// Extract all unique column names from all data objects
|
|
614
|
+
const columnSet = new Set();
|
|
615
|
+
for (const data of dataArray) {
|
|
616
|
+
for (const key of Object.keys(data)) {
|
|
617
|
+
const column = this.schema.columns[key];
|
|
618
|
+
if (column) {
|
|
619
|
+
const config = column.build();
|
|
620
|
+
if (!config.autoIncrement) {
|
|
621
|
+
columnSet.add(key);
|
|
622
|
+
}
|
|
623
|
+
}
|
|
624
|
+
}
|
|
625
|
+
}
|
|
626
|
+
const columns = Array.from(columnSet);
|
|
627
|
+
const values = [];
|
|
628
|
+
const valuePlaceholders = [];
|
|
629
|
+
let paramIndex = 1;
|
|
630
|
+
// Build placeholders for each row
|
|
631
|
+
for (const data of dataArray) {
|
|
632
|
+
const rowPlaceholders = [];
|
|
633
|
+
for (const key of columns) {
|
|
634
|
+
const value = data[key];
|
|
635
|
+
const column = this.schema.columns[key];
|
|
636
|
+
const config = column.build();
|
|
637
|
+
// Apply toDriver mapper if present
|
|
638
|
+
const mappedValue = config.mapper
|
|
639
|
+
? config.mapper.toDriver(value !== undefined ? value : null)
|
|
640
|
+
: (value !== undefined ? value : null);
|
|
641
|
+
values.push(mappedValue);
|
|
642
|
+
rowPlaceholders.push(`$${paramIndex++}`);
|
|
643
|
+
}
|
|
644
|
+
valuePlaceholders.push(`(${rowPlaceholders.join(', ')})`);
|
|
645
|
+
}
|
|
646
|
+
const columnNames = columns.map(key => {
|
|
647
|
+
const column = this.schema.columns[key];
|
|
648
|
+
const config = column.build();
|
|
649
|
+
return `"${config.name}"`;
|
|
650
|
+
});
|
|
651
|
+
const returningColumns = Object.entries(this.schema.columns)
|
|
652
|
+
.map(([_, col]) => `"${col.build().name}"`)
|
|
653
|
+
.join(', ');
|
|
654
|
+
const qualifiedTableName = this.getQualifiedTableName();
|
|
655
|
+
const sql = `
|
|
656
|
+
INSERT INTO ${qualifiedTableName} (${columnNames.join(', ')})
|
|
657
|
+
VALUES ${valuePlaceholders.join(', ')}
|
|
658
|
+
RETURNING ${returningColumns}
|
|
659
|
+
`;
|
|
660
|
+
const result = this.executor
|
|
661
|
+
? await this.executor.query(sql, values)
|
|
662
|
+
: await this.client.query(sql, values);
|
|
663
|
+
return result.rows;
|
|
664
|
+
}
|
|
665
|
+
/**
|
|
666
|
+
* Insert with conflict resolution (upsert)
|
|
667
|
+
*/
|
|
668
|
+
onConflictDoNothing() {
|
|
669
|
+
return new InsertBuilder(this.schema, this.client, this.executor);
|
|
670
|
+
}
|
|
671
|
+
/**
|
|
672
|
+
* Insert with conflict resolution (upsert) - start building the upsert query
|
|
673
|
+
*/
|
|
674
|
+
values(data) {
|
|
675
|
+
const builder = new InsertBuilder(this.schema, this.client, this.executor);
|
|
676
|
+
return builder.values(data);
|
|
677
|
+
}
|
|
678
|
+
/**
|
|
679
|
+
* Update rows
|
|
680
|
+
*/
|
|
681
|
+
async update(id, data) {
|
|
682
|
+
const setClauses = [];
|
|
683
|
+
const values = [];
|
|
684
|
+
let paramIndex = 1;
|
|
685
|
+
// Find primary key
|
|
686
|
+
let pkColumnName;
|
|
687
|
+
for (const [key, col] of Object.entries(this.schema.columns)) {
|
|
688
|
+
const config = col.build();
|
|
689
|
+
if (config.primaryKey) {
|
|
690
|
+
pkColumnName = config.name;
|
|
691
|
+
break;
|
|
692
|
+
}
|
|
693
|
+
}
|
|
694
|
+
if (!pkColumnName) {
|
|
695
|
+
throw new Error(`Table ${this.schema.name} has no primary key`);
|
|
696
|
+
}
|
|
697
|
+
for (const [key, value] of Object.entries(data)) {
|
|
698
|
+
const column = this.schema.columns[key];
|
|
699
|
+
if (column) {
|
|
700
|
+
const config = column.build();
|
|
701
|
+
if (!config.primaryKey) {
|
|
702
|
+
setClauses.push(`"${config.name}" = $${paramIndex++}`);
|
|
703
|
+
// Apply toDriver mapper if present
|
|
704
|
+
const mappedValue = config.mapper
|
|
705
|
+
? config.mapper.toDriver(value)
|
|
706
|
+
: value;
|
|
707
|
+
values.push(mappedValue);
|
|
708
|
+
}
|
|
709
|
+
}
|
|
710
|
+
}
|
|
711
|
+
if (setClauses.length === 0) {
|
|
712
|
+
return null;
|
|
713
|
+
}
|
|
714
|
+
values.push(id);
|
|
715
|
+
const returningColumns = Object.entries(this.schema.columns)
|
|
716
|
+
.map(([_, col]) => `"${col.build().name}"`)
|
|
717
|
+
.join(', ');
|
|
718
|
+
const qualifiedTableName = this.getQualifiedTableName();
|
|
719
|
+
const sql = `
|
|
720
|
+
UPDATE ${qualifiedTableName}
|
|
721
|
+
SET ${setClauses.join(', ')}
|
|
722
|
+
WHERE "${pkColumnName}" = $${paramIndex}
|
|
723
|
+
RETURNING ${returningColumns}
|
|
724
|
+
`;
|
|
725
|
+
const result = this.executor
|
|
726
|
+
? await this.executor.query(sql, values)
|
|
727
|
+
: await this.client.query(sql, values);
|
|
728
|
+
return result.rows.length > 0 ? result.rows[0] : null;
|
|
729
|
+
}
|
|
730
|
+
/**
|
|
731
|
+
* Delete a row by id
|
|
732
|
+
*/
|
|
733
|
+
async delete(id) {
|
|
734
|
+
// Find primary key
|
|
735
|
+
let pkColumnName;
|
|
736
|
+
for (const [key, col] of Object.entries(this.schema.columns)) {
|
|
737
|
+
const config = col.build();
|
|
738
|
+
if (config.primaryKey) {
|
|
739
|
+
pkColumnName = config.name;
|
|
740
|
+
break;
|
|
741
|
+
}
|
|
742
|
+
}
|
|
743
|
+
if (!pkColumnName) {
|
|
744
|
+
throw new Error(`Table ${this.schema.name} has no primary key`);
|
|
745
|
+
}
|
|
746
|
+
const qualifiedTableName = this.getQualifiedTableName();
|
|
747
|
+
const sql = `DELETE FROM ${qualifiedTableName} WHERE "${pkColumnName}" = $1`;
|
|
748
|
+
const result = this.executor
|
|
749
|
+
? await this.executor.query(sql, [id])
|
|
750
|
+
: await this.client.query(sql, [id]);
|
|
751
|
+
return result.rowCount !== null && result.rowCount > 0;
|
|
752
|
+
}
|
|
753
|
+
}
|
|
754
|
+
exports.TableAccessor = TableAccessor;
|
|
755
|
+
/**
|
|
756
|
+
* DataContext - main entry point for database operations
|
|
757
|
+
*/
|
|
758
|
+
class DataContext {
|
|
759
|
+
constructor(client, schema, queryOptions) {
|
|
760
|
+
this.schemaRegistry = new Map();
|
|
761
|
+
this.tableAccessors = new Map();
|
|
762
|
+
this.client = client;
|
|
763
|
+
this.queryOptions = queryOptions;
|
|
764
|
+
// Create executor if logging is enabled
|
|
765
|
+
if (queryOptions?.logQueries || queryOptions?.logExecutionTime) {
|
|
766
|
+
this.executor = new QueryExecutor(client, queryOptions);
|
|
767
|
+
}
|
|
768
|
+
this.initializeSchema(schema);
|
|
769
|
+
}
|
|
770
|
+
/**
|
|
771
|
+
* Initialize schema and create table accessors
|
|
772
|
+
*/
|
|
773
|
+
initializeSchema(schema) {
|
|
774
|
+
for (const [key, tableBuilder] of Object.entries(schema)) {
|
|
775
|
+
const tableSchema = tableBuilder.build();
|
|
776
|
+
this.schemaRegistry.set(tableSchema.name, tableSchema);
|
|
777
|
+
const accessor = new TableAccessor(tableBuilder, this.client, this.schemaRegistry, this.executor, this.queryOptions?.collectionStrategy);
|
|
778
|
+
this.tableAccessors.set(key, accessor);
|
|
779
|
+
// Attach to context (skip if property already has a getter on prototype chain)
|
|
780
|
+
const descriptor = Object.getOwnPropertyDescriptor(this, key) ||
|
|
781
|
+
Object.getOwnPropertyDescriptor(Object.getPrototypeOf(this), key);
|
|
782
|
+
if (!descriptor || !descriptor.get) {
|
|
783
|
+
this[key] = accessor;
|
|
784
|
+
}
|
|
785
|
+
}
|
|
786
|
+
}
|
|
787
|
+
/**
|
|
788
|
+
* Get table accessor by name
|
|
789
|
+
*/
|
|
790
|
+
getTable(name) {
|
|
791
|
+
const accessor = this.tableAccessors.get(name);
|
|
792
|
+
if (!accessor) {
|
|
793
|
+
throw new Error(`Table ${String(name)} not found in schema`);
|
|
794
|
+
}
|
|
795
|
+
return accessor;
|
|
796
|
+
}
|
|
797
|
+
/**
|
|
798
|
+
* Execute raw SQL
|
|
799
|
+
*/
|
|
800
|
+
async query(sql, params) {
|
|
801
|
+
return this.client.query(sql, params);
|
|
802
|
+
}
|
|
803
|
+
/**
|
|
804
|
+
* Execute in transaction
|
|
805
|
+
*/
|
|
806
|
+
async transaction(fn) {
|
|
807
|
+
const connection = await this.client.connect();
|
|
808
|
+
try {
|
|
809
|
+
await connection.query('BEGIN');
|
|
810
|
+
const result = await fn(this);
|
|
811
|
+
await connection.query('COMMIT');
|
|
812
|
+
return result;
|
|
813
|
+
}
|
|
814
|
+
catch (error) {
|
|
815
|
+
await connection.query('ROLLBACK');
|
|
816
|
+
throw error;
|
|
817
|
+
}
|
|
818
|
+
finally {
|
|
819
|
+
connection.release();
|
|
820
|
+
}
|
|
821
|
+
}
|
|
822
|
+
/**
|
|
823
|
+
* Get schema manager for create/drop operations and automatic migrations
|
|
824
|
+
*/
|
|
825
|
+
getSchemaManager() {
|
|
826
|
+
return new db_schema_manager_1.DbSchemaManager(this.client, this.schemaRegistry, { logQueries: this.queryOptions?.logQueries });
|
|
827
|
+
}
|
|
828
|
+
/**
|
|
829
|
+
* Close database connection
|
|
830
|
+
*/
|
|
831
|
+
async dispose() {
|
|
832
|
+
await this.client.end();
|
|
833
|
+
}
|
|
834
|
+
}
|
|
835
|
+
exports.DataContext = DataContext;
|
|
836
|
+
/**
|
|
837
|
+
* DbEntity insert builder for upsert operations with proper typing
|
|
838
|
+
*/
|
|
839
|
+
class EntityInsertBuilder {
|
|
840
|
+
constructor(builder) {
|
|
841
|
+
this.builder = builder;
|
|
842
|
+
}
|
|
843
|
+
/**
|
|
844
|
+
* Specify conflict target (columns or constraint name)
|
|
845
|
+
*/
|
|
846
|
+
onConflict(target) {
|
|
847
|
+
this.builder.onConflict(target);
|
|
848
|
+
return this;
|
|
849
|
+
}
|
|
850
|
+
/**
|
|
851
|
+
* Do nothing on conflict
|
|
852
|
+
*/
|
|
853
|
+
doNothing() {
|
|
854
|
+
this.builder.doNothing();
|
|
855
|
+
return this;
|
|
856
|
+
}
|
|
857
|
+
/**
|
|
858
|
+
* Update on conflict (upsert)
|
|
859
|
+
*/
|
|
860
|
+
doUpdate(options) {
|
|
861
|
+
this.builder.doUpdate(options);
|
|
862
|
+
return this;
|
|
863
|
+
}
|
|
864
|
+
/**
|
|
865
|
+
* Execute the insert/upsert
|
|
866
|
+
*/
|
|
867
|
+
async execute() {
|
|
868
|
+
return this.builder.execute();
|
|
869
|
+
}
|
|
870
|
+
}
|
|
871
|
+
exports.EntityInsertBuilder = EntityInsertBuilder;
|
|
872
|
+
/**
|
|
873
|
+
* Table accessor with entity typing
|
|
874
|
+
*/
|
|
875
|
+
class DbEntityTable {
|
|
876
|
+
constructor(context, tableName, tableBuilder) {
|
|
877
|
+
this.context = context;
|
|
878
|
+
this.tableName = tableName;
|
|
879
|
+
this.tableBuilder = tableBuilder;
|
|
880
|
+
}
|
|
881
|
+
/**
|
|
882
|
+
* Get the table schema for this entity
|
|
883
|
+
* @internal
|
|
884
|
+
*/
|
|
885
|
+
_getSchema() {
|
|
886
|
+
const schemaRegistry = this.context.schemaRegistry;
|
|
887
|
+
const schema = schemaRegistry.get(this.tableName);
|
|
888
|
+
if (!schema) {
|
|
889
|
+
throw new Error(`Schema not found for table ${this.tableName}`);
|
|
890
|
+
}
|
|
891
|
+
return schema;
|
|
892
|
+
}
|
|
893
|
+
/**
|
|
894
|
+
* Get the database client
|
|
895
|
+
* @internal
|
|
896
|
+
*/
|
|
897
|
+
_getClient() {
|
|
898
|
+
return this.context.client;
|
|
899
|
+
}
|
|
900
|
+
/**
|
|
901
|
+
* Get the query executor for logging
|
|
902
|
+
* @internal
|
|
903
|
+
*/
|
|
904
|
+
_getExecutor() {
|
|
905
|
+
return this.context.executor;
|
|
906
|
+
}
|
|
907
|
+
/**
|
|
908
|
+
* Get the collection strategy
|
|
909
|
+
* @internal
|
|
910
|
+
*/
|
|
911
|
+
_getCollectionStrategy() {
|
|
912
|
+
return this.context.queryOptions?.collectionStrategy;
|
|
913
|
+
}
|
|
914
|
+
/**
|
|
915
|
+
* Get qualified table name with schema prefix if specified
|
|
916
|
+
* @internal
|
|
917
|
+
*/
|
|
918
|
+
_getQualifiedTableName() {
|
|
919
|
+
const schema = this._getSchema();
|
|
920
|
+
return schema.schema
|
|
921
|
+
? `"${schema.schema}"."${schema.name}"`
|
|
922
|
+
: `"${schema.name}"`;
|
|
923
|
+
}
|
|
924
|
+
/**
|
|
925
|
+
* Configure query options for the current query chain
|
|
926
|
+
* Returns a new DbEntityTable instance with a modified context that has the specified options
|
|
927
|
+
*
|
|
928
|
+
* @example
|
|
929
|
+
* ```typescript
|
|
930
|
+
* const results = await db.users
|
|
931
|
+
* .withQueryOptions({ logQueries: true, collectionStrategy: 'temptable' })
|
|
932
|
+
* .select(u => ({ id: u.id, name: u.username }))
|
|
933
|
+
* .toList();
|
|
934
|
+
* ```
|
|
935
|
+
*/
|
|
936
|
+
withQueryOptions(options) {
|
|
937
|
+
// Create a proxy context with modified options
|
|
938
|
+
const originalContext = this.context;
|
|
939
|
+
const originalOptions = originalContext.queryOptions || {};
|
|
940
|
+
const tableName = this.tableName;
|
|
941
|
+
// Merge options
|
|
942
|
+
const mergedOptions = { ...originalOptions, ...options };
|
|
943
|
+
// Create new executor if logging options are provided
|
|
944
|
+
let newExecutor = originalContext.executor;
|
|
945
|
+
if (mergedOptions.logQueries || mergedOptions.logExecutionTime) {
|
|
946
|
+
newExecutor = new QueryExecutor(originalContext.client, mergedOptions);
|
|
947
|
+
}
|
|
948
|
+
// Create a proxy context that overrides queryOptions, executor, and getTable
|
|
949
|
+
const proxyContext = new Proxy(originalContext, {
|
|
950
|
+
get(target, prop) {
|
|
951
|
+
if (prop === 'queryOptions') {
|
|
952
|
+
return mergedOptions;
|
|
953
|
+
}
|
|
954
|
+
if (prop === 'executor') {
|
|
955
|
+
return newExecutor;
|
|
956
|
+
}
|
|
957
|
+
if (prop === 'getTable') {
|
|
958
|
+
return (name) => {
|
|
959
|
+
// Get the original TableAccessor
|
|
960
|
+
const originalAccessor = target.tableAccessors.get(name);
|
|
961
|
+
if (!originalAccessor) {
|
|
962
|
+
return target.getTable(name);
|
|
963
|
+
}
|
|
964
|
+
// If requesting this table, return a TableAccessor with updated options
|
|
965
|
+
if (name === tableName) {
|
|
966
|
+
const schemaRegistry = target.schemaRegistry;
|
|
967
|
+
const client = target.client;
|
|
968
|
+
const originalTableBuilder = originalAccessor.tableBuilder;
|
|
969
|
+
return new TableAccessor(originalTableBuilder, client, schemaRegistry, newExecutor, mergedOptions.collectionStrategy);
|
|
970
|
+
}
|
|
971
|
+
// Otherwise return the original table
|
|
972
|
+
return originalAccessor;
|
|
973
|
+
};
|
|
974
|
+
}
|
|
975
|
+
return target[prop];
|
|
976
|
+
}
|
|
977
|
+
});
|
|
978
|
+
// Return new instance with proxy context
|
|
979
|
+
return new DbEntityTable(proxyContext, this.tableName, this.tableBuilder);
|
|
980
|
+
}
|
|
981
|
+
/**
|
|
982
|
+
* Select all records - returns full entities with unwrapped DbColumns
|
|
983
|
+
*/
|
|
984
|
+
async toList() {
|
|
985
|
+
const queryBuilder = this.context.getTable(this.tableName);
|
|
986
|
+
const schema = this._getSchema();
|
|
987
|
+
// Build SELECT with all columns
|
|
988
|
+
const columns = Object.entries(schema.columns)
|
|
989
|
+
.map(([_, col]) => `"${col.build().name}"`)
|
|
990
|
+
.join(', ');
|
|
991
|
+
const qualifiedTableName = this._getQualifiedTableName();
|
|
992
|
+
const sql = `SELECT ${columns} FROM ${qualifiedTableName}`;
|
|
993
|
+
const executor = this._getExecutor();
|
|
994
|
+
const client = this._getClient();
|
|
995
|
+
const result = executor
|
|
996
|
+
? await executor.query(sql, [])
|
|
997
|
+
: await client.query(sql, []);
|
|
998
|
+
return this.mapResultsToEntities(result.rows);
|
|
999
|
+
}
|
|
1000
|
+
/**
|
|
1001
|
+
* Count all records
|
|
1002
|
+
*/
|
|
1003
|
+
async count() {
|
|
1004
|
+
const schema = this._getSchema();
|
|
1005
|
+
const qualifiedTableName = this._getQualifiedTableName();
|
|
1006
|
+
const sql = `SELECT COUNT(*) as count FROM ${qualifiedTableName}`;
|
|
1007
|
+
const executor = this._getExecutor();
|
|
1008
|
+
const client = this._getClient();
|
|
1009
|
+
const result = executor
|
|
1010
|
+
? await executor.query(sql, [])
|
|
1011
|
+
: await client.query(sql, []);
|
|
1012
|
+
return parseInt(result.rows[0].count);
|
|
1013
|
+
}
|
|
1014
|
+
orderBy(selector) {
|
|
1015
|
+
const schema = this._getSchema();
|
|
1016
|
+
const allColumnsSelector = (e) => {
|
|
1017
|
+
const result = {};
|
|
1018
|
+
for (const colName of Object.keys(schema.columns)) {
|
|
1019
|
+
result[colName] = e[colName];
|
|
1020
|
+
}
|
|
1021
|
+
return result;
|
|
1022
|
+
};
|
|
1023
|
+
const qb = this.context.getTable(this.tableName).select(allColumnsSelector);
|
|
1024
|
+
return qb.orderBy(selector);
|
|
1025
|
+
}
|
|
1026
|
+
/**
|
|
1027
|
+
* Limit results
|
|
1028
|
+
*/
|
|
1029
|
+
limit(count) {
|
|
1030
|
+
const schema = this._getSchema();
|
|
1031
|
+
const allColumnsSelector = (e) => {
|
|
1032
|
+
const result = {};
|
|
1033
|
+
for (const colName of Object.keys(schema.columns)) {
|
|
1034
|
+
result[colName] = e[colName];
|
|
1035
|
+
}
|
|
1036
|
+
return result;
|
|
1037
|
+
};
|
|
1038
|
+
const qb = this.context.getTable(this.tableName).select(allColumnsSelector);
|
|
1039
|
+
return qb.limit(count);
|
|
1040
|
+
}
|
|
1041
|
+
/**
|
|
1042
|
+
* Offset results
|
|
1043
|
+
*/
|
|
1044
|
+
offset(count) {
|
|
1045
|
+
const schema = this._getSchema();
|
|
1046
|
+
const allColumnsSelector = (e) => {
|
|
1047
|
+
const result = {};
|
|
1048
|
+
for (const colName of Object.keys(schema.columns)) {
|
|
1049
|
+
result[colName] = e[colName];
|
|
1050
|
+
}
|
|
1051
|
+
return result;
|
|
1052
|
+
};
|
|
1053
|
+
const qb = this.context.getTable(this.tableName).select(allColumnsSelector);
|
|
1054
|
+
return qb.offset(count);
|
|
1055
|
+
}
|
|
1056
|
+
/**
|
|
1057
|
+
* Select query
|
|
1058
|
+
*/
|
|
1059
|
+
select(selector) {
|
|
1060
|
+
const queryBuilder = this.context.getTable(this.tableName).select(selector);
|
|
1061
|
+
return queryBuilder;
|
|
1062
|
+
}
|
|
1063
|
+
/**
|
|
1064
|
+
* Select distinct
|
|
1065
|
+
*/
|
|
1066
|
+
selectDistinct(selector) {
|
|
1067
|
+
const queryBuilder = this.context.getTable(this.tableName);
|
|
1068
|
+
// First select, then call selectDistinct to get a new builder with isDistinct=true
|
|
1069
|
+
const selectBuilder = queryBuilder.select(selector);
|
|
1070
|
+
const distinctBuilder = selectBuilder.selectDistinct((x) => x);
|
|
1071
|
+
return distinctBuilder;
|
|
1072
|
+
}
|
|
1073
|
+
/**
|
|
1074
|
+
* Where query - returns all columns by default
|
|
1075
|
+
*/
|
|
1076
|
+
where(condition) {
|
|
1077
|
+
const schema = this._getSchema();
|
|
1078
|
+
// Create a selector that selects all columns with navigation property access
|
|
1079
|
+
const allColumnsSelector = (e) => {
|
|
1080
|
+
const result = {};
|
|
1081
|
+
// Copy all column properties
|
|
1082
|
+
for (const colName of Object.keys(schema.columns)) {
|
|
1083
|
+
result[colName] = e[colName];
|
|
1084
|
+
}
|
|
1085
|
+
// Add navigation properties
|
|
1086
|
+
for (const relName of Object.keys(schema.relations)) {
|
|
1087
|
+
const relConfig = schema.relations[relName];
|
|
1088
|
+
if (relConfig.type === 'many') {
|
|
1089
|
+
// For collections, use empty array placeholder
|
|
1090
|
+
// (prevents CollectionQueryBuilder evaluation)
|
|
1091
|
+
result[relName] = [];
|
|
1092
|
+
}
|
|
1093
|
+
else {
|
|
1094
|
+
// For references, copy the ReferenceQueryBuilder mock
|
|
1095
|
+
// This allows accessing fields like o.user!.username in chained selectors
|
|
1096
|
+
result[relName] = e[relName];
|
|
1097
|
+
}
|
|
1098
|
+
}
|
|
1099
|
+
return result;
|
|
1100
|
+
};
|
|
1101
|
+
const queryBuilder = this.context.getTable(this.tableName)
|
|
1102
|
+
.where(condition)
|
|
1103
|
+
.select(allColumnsSelector);
|
|
1104
|
+
return queryBuilder;
|
|
1105
|
+
}
|
|
1106
|
+
/**
|
|
1107
|
+
* Left join with another table or subquery and selector
|
|
1108
|
+
*/
|
|
1109
|
+
leftJoin(rightTable, condition, selector, alias) {
|
|
1110
|
+
const queryBuilder = this.context.getTable(this.tableName).leftJoin(rightTable, condition, selector, alias);
|
|
1111
|
+
return queryBuilder;
|
|
1112
|
+
}
|
|
1113
|
+
/**
|
|
1114
|
+
* Inner join with another table or subquery and selector
|
|
1115
|
+
*/
|
|
1116
|
+
innerJoin(rightTable, condition, selector, alias) {
|
|
1117
|
+
const queryBuilder = this.context.getTable(this.tableName).innerJoin(rightTable, condition, selector, alias);
|
|
1118
|
+
return queryBuilder;
|
|
1119
|
+
}
|
|
1120
|
+
/**
|
|
1121
|
+
* Insert - accepts only DbColumn properties (excludes navigation properties)
|
|
1122
|
+
*/
|
|
1123
|
+
async insert(data) {
|
|
1124
|
+
const result = await this.context.getTable(this.tableName).insert(data);
|
|
1125
|
+
return this.mapResultToEntity(result);
|
|
1126
|
+
}
|
|
1127
|
+
/**
|
|
1128
|
+
* Insert multiple records
|
|
1129
|
+
*/
|
|
1130
|
+
async insertMany(data) {
|
|
1131
|
+
return this.insertBulk(data);
|
|
1132
|
+
}
|
|
1133
|
+
/**
|
|
1134
|
+
* Upsert (insert or update on conflict)
|
|
1135
|
+
*/
|
|
1136
|
+
async upsert(data, config) {
|
|
1137
|
+
return this.upsertBulk(data, config);
|
|
1138
|
+
}
|
|
1139
|
+
/**
|
|
1140
|
+
* Bulk insert with advanced configuration
|
|
1141
|
+
* Supports automatic chunking for large datasets
|
|
1142
|
+
*/
|
|
1143
|
+
async insertBulk(value, insertConfig) {
|
|
1144
|
+
const results = await this.context.getTable(this.tableName).insertBulk(value, insertConfig);
|
|
1145
|
+
return this.mapResultsToEntities(results);
|
|
1146
|
+
}
|
|
1147
|
+
/**
|
|
1148
|
+
* Upsert with advanced configuration
|
|
1149
|
+
* Auto-detects primary keys and supports chunking
|
|
1150
|
+
*/
|
|
1151
|
+
async upsertBulk(values, config) {
|
|
1152
|
+
// Convert typed config to base config
|
|
1153
|
+
const baseConfig = {
|
|
1154
|
+
chunkSize: config?.chunkSize,
|
|
1155
|
+
overridingSystemValue: config?.overridingSystemValue,
|
|
1156
|
+
targetWhere: config?.targetWhere,
|
|
1157
|
+
setWhere: config?.setWhere,
|
|
1158
|
+
referenceItem: config?.referenceItem,
|
|
1159
|
+
updateColumnFilter: config?.updateColumnFilter,
|
|
1160
|
+
};
|
|
1161
|
+
// Handle primaryKey (can be lambda, string, or array)
|
|
1162
|
+
if (config?.primaryKey) {
|
|
1163
|
+
if (typeof config.primaryKey === 'function') {
|
|
1164
|
+
// Lambda selector - extract property names
|
|
1165
|
+
const pkProps = this.extractPropertyNames(config.primaryKey);
|
|
1166
|
+
baseConfig.primaryKey = pkProps.length === 1 ? pkProps[0] : pkProps;
|
|
1167
|
+
}
|
|
1168
|
+
else {
|
|
1169
|
+
// Direct string or array
|
|
1170
|
+
baseConfig.primaryKey = config.primaryKey;
|
|
1171
|
+
}
|
|
1172
|
+
}
|
|
1173
|
+
// Handle updateColumns (can be lambda or array)
|
|
1174
|
+
if (config?.updateColumns) {
|
|
1175
|
+
if (typeof config.updateColumns === 'function') {
|
|
1176
|
+
// Lambda selector - extract property names
|
|
1177
|
+
baseConfig.updateColumns = this.extractPropertyNames(config.updateColumns);
|
|
1178
|
+
}
|
|
1179
|
+
else {
|
|
1180
|
+
// Direct array
|
|
1181
|
+
baseConfig.updateColumns = config.updateColumns;
|
|
1182
|
+
}
|
|
1183
|
+
}
|
|
1184
|
+
const results = await this.context.getTable(this.tableName).upsertBulk(values, baseConfig);
|
|
1185
|
+
return this.mapResultsToEntities(results);
|
|
1186
|
+
}
|
|
1187
|
+
/**
|
|
1188
|
+
* Map database column names back to property names
|
|
1189
|
+
*/
|
|
1190
|
+
mapResultToEntity(result) {
|
|
1191
|
+
const schema = this._getSchema();
|
|
1192
|
+
const mapped = {};
|
|
1193
|
+
for (const [propName, colBuilder] of Object.entries(schema.columns)) {
|
|
1194
|
+
const config = colBuilder.build();
|
|
1195
|
+
const dbColumnName = config.name;
|
|
1196
|
+
if (dbColumnName in result) {
|
|
1197
|
+
mapped[propName] = result[dbColumnName];
|
|
1198
|
+
}
|
|
1199
|
+
}
|
|
1200
|
+
return mapped;
|
|
1201
|
+
}
|
|
1202
|
+
/**
|
|
1203
|
+
* Map array of database results to entities
|
|
1204
|
+
*/
|
|
1205
|
+
mapResultsToEntities(results) {
|
|
1206
|
+
return results.map(r => this.mapResultToEntity(r));
|
|
1207
|
+
}
|
|
1208
|
+
/**
|
|
1209
|
+
* Extract property names from lambda selector
|
|
1210
|
+
*/
|
|
1211
|
+
extractPropertyNames(selector) {
|
|
1212
|
+
const selectorStr = selector.toString();
|
|
1213
|
+
// Match patterns like: e => e.username or e => ({ username: e.username, email: e.email })
|
|
1214
|
+
const propertyNames = [];
|
|
1215
|
+
// Simple property access: e => e.username
|
|
1216
|
+
const simpleMatch = selectorStr.match(/=>\s*\w+\.(\w+)/);
|
|
1217
|
+
if (simpleMatch) {
|
|
1218
|
+
propertyNames.push(simpleMatch[1]);
|
|
1219
|
+
return propertyNames;
|
|
1220
|
+
}
|
|
1221
|
+
// Object literal: e => ({ username: e.username, email: e.email })
|
|
1222
|
+
const objectMatches = selectorStr.matchAll(/(\w+):\s*\w+\.\1/g);
|
|
1223
|
+
for (const match of objectMatches) {
|
|
1224
|
+
propertyNames.push(match[1]);
|
|
1225
|
+
}
|
|
1226
|
+
return propertyNames;
|
|
1227
|
+
}
|
|
1228
|
+
/**
|
|
1229
|
+
* Start building an upsert query with values
|
|
1230
|
+
*/
|
|
1231
|
+
values(data) {
|
|
1232
|
+
const builder = this.context.getTable(this.tableName).values(data);
|
|
1233
|
+
return new EntityInsertBuilder(builder);
|
|
1234
|
+
}
|
|
1235
|
+
/**
|
|
1236
|
+
* Update records matching condition
|
|
1237
|
+
* Usage: db.users.update({ age: 30 }, u => eq(u.id, 1))
|
|
1238
|
+
*/
|
|
1239
|
+
async update(data, condition) {
|
|
1240
|
+
const schema = this._getSchema();
|
|
1241
|
+
const executor = this._getExecutor();
|
|
1242
|
+
const client = this._getClient();
|
|
1243
|
+
// Build SET clause
|
|
1244
|
+
const setClauses = [];
|
|
1245
|
+
const values = [];
|
|
1246
|
+
let paramIndex = 1;
|
|
1247
|
+
for (const [key, value] of Object.entries(data)) {
|
|
1248
|
+
const column = schema.columns[key];
|
|
1249
|
+
if (column) {
|
|
1250
|
+
const config = column.build();
|
|
1251
|
+
setClauses.push(`"${config.name}" = $${paramIndex++}`);
|
|
1252
|
+
values.push(value);
|
|
1253
|
+
}
|
|
1254
|
+
}
|
|
1255
|
+
if (setClauses.length === 0) {
|
|
1256
|
+
throw new Error('No valid columns to update');
|
|
1257
|
+
}
|
|
1258
|
+
// Build WHERE clause
|
|
1259
|
+
const mockEntity = this.createMockEntity();
|
|
1260
|
+
const whereCondition = condition(mockEntity);
|
|
1261
|
+
const condBuilder = new conditions_1.ConditionBuilder();
|
|
1262
|
+
const { sql: whereSql, params: whereParams } = condBuilder.build(whereCondition, paramIndex);
|
|
1263
|
+
values.push(...whereParams);
|
|
1264
|
+
// Build RETURNING clause
|
|
1265
|
+
const returningColumns = Object.entries(schema.columns)
|
|
1266
|
+
.map(([_, col]) => `"${col.build().name}"`)
|
|
1267
|
+
.join(', ');
|
|
1268
|
+
const qualifiedTableName = this._getQualifiedTableName();
|
|
1269
|
+
const sql = `
|
|
1270
|
+
UPDATE ${qualifiedTableName}
|
|
1271
|
+
SET ${setClauses.join(', ')}
|
|
1272
|
+
WHERE ${whereSql}
|
|
1273
|
+
RETURNING ${returningColumns}
|
|
1274
|
+
`;
|
|
1275
|
+
const result = executor
|
|
1276
|
+
? await executor.query(sql, values)
|
|
1277
|
+
: await client.query(sql, values);
|
|
1278
|
+
return this.mapResultsToEntities(result.rows);
|
|
1279
|
+
}
|
|
1280
|
+
/**
|
|
1281
|
+
* Delete records matching condition
|
|
1282
|
+
* Usage: db.users.delete(u => eq(u.id, 1))
|
|
1283
|
+
*/
|
|
1284
|
+
async delete(condition) {
|
|
1285
|
+
const schema = this._getSchema();
|
|
1286
|
+
const executor = this._getExecutor();
|
|
1287
|
+
const client = this._getClient();
|
|
1288
|
+
// Build WHERE clause
|
|
1289
|
+
const mockEntity = this.createMockEntity();
|
|
1290
|
+
const whereCondition = condition(mockEntity);
|
|
1291
|
+
const condBuilder = new conditions_1.ConditionBuilder();
|
|
1292
|
+
const { sql: whereSql, params: whereParams } = condBuilder.build(whereCondition, 1);
|
|
1293
|
+
const qualifiedTableName = this._getQualifiedTableName();
|
|
1294
|
+
const sql = `DELETE FROM ${qualifiedTableName} WHERE ${whereSql}`;
|
|
1295
|
+
const result = executor
|
|
1296
|
+
? await executor.query(sql, whereParams)
|
|
1297
|
+
: await client.query(sql, whereParams);
|
|
1298
|
+
}
|
|
1299
|
+
/**
|
|
1300
|
+
* Create a mock entity for type inference in lambdas
|
|
1301
|
+
*/
|
|
1302
|
+
createMockEntity() {
|
|
1303
|
+
const schema = this._getSchema();
|
|
1304
|
+
const mock = {};
|
|
1305
|
+
// Add all columns as DbColumn-like objects
|
|
1306
|
+
for (const [propName, colBuilder] of Object.entries(schema.columns)) {
|
|
1307
|
+
const config = colBuilder.build();
|
|
1308
|
+
Object.defineProperty(mock, propName, {
|
|
1309
|
+
get: () => ({
|
|
1310
|
+
__fieldName: propName,
|
|
1311
|
+
__dbColumnName: config.name,
|
|
1312
|
+
__isDbColumn: true,
|
|
1313
|
+
}),
|
|
1314
|
+
enumerable: true,
|
|
1315
|
+
});
|
|
1316
|
+
}
|
|
1317
|
+
// Add navigation properties (both collections and references)
|
|
1318
|
+
for (const [relName, relConfig] of Object.entries(schema.relations)) {
|
|
1319
|
+
if (relConfig.type === 'many') {
|
|
1320
|
+
Object.defineProperty(mock, relName, {
|
|
1321
|
+
get: () => {
|
|
1322
|
+
const targetSchema = relConfig.targetTableBuilder?.build();
|
|
1323
|
+
return new query_builder_1.CollectionQueryBuilder(relName, relConfig.targetTable, relConfig.foreignKey || relConfig.foreignKeys?.[0] || '', schema.name, targetSchema);
|
|
1324
|
+
},
|
|
1325
|
+
enumerable: true,
|
|
1326
|
+
});
|
|
1327
|
+
}
|
|
1328
|
+
else {
|
|
1329
|
+
// Single reference navigation (many-to-one, one-to-one)
|
|
1330
|
+
Object.defineProperty(mock, relName, {
|
|
1331
|
+
get: () => {
|
|
1332
|
+
const targetSchema = relConfig.targetTableBuilder?.build();
|
|
1333
|
+
const refBuilder = new query_builder_1.ReferenceQueryBuilder(relName, relConfig.targetTable, relConfig.foreignKeys || [relConfig.foreignKey || ''], relConfig.matches || [], relConfig.isMandatory ?? false, targetSchema);
|
|
1334
|
+
return refBuilder.createMockTargetRow();
|
|
1335
|
+
},
|
|
1336
|
+
enumerable: true,
|
|
1337
|
+
});
|
|
1338
|
+
}
|
|
1339
|
+
}
|
|
1340
|
+
return mock;
|
|
1341
|
+
}
|
|
1342
|
+
}
|
|
1343
|
+
exports.DbEntityTable = DbEntityTable;
|
|
1344
|
+
/**
|
|
1345
|
+
* Base database context with entity-first approach
|
|
1346
|
+
*/
|
|
1347
|
+
class DatabaseContext extends DataContext {
|
|
1348
|
+
constructor(client, queryOptions) {
|
|
1349
|
+
// Initialize model config
|
|
1350
|
+
const modelConfig = new model_config_1.DbModelConfig();
|
|
1351
|
+
// Get the actual derived class's setupModel
|
|
1352
|
+
const derivedPrototype = new.target.prototype;
|
|
1353
|
+
if (derivedPrototype.setupModel) {
|
|
1354
|
+
derivedPrototype.setupModel.call({ setupModel: derivedPrototype.setupModel }, modelConfig);
|
|
1355
|
+
}
|
|
1356
|
+
// Build schema from model
|
|
1357
|
+
const schema = {};
|
|
1358
|
+
const tables = modelConfig.buildTables();
|
|
1359
|
+
for (const [tableName, tableBuilder] of tables) {
|
|
1360
|
+
schema[tableName] = tableBuilder;
|
|
1361
|
+
}
|
|
1362
|
+
// Call parent with built schema
|
|
1363
|
+
super(client, schema, queryOptions);
|
|
1364
|
+
this.entityTables = new Map();
|
|
1365
|
+
this.sequenceRegistry = new Map();
|
|
1366
|
+
this.sequenceInstances = new Map();
|
|
1367
|
+
this.modelConfig = modelConfig;
|
|
1368
|
+
// Setup sequences after construction (call setupSequences if it exists)
|
|
1369
|
+
if (derivedPrototype.setupSequences) {
|
|
1370
|
+
derivedPrototype.setupSequences.call(this);
|
|
1371
|
+
}
|
|
1372
|
+
}
|
|
1373
|
+
/**
|
|
1374
|
+
* Hook called after database migrations/schema creation are complete.
|
|
1375
|
+
* Override this method to execute custom SQL scripts that are outside the scope of the ORM.
|
|
1376
|
+
*
|
|
1377
|
+
* @example
|
|
1378
|
+
* ```typescript
|
|
1379
|
+
* protected async onMigrationComplete(client: DatabaseClient): Promise<void> {
|
|
1380
|
+
* // Create custom functions, views, triggers, etc.
|
|
1381
|
+
* await client.query(`
|
|
1382
|
+
* CREATE OR REPLACE FUNCTION custom_function()
|
|
1383
|
+
* RETURNS void AS $$
|
|
1384
|
+
* BEGIN
|
|
1385
|
+
* -- Custom logic here
|
|
1386
|
+
* END;
|
|
1387
|
+
* $$ LANGUAGE plpgsql;
|
|
1388
|
+
* `);
|
|
1389
|
+
* }
|
|
1390
|
+
* ```
|
|
1391
|
+
*
|
|
1392
|
+
* @param client - Database client for executing custom SQL
|
|
1393
|
+
*/
|
|
1394
|
+
async onMigrationComplete(client) {
|
|
1395
|
+
// Default implementation does nothing
|
|
1396
|
+
// Override in derived class to execute custom scripts
|
|
1397
|
+
}
|
|
1398
|
+
/**
|
|
1399
|
+
* Register a sequence in the schema
|
|
1400
|
+
* @param config - Sequence configuration
|
|
1401
|
+
*/
|
|
1402
|
+
registerSequence(config) {
|
|
1403
|
+
const key = config.schema ? `${config.schema}.${config.name}` : config.name;
|
|
1404
|
+
this.sequenceRegistry.set(key, config);
|
|
1405
|
+
}
|
|
1406
|
+
/**
|
|
1407
|
+
* Get a sequence instance for interacting with the database
|
|
1408
|
+
* @param config - Sequence configuration
|
|
1409
|
+
* @returns DbSequence instance with nextValue() and resync() methods
|
|
1410
|
+
*/
|
|
1411
|
+
sequence(config) {
|
|
1412
|
+
const key = config.schema ? `${config.schema}.${config.name}` : config.name;
|
|
1413
|
+
// Register if not already registered
|
|
1414
|
+
if (!this.sequenceRegistry.has(key)) {
|
|
1415
|
+
this.sequenceRegistry.set(key, config);
|
|
1416
|
+
}
|
|
1417
|
+
// Return cached instance or create new one
|
|
1418
|
+
let instance = this.sequenceInstances.get(key);
|
|
1419
|
+
if (!instance) {
|
|
1420
|
+
instance = new sequence_builder_1.DbSequence(this.client, config);
|
|
1421
|
+
this.sequenceInstances.set(key, instance);
|
|
1422
|
+
}
|
|
1423
|
+
return instance;
|
|
1424
|
+
}
|
|
1425
|
+
/**
|
|
1426
|
+
* Get all registered sequences
|
|
1427
|
+
* @internal
|
|
1428
|
+
*/
|
|
1429
|
+
getSequenceRegistry() {
|
|
1430
|
+
return this.sequenceRegistry;
|
|
1431
|
+
}
|
|
1432
|
+
/**
|
|
1433
|
+
* Get schema manager for create/drop operations with post-migration hook support
|
|
1434
|
+
*/
|
|
1435
|
+
getSchemaManager() {
|
|
1436
|
+
return new db_schema_manager_1.DbSchemaManager(this.client, this.schemaRegistry, {
|
|
1437
|
+
logQueries: this.queryOptions?.logQueries,
|
|
1438
|
+
postMigrationHook: async (client) => {
|
|
1439
|
+
await this.onMigrationComplete(client);
|
|
1440
|
+
},
|
|
1441
|
+
sequenceRegistry: this.sequenceRegistry
|
|
1442
|
+
});
|
|
1443
|
+
}
|
|
1444
|
+
/**
|
|
1445
|
+
* Get strongly-typed table accessor for an entity
|
|
1446
|
+
* @internal - Use property accessors on derived class instead
|
|
1447
|
+
*/
|
|
1448
|
+
table(entityClass) {
|
|
1449
|
+
let table = this.entityTables.get(entityClass);
|
|
1450
|
+
if (!table) {
|
|
1451
|
+
const metadata = entity_base_1.EntityMetadataStore.getMetadata(entityClass);
|
|
1452
|
+
if (!metadata) {
|
|
1453
|
+
throw new Error(`No metadata found for entity ${entityClass.name}`);
|
|
1454
|
+
}
|
|
1455
|
+
// EntityTable doesn't need the tableBuilder, it just uses getTable() internally
|
|
1456
|
+
table = new DbEntityTable(this, metadata.tableName, null);
|
|
1457
|
+
this.entityTables.set(entityClass, table);
|
|
1458
|
+
}
|
|
1459
|
+
return table;
|
|
1460
|
+
}
|
|
1461
|
+
}
|
|
1462
|
+
exports.DatabaseContext = DatabaseContext;
|
|
1463
|
+
//# sourceMappingURL=db-context.js.map
|