forge-sql-orm 1.0.31 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +216 -695
- package/dist/ForgeSQLORM.js +526 -564
- package/dist/ForgeSQLORM.js.map +1 -1
- package/dist/ForgeSQLORM.mjs +527 -554
- package/dist/ForgeSQLORM.mjs.map +1 -1
- package/dist/core/ForgeSQLCrudOperations.d.ts +101 -130
- package/dist/core/ForgeSQLCrudOperations.d.ts.map +1 -1
- package/dist/core/ForgeSQLORM.d.ts +11 -10
- package/dist/core/ForgeSQLORM.d.ts.map +1 -1
- package/dist/core/ForgeSQLQueryBuilder.d.ts +275 -111
- package/dist/core/ForgeSQLQueryBuilder.d.ts.map +1 -1
- package/dist/core/ForgeSQLSelectOperations.d.ts +65 -22
- package/dist/core/ForgeSQLSelectOperations.d.ts.map +1 -1
- package/dist/core/SystemTables.d.ts +59 -0
- package/dist/core/SystemTables.d.ts.map +1 -0
- package/dist/index.d.ts +1 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/utils/sqlUtils.d.ts +53 -6
- package/dist/utils/sqlUtils.d.ts.map +1 -1
- package/dist-cli/cli.js +461 -397
- package/dist-cli/cli.js.map +1 -1
- package/dist-cli/cli.mjs +461 -397
- package/dist-cli/cli.mjs.map +1 -1
- package/package.json +21 -27
- package/src/core/ForgeSQLCrudOperations.ts +360 -473
- package/src/core/ForgeSQLORM.ts +38 -79
- package/src/core/ForgeSQLQueryBuilder.ts +255 -132
- package/src/core/ForgeSQLSelectOperations.ts +185 -72
- package/src/core/SystemTables.ts +7 -0
- package/src/index.ts +1 -2
- package/src/utils/sqlUtils.ts +164 -22
- package/dist/core/ComplexQuerySchemaBuilder.d.ts +0 -38
- package/dist/core/ComplexQuerySchemaBuilder.d.ts.map +0 -1
- package/dist/knex/index.d.ts +0 -4
- package/dist/knex/index.d.ts.map +0 -1
- package/src/core/ComplexQuerySchemaBuilder.ts +0 -63
- package/src/knex/index.ts +0 -4
package/dist/ForgeSQLORM.js
CHANGED
|
@@ -1,27 +1,11 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperties(exports, { __esModule: { value: true }, [Symbol.toStringTag]: { value: "Module" } });
|
|
3
|
-
const
|
|
4
|
-
const sql = require("@forge/sql");
|
|
3
|
+
const drizzleOrm = require("drizzle-orm");
|
|
5
4
|
const moment = require("moment");
|
|
6
|
-
const
|
|
7
|
-
const
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
const transformValue = (value, wrapValue = false) => {
|
|
11
|
-
switch (value.type) {
|
|
12
|
-
case "text":
|
|
13
|
-
case "string":
|
|
14
|
-
return wrapIfNeeded(`${value.value}`, wrapValue);
|
|
15
|
-
case "datetime":
|
|
16
|
-
return wrapIfNeeded(`${moment(value.value).format("YYYY-MM-DDTHH:mm:ss.SSS")}`, wrapValue);
|
|
17
|
-
case "date":
|
|
18
|
-
return wrapIfNeeded(`${moment(value.value).format("YYYY-MM-DD")}`, wrapValue);
|
|
19
|
-
case "time":
|
|
20
|
-
return wrapIfNeeded(`${moment(value.value).format("HH:mm:ss.SSS")}`, wrapValue);
|
|
21
|
-
default:
|
|
22
|
-
return value.value;
|
|
23
|
-
}
|
|
24
|
-
};
|
|
5
|
+
const sql = require("@forge/sql");
|
|
6
|
+
const mysql2 = require("drizzle-orm/mysql2");
|
|
7
|
+
const mysqlCore = require("drizzle-orm/mysql-core");
|
|
8
|
+
const moment$1 = require("moment/moment.js");
|
|
25
9
|
const parseDateTime = (value, format) => {
|
|
26
10
|
const m = moment(value, format, true);
|
|
27
11
|
if (!m.isValid()) {
|
|
@@ -29,576 +13,532 @@ const parseDateTime = (value, format) => {
|
|
|
29
13
|
}
|
|
30
14
|
return m.toDate();
|
|
31
15
|
};
|
|
16
|
+
function extractAlias(query) {
|
|
17
|
+
const match = query.match(/\bas\s+(['"`]?)([\w*]+)\1$/i);
|
|
18
|
+
return match ? match[2] : query;
|
|
19
|
+
}
|
|
20
|
+
function getPrimaryKeys(table) {
|
|
21
|
+
const { columns, primaryKeys } = getTableMetadata(table);
|
|
22
|
+
const columnPrimaryKeys = Object.entries(columns).filter(([, column]) => column.primary);
|
|
23
|
+
if (columnPrimaryKeys.length > 0) {
|
|
24
|
+
return columnPrimaryKeys;
|
|
25
|
+
}
|
|
26
|
+
if (Array.isArray(primaryKeys) && primaryKeys.length > 0) {
|
|
27
|
+
const primaryKeyColumns = /* @__PURE__ */ new Set();
|
|
28
|
+
primaryKeys.forEach((primaryKeyBuilder) => {
|
|
29
|
+
Object.entries(columns).filter(([, column]) => {
|
|
30
|
+
return primaryKeyBuilder.columns.includes(column);
|
|
31
|
+
}).forEach(([name, column]) => {
|
|
32
|
+
primaryKeyColumns.add([name, column]);
|
|
33
|
+
});
|
|
34
|
+
});
|
|
35
|
+
return Array.from(primaryKeyColumns);
|
|
36
|
+
}
|
|
37
|
+
return [];
|
|
38
|
+
}
|
|
39
|
+
function getTableMetadata(table) {
|
|
40
|
+
const symbols = Object.getOwnPropertySymbols(table);
|
|
41
|
+
const nameSymbol = symbols.find((s) => s.toString().includes("Name"));
|
|
42
|
+
const columnsSymbol = symbols.find((s) => s.toString().includes("Columns"));
|
|
43
|
+
const extraSymbol = symbols.find((s) => s.toString().includes("ExtraConfigBuilder"));
|
|
44
|
+
const builders = {
|
|
45
|
+
indexes: [],
|
|
46
|
+
checks: [],
|
|
47
|
+
foreignKeys: [],
|
|
48
|
+
primaryKeys: [],
|
|
49
|
+
uniqueConstraints: [],
|
|
50
|
+
extras: []
|
|
51
|
+
};
|
|
52
|
+
if (extraSymbol) {
|
|
53
|
+
const extraConfigBuilder = table[extraSymbol];
|
|
54
|
+
if (extraConfigBuilder && typeof extraConfigBuilder === "function") {
|
|
55
|
+
const configBuilderData = extraConfigBuilder(table);
|
|
56
|
+
if (configBuilderData) {
|
|
57
|
+
const configBuilders = Array.isArray(configBuilderData) ? configBuilderData : Object.values(configBuilderData).map(
|
|
58
|
+
(item) => item.value || item
|
|
59
|
+
);
|
|
60
|
+
configBuilders.forEach((builder) => {
|
|
61
|
+
if (!builder?.constructor) return;
|
|
62
|
+
const builderName = builder.constructor.name.toLowerCase();
|
|
63
|
+
const builderMap = {
|
|
64
|
+
indexbuilder: builders.indexes,
|
|
65
|
+
checkbuilder: builders.checks,
|
|
66
|
+
foreignkeybuilder: builders.foreignKeys,
|
|
67
|
+
primarykeybuilder: builders.primaryKeys,
|
|
68
|
+
uniqueconstraintbuilder: builders.uniqueConstraints
|
|
69
|
+
};
|
|
70
|
+
for (const [type, array] of Object.entries(builderMap)) {
|
|
71
|
+
if (builderName.includes(type)) {
|
|
72
|
+
array.push(builder);
|
|
73
|
+
break;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
builders.extras.push(builder);
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
return {
|
|
82
|
+
tableName: nameSymbol ? table[nameSymbol] : "",
|
|
83
|
+
columns: columnsSymbol ? table[columnsSymbol] : {},
|
|
84
|
+
...builders
|
|
85
|
+
};
|
|
86
|
+
}
|
|
32
87
|
class ForgeSQLCrudOperations {
|
|
33
88
|
forgeOperations;
|
|
34
89
|
options;
|
|
90
|
+
/**
|
|
91
|
+
* Creates a new instance of ForgeSQLCrudOperations.
|
|
92
|
+
* @param forgeSqlOperations - The ForgeSQL operations instance
|
|
93
|
+
* @param options - Configuration options for the ORM
|
|
94
|
+
*/
|
|
35
95
|
constructor(forgeSqlOperations, options) {
|
|
36
96
|
this.forgeOperations = forgeSqlOperations;
|
|
37
97
|
this.options = options;
|
|
38
98
|
}
|
|
39
99
|
/**
|
|
40
|
-
*
|
|
41
|
-
* If a version field exists in the schema, its value is set accordingly.
|
|
42
|
-
*
|
|
43
|
-
* @param schema - The entity schema.
|
|
44
|
-
* @param models - The list of entities to insert.
|
|
45
|
-
* @param updateIfExists - Whether to update the row if it already exists.
|
|
46
|
-
* @returns An object containing the SQL query, column names, and values.
|
|
47
|
-
*/
|
|
48
|
-
async generateInsertScript(schema, models, updateIfExists) {
|
|
49
|
-
const columnNames = /* @__PURE__ */ new Set();
|
|
50
|
-
const modelFieldValues = [];
|
|
51
|
-
models.forEach((model) => {
|
|
52
|
-
const fieldValues = {};
|
|
53
|
-
schema.meta.props.forEach((prop) => {
|
|
54
|
-
const value = model[prop.name];
|
|
55
|
-
if (prop.kind === "scalar" && value !== void 0) {
|
|
56
|
-
const columnName = this.getRealFieldNameFromSchema(prop);
|
|
57
|
-
columnNames.add(columnName);
|
|
58
|
-
fieldValues[columnName] = { type: prop.type, value };
|
|
59
|
-
}
|
|
60
|
-
});
|
|
61
|
-
modelFieldValues.push(fieldValues);
|
|
62
|
-
});
|
|
63
|
-
const versionField = this.getVersionField(schema);
|
|
64
|
-
if (versionField) {
|
|
65
|
-
modelFieldValues.forEach((mv) => {
|
|
66
|
-
const versionRealName = this.getRealFieldNameFromSchema(versionField);
|
|
67
|
-
if (mv[versionRealName]) {
|
|
68
|
-
mv[versionRealName].value = transformValue(
|
|
69
|
-
{ value: this.createVersionField(versionField), type: versionField.name },
|
|
70
|
-
true
|
|
71
|
-
);
|
|
72
|
-
} else {
|
|
73
|
-
mv[versionRealName] = {
|
|
74
|
-
type: versionField.type,
|
|
75
|
-
value: transformValue(
|
|
76
|
-
{ value: this.createVersionField(versionField), type: versionField.name },
|
|
77
|
-
true
|
|
78
|
-
)
|
|
79
|
-
};
|
|
80
|
-
columnNames.add(versionField.name);
|
|
81
|
-
}
|
|
82
|
-
});
|
|
83
|
-
}
|
|
84
|
-
const columns = Array.from(columnNames);
|
|
85
|
-
const values = modelFieldValues.flatMap(
|
|
86
|
-
(fieldValueMap) => columns.map(
|
|
87
|
-
(column) => fieldValueMap[column] || {
|
|
88
|
-
type: "string",
|
|
89
|
-
value: null
|
|
90
|
-
}
|
|
91
|
-
)
|
|
92
|
-
);
|
|
93
|
-
const insertValues = modelFieldValues.map((fieldValueMap) => {
|
|
94
|
-
const rowValues = columns.map(
|
|
95
|
-
(column) => transformValue(
|
|
96
|
-
fieldValueMap[column] || { type: "string", value: null },
|
|
97
|
-
true
|
|
98
|
-
)
|
|
99
|
-
).join(",");
|
|
100
|
-
return `(${rowValues})`;
|
|
101
|
-
}).join(", ");
|
|
102
|
-
const insertEmptyValues = modelFieldValues.map(() => {
|
|
103
|
-
const rowValues = columns.map(
|
|
104
|
-
() => "?"
|
|
105
|
-
).join(",");
|
|
106
|
-
return `(${rowValues})`;
|
|
107
|
-
}).join(", ");
|
|
108
|
-
const updateClause = updateIfExists ? ` ON DUPLICATE KEY UPDATE ${columns.map((col) => `${col} = VALUES(${col})`).join(",")}` : "";
|
|
109
|
-
return {
|
|
110
|
-
sql: `INSERT INTO ${schema.meta.collection} (${columns.join(",")}) VALUES ${insertValues}${updateClause}`,
|
|
111
|
-
query: `INSERT INTO ${schema.meta.collection} (${columns.join(",")}) VALUES ${insertEmptyValues}${updateClause}`,
|
|
112
|
-
fields: columns,
|
|
113
|
-
values
|
|
114
|
-
};
|
|
115
|
-
}
|
|
116
|
-
/**
|
|
117
|
-
* Inserts records into the database.
|
|
100
|
+
* Inserts records into the database with optional versioning support.
|
|
118
101
|
* If a version field exists in the schema, versioning is applied.
|
|
119
102
|
*
|
|
120
|
-
* @
|
|
121
|
-
* @param
|
|
122
|
-
* @param
|
|
123
|
-
* @
|
|
103
|
+
* @template T - The type of the table schema
|
|
104
|
+
* @param {T} schema - The entity schema
|
|
105
|
+
* @param {Partial<InferInsertModel<T>>[]} models - Array of entities to insert
|
|
106
|
+
* @param {boolean} [updateIfExists=false] - Whether to update existing records
|
|
107
|
+
* @returns {Promise<number>} The number of inserted rows
|
|
108
|
+
* @throws {Error} If the insert operation fails
|
|
124
109
|
*/
|
|
125
110
|
async insert(schema, models, updateIfExists = false) {
|
|
126
|
-
if (!models
|
|
127
|
-
const
|
|
111
|
+
if (!models?.length) return 0;
|
|
112
|
+
const { tableName, columns } = getTableMetadata(schema);
|
|
113
|
+
const versionMetadata = this.validateVersionField(tableName, columns);
|
|
114
|
+
const preparedModels = models.map(
|
|
115
|
+
(model) => this.prepareModelWithVersion(model, versionMetadata, columns)
|
|
116
|
+
);
|
|
117
|
+
const queryBuilder = this.forgeOperations.getDrizzleQueryBuilder().insert(schema).values(preparedModels);
|
|
118
|
+
const finalQuery = updateIfExists ? queryBuilder.onDuplicateKeyUpdate({
|
|
119
|
+
set: Object.fromEntries(
|
|
120
|
+
Object.keys(preparedModels[0]).map((key) => [key, schema[key]])
|
|
121
|
+
)
|
|
122
|
+
}) : queryBuilder;
|
|
123
|
+
const query = finalQuery.toSQL();
|
|
128
124
|
if (this.options?.logRawSqlQuery) {
|
|
129
|
-
console.debug("INSERT SQL:
|
|
130
|
-
}
|
|
131
|
-
const sqlStatement = sql.sql.prepare(query.sql);
|
|
132
|
-
const result = await sqlStatement.execute();
|
|
133
|
-
return result.rows.insertId;
|
|
134
|
-
}
|
|
135
|
-
/**
|
|
136
|
-
* Retrieves the primary key properties from the entity schema.
|
|
137
|
-
*
|
|
138
|
-
* @param schema - The entity schema.
|
|
139
|
-
* @returns An array of primary key properties.
|
|
140
|
-
* @throws If no primary keys are found.
|
|
141
|
-
*/
|
|
142
|
-
getPrimaryKeys(schema) {
|
|
143
|
-
const primaryKeys = schema.meta.props.filter((prop) => prop.primary);
|
|
144
|
-
if (!primaryKeys.length) {
|
|
145
|
-
throw new Error(`No primary keys found for schema: ${schema.meta.className}`);
|
|
125
|
+
console.debug("INSERT SQL:", query.sql);
|
|
146
126
|
}
|
|
147
|
-
|
|
127
|
+
const result = await this.forgeOperations.fetch().executeRawUpdateSQL(query.sql, query.params);
|
|
128
|
+
return result.insertId;
|
|
148
129
|
}
|
|
149
130
|
/**
|
|
150
|
-
* Deletes a record by its primary key.
|
|
131
|
+
* Deletes a record by its primary key with optional version check.
|
|
132
|
+
* If versioning is enabled, ensures the record hasn't been modified since last read.
|
|
151
133
|
*
|
|
152
|
-
* @
|
|
153
|
-
* @param
|
|
154
|
-
* @
|
|
155
|
-
* @
|
|
134
|
+
* @template T - The type of the table schema
|
|
135
|
+
* @param {unknown} id - The ID of the record to delete
|
|
136
|
+
* @param {T} schema - The entity schema
|
|
137
|
+
* @returns {Promise<number>} Number of affected rows
|
|
138
|
+
* @throws {Error} If the delete operation fails
|
|
139
|
+
* @throws {Error} If multiple primary keys are found
|
|
156
140
|
*/
|
|
157
141
|
async deleteById(id, schema) {
|
|
142
|
+
const { tableName, columns } = getTableMetadata(schema);
|
|
158
143
|
const primaryKeys = this.getPrimaryKeys(schema);
|
|
159
|
-
if (primaryKeys.length
|
|
160
|
-
throw new Error("Only
|
|
161
|
-
}
|
|
162
|
-
const primaryKey = primaryKeys[0];
|
|
163
|
-
const queryBuilder = this.forgeOperations.createQueryBuilder(schema.meta.class).delete();
|
|
164
|
-
queryBuilder.andWhere({ [primaryKey.name]: { $eq: id } });
|
|
165
|
-
const query = queryBuilder.getFormattedQuery();
|
|
166
|
-
if (this.options?.logRawSqlQuery) {
|
|
167
|
-
console.debug("DELETE SQL: " + queryBuilder.getQuery());
|
|
168
|
-
}
|
|
169
|
-
const sqlStatement = sql.sql.prepare(query);
|
|
170
|
-
const result = await sqlStatement.execute();
|
|
171
|
-
return result.rows.affectedRows;
|
|
172
|
-
}
|
|
173
|
-
/**
|
|
174
|
-
* Retrieves the version field from the entity schema.
|
|
175
|
-
* The version field must be of type datetime, integer, or decimal, not a primary key, and not nullable.
|
|
176
|
-
*
|
|
177
|
-
* @param schema - The entity schema.
|
|
178
|
-
* @returns The version field property if it exists.
|
|
179
|
-
*/
|
|
180
|
-
getVersionField(schema) {
|
|
181
|
-
if (this.options.disableOptimisticLocking) {
|
|
182
|
-
return void 0;
|
|
144
|
+
if (primaryKeys.length !== 1) {
|
|
145
|
+
throw new Error("Only single primary key is supported");
|
|
183
146
|
}
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
`Version field "${prop.name}" in table ${schema.meta.tableName} cannot be a primary key`
|
|
196
|
-
);
|
|
197
|
-
return false;
|
|
198
|
-
}
|
|
199
|
-
return true;
|
|
200
|
-
}).find((prop) => {
|
|
201
|
-
if (prop.nullable) {
|
|
202
|
-
console.warn(
|
|
203
|
-
`Version field "${prop.name}" in table ${schema.meta.tableName} should not be nullable`
|
|
204
|
-
);
|
|
205
|
-
return false;
|
|
206
|
-
}
|
|
207
|
-
return true;
|
|
208
|
-
});
|
|
209
|
-
}
|
|
210
|
-
/**
|
|
211
|
-
* Increments the version field of an entity.
|
|
212
|
-
* For datetime types, sets the current date; for numeric types, increments by 1.
|
|
213
|
-
*
|
|
214
|
-
* @param versionField - The version field property.
|
|
215
|
-
* @param updateModel - The entity to update.
|
|
216
|
-
*/
|
|
217
|
-
incrementVersionField(versionField, updateModel) {
|
|
218
|
-
const key = versionField.name;
|
|
219
|
-
switch (versionField.type) {
|
|
220
|
-
case "datetime": {
|
|
221
|
-
updateModel[key] = /* @__PURE__ */ new Date();
|
|
222
|
-
break;
|
|
223
|
-
}
|
|
224
|
-
case "decimal":
|
|
225
|
-
case "integer": {
|
|
226
|
-
updateModel[key] = updateModel[key] + 1;
|
|
227
|
-
break;
|
|
147
|
+
const [primaryKeyName, primaryKeyColumn] = primaryKeys[0];
|
|
148
|
+
const versionMetadata = this.validateVersionField(tableName, columns);
|
|
149
|
+
const conditions = [drizzleOrm.eq(primaryKeyColumn, id)];
|
|
150
|
+
if (versionMetadata && columns) {
|
|
151
|
+
const versionField = columns[versionMetadata.fieldName];
|
|
152
|
+
if (versionField) {
|
|
153
|
+
const oldModel = await this.getOldModel({ [primaryKeyName]: id }, schema, [
|
|
154
|
+
versionMetadata.fieldName,
|
|
155
|
+
versionField
|
|
156
|
+
]);
|
|
157
|
+
conditions.push(drizzleOrm.eq(versionField, oldModel[versionMetadata.fieldName]));
|
|
228
158
|
}
|
|
229
|
-
default:
|
|
230
|
-
throw new Error(`Unsupported version field type: ${versionField.type}`);
|
|
231
159
|
}
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
* For datetime types, returns the current date; for numeric types, returns 0.
|
|
236
|
-
*
|
|
237
|
-
* @param versionField - The version field property.
|
|
238
|
-
*/
|
|
239
|
-
createVersionField(versionField) {
|
|
240
|
-
switch (versionField.type) {
|
|
241
|
-
case "datetime": {
|
|
242
|
-
return /* @__PURE__ */ new Date();
|
|
243
|
-
}
|
|
244
|
-
case "decimal":
|
|
245
|
-
case "integer": {
|
|
246
|
-
return 0;
|
|
247
|
-
}
|
|
248
|
-
default:
|
|
249
|
-
throw new Error(`Unsupported version field type: ${versionField.type}`);
|
|
160
|
+
const queryBuilder = this.forgeOperations.getDrizzleQueryBuilder().delete(schema).where(drizzleOrm.and(...conditions));
|
|
161
|
+
if (this.options?.logRawSqlQuery) {
|
|
162
|
+
console.debug("DELETE SQL:", queryBuilder.toSQL().sql);
|
|
250
163
|
}
|
|
164
|
+
const result = await this.forgeOperations.fetch().executeRawUpdateSQL(queryBuilder.toSQL().sql, queryBuilder.toSQL().params);
|
|
165
|
+
return result.affectedRows;
|
|
251
166
|
}
|
|
252
167
|
/**
|
|
253
|
-
* Updates a record by its primary key
|
|
168
|
+
* Updates a record by its primary key with optimistic locking support.
|
|
169
|
+
* If versioning is enabled:
|
|
170
|
+
* - Retrieves the current version
|
|
171
|
+
* - Checks for concurrent modifications
|
|
172
|
+
* - Increments the version on successful update
|
|
254
173
|
*
|
|
255
|
-
* @
|
|
256
|
-
* @param
|
|
174
|
+
* @template T - The type of the table schema
|
|
175
|
+
* @param {Partial<InferInsertModel<T>>} entity - The entity with updated values
|
|
176
|
+
* @param {T} schema - The entity schema
|
|
177
|
+
* @returns {Promise<number>} Number of affected rows
|
|
178
|
+
* @throws {Error} If the primary key is not provided
|
|
179
|
+
* @throws {Error} If optimistic locking check fails
|
|
180
|
+
* @throws {Error} If multiple primary keys are found
|
|
257
181
|
*/
|
|
258
182
|
async updateById(entity, schema) {
|
|
259
|
-
const
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
* Updates specified fields of records based on provided conditions.
|
|
264
|
-
* If the "where" parameter is not provided, the WHERE clause is built from the entity fields
|
|
265
|
-
* that are not included in the list of fields to update.
|
|
266
|
-
*
|
|
267
|
-
* @param entity - The object containing values to update and potential criteria for filtering.
|
|
268
|
-
* @param fields - Array of field names to update.
|
|
269
|
-
* @param schema - The entity schema.
|
|
270
|
-
* @param where - Optional filtering conditions for the WHERE clause.
|
|
271
|
-
* @returns The number of affected rows.
|
|
272
|
-
* @throws If no filtering criteria are provided (either via "where" or from the remaining entity fields).
|
|
273
|
-
*/
|
|
274
|
-
async updateFields(entity, fields, schema, where) {
|
|
275
|
-
const updateData = this.filterEntityFields(entity, fields);
|
|
276
|
-
const updateModel = this.modifyModel(updateData, schema);
|
|
277
|
-
let queryBuilder = this.forgeOperations.createQueryBuilder(schema.meta.class).getKnexQuery();
|
|
278
|
-
queryBuilder.update(updateModel);
|
|
279
|
-
if (where) {
|
|
280
|
-
queryBuilder.where(where);
|
|
281
|
-
} else {
|
|
282
|
-
const filterCriteria = Object.keys(entity).filter((key) => !fields.includes(key)).reduce((criteria, key) => {
|
|
283
|
-
if (entity[key] !== void 0) {
|
|
284
|
-
criteria[key] = entity[key];
|
|
285
|
-
}
|
|
286
|
-
return criteria;
|
|
287
|
-
}, {});
|
|
288
|
-
if (Object.keys(filterCriteria).length === 0) {
|
|
289
|
-
throw new Error(
|
|
290
|
-
"Filtering criteria (WHERE clause) must be provided either via the 'where' parameter or through non-updated entity fields"
|
|
291
|
-
);
|
|
292
|
-
}
|
|
293
|
-
queryBuilder.where(filterCriteria);
|
|
183
|
+
const { tableName, columns } = getTableMetadata(schema);
|
|
184
|
+
const primaryKeys = this.getPrimaryKeys(schema);
|
|
185
|
+
if (primaryKeys.length !== 1) {
|
|
186
|
+
throw new Error("Only single primary key is supported");
|
|
294
187
|
}
|
|
295
|
-
|
|
296
|
-
|
|
188
|
+
const [primaryKeyName, primaryKeyColumn] = primaryKeys[0];
|
|
189
|
+
const versionMetadata = this.validateVersionField(tableName, columns);
|
|
190
|
+
if (!(primaryKeyName in entity)) {
|
|
191
|
+
throw new Error(`Primary key ${primaryKeyName} must be provided in the entity`);
|
|
297
192
|
}
|
|
298
|
-
const
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
primaryKeys.forEach((pk) => {
|
|
314
|
-
if (!fields.includes(pk.name)) {
|
|
315
|
-
throw new Error("Update fields must include primary key: " + pk.name);
|
|
316
|
-
}
|
|
317
|
-
});
|
|
318
|
-
const updatedEntity = this.filterEntityFields(entity, fields);
|
|
319
|
-
let queryBuilder = this.forgeOperations.createQueryBuilder(schema.meta.class).getKnexQuery();
|
|
320
|
-
const versionField = this.getVersionField(schema);
|
|
321
|
-
const useVersion = Boolean(versionField);
|
|
322
|
-
let updateModel = { ...updatedEntity };
|
|
323
|
-
if (useVersion && versionField) {
|
|
324
|
-
let oldModel = entity;
|
|
325
|
-
if (entity[versionField.name] === void 0 || entity[versionField.name] === null) {
|
|
326
|
-
oldModel = await this.getOldModel(primaryKeys, entity, schema, versionField);
|
|
327
|
-
}
|
|
328
|
-
const primaryFieldNames = primaryKeys.map((pk) => pk.name);
|
|
329
|
-
const fieldsToRetain = primaryFieldNames.concat(versionField.name);
|
|
330
|
-
const fromEntries = Object.fromEntries(fieldsToRetain.map((key) => [key, oldModel[key]]));
|
|
331
|
-
updateModel = { ...updatedEntity, ...fromEntries };
|
|
332
|
-
this.incrementVersionField(versionField, updateModel);
|
|
333
|
-
updateModel = this.modifyModel(updateModel, schema);
|
|
334
|
-
queryBuilder.update(updateModel);
|
|
335
|
-
if (oldModel[versionField.name] !== void 0 || oldModel[versionField.name] !== null && this.isValid(oldModel[versionField.name])) {
|
|
336
|
-
queryBuilder.andWhere(this.optWhere(oldModel, versionField));
|
|
193
|
+
const currentVersion = await this.getCurrentVersion(
|
|
194
|
+
entity,
|
|
195
|
+
primaryKeyName,
|
|
196
|
+
versionMetadata,
|
|
197
|
+
columns,
|
|
198
|
+
schema
|
|
199
|
+
);
|
|
200
|
+
const updateData = this.prepareUpdateData(entity, versionMetadata, columns, currentVersion);
|
|
201
|
+
const conditions = [
|
|
202
|
+
drizzleOrm.eq(primaryKeyColumn, entity[primaryKeyName])
|
|
203
|
+
];
|
|
204
|
+
if (versionMetadata && columns) {
|
|
205
|
+
const versionField = columns[versionMetadata.fieldName];
|
|
206
|
+
if (versionField) {
|
|
207
|
+
conditions.push(drizzleOrm.eq(versionField, currentVersion));
|
|
337
208
|
}
|
|
338
|
-
} else {
|
|
339
|
-
updateModel = this.modifyModel(updatedEntity, schema);
|
|
340
|
-
queryBuilder.update(updateModel);
|
|
341
209
|
}
|
|
342
|
-
this.
|
|
343
|
-
const sqlQuery = queryBuilder.toQuery();
|
|
210
|
+
const queryBuilder = this.forgeOperations.getDrizzleQueryBuilder().update(schema).set(updateData).where(drizzleOrm.and(...conditions));
|
|
344
211
|
if (this.options?.logRawSqlQuery) {
|
|
345
|
-
console.debug("UPDATE SQL:
|
|
212
|
+
console.debug("UPDATE SQL:", queryBuilder.toSQL().sql);
|
|
346
213
|
}
|
|
347
|
-
const
|
|
348
|
-
if (
|
|
214
|
+
const result = await this.forgeOperations.fetch().executeRawUpdateSQL(queryBuilder.toSQL().sql, queryBuilder.toSQL().params);
|
|
215
|
+
if (versionMetadata && result.affectedRows === 0) {
|
|
349
216
|
throw new Error(
|
|
350
|
-
|
|
217
|
+
`Optimistic locking failed: record with primary key ${entity[primaryKeyName]} has been modified`
|
|
351
218
|
);
|
|
352
219
|
}
|
|
220
|
+
return result.affectedRows;
|
|
353
221
|
}
|
|
354
222
|
/**
|
|
355
|
-
*
|
|
223
|
+
* Updates specified fields of records based on provided conditions.
|
|
224
|
+
* This method does not support versioning and should be used with caution.
|
|
356
225
|
*
|
|
357
|
-
* @
|
|
358
|
-
* @param
|
|
359
|
-
* @
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
226
|
+
* @template T - The type of the table schema
|
|
227
|
+
* @param {Partial<InferInsertModel<T>>} updateData - The data to update
|
|
228
|
+
* @param {T} schema - The entity schema
|
|
229
|
+
* @param {SQL<unknown>} where - The WHERE conditions
|
|
230
|
+
* @returns {Promise<number>} Number of affected rows
|
|
231
|
+
* @throws {Error} If WHERE conditions are not provided
|
|
232
|
+
* @throws {Error} If the update operation fails
|
|
233
|
+
*/
|
|
234
|
+
async updateFields(updateData, schema, where) {
|
|
235
|
+
if (!where) {
|
|
236
|
+
throw new Error("WHERE conditions must be provided");
|
|
237
|
+
}
|
|
238
|
+
const queryBuilder = this.forgeOperations.getDrizzleQueryBuilder().update(schema).set(updateData).where(where);
|
|
239
|
+
if (this.options?.logRawSqlQuery) {
|
|
240
|
+
console.debug("UPDATE SQL:", queryBuilder.toSQL().sql);
|
|
241
|
+
}
|
|
242
|
+
const result = await this.forgeOperations.fetch().executeRawUpdateSQL(queryBuilder.toSQL().sql, queryBuilder.toSQL().params);
|
|
243
|
+
return result.affectedRows;
|
|
367
244
|
}
|
|
245
|
+
// Helper methods
|
|
368
246
|
/**
|
|
369
|
-
*
|
|
370
|
-
*
|
|
371
|
-
* @param
|
|
372
|
-
* @
|
|
373
|
-
* @
|
|
374
|
-
* @param versionField - The version field property.
|
|
375
|
-
* @returns The existing record from the database.
|
|
376
|
-
* @throws If the record does not exist or if multiple records are found.
|
|
247
|
+
* Gets primary keys from the schema.
|
|
248
|
+
* @template T - The type of the table schema
|
|
249
|
+
* @param {T} schema - The table schema
|
|
250
|
+
* @returns {[string, AnyColumn][]} Array of primary key name and column pairs
|
|
251
|
+
* @throws {Error} If no primary keys are found
|
|
377
252
|
*/
|
|
378
|
-
|
|
379
|
-
const
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
this.addPrimaryWhere(queryBuilder, primaryKeys, entity);
|
|
383
|
-
const formattedQuery = queryBuilder.getFormattedQuery();
|
|
384
|
-
const models = await this.forgeOperations.fetch().executeSchemaSQL(formattedQuery, schema);
|
|
385
|
-
if (!models || models.length === 0) {
|
|
386
|
-
throw new Error(`Cannot modify record because it does not exist in table ${schema.meta.tableName}`);
|
|
387
|
-
}
|
|
388
|
-
if (models.length > 1) {
|
|
389
|
-
throw new Error(
|
|
390
|
-
`Cannot modify record because multiple rows with the same ID were found in table ${schema.meta.tableName}. Please verify the table metadata.`
|
|
391
|
-
);
|
|
253
|
+
getPrimaryKeys(schema) {
|
|
254
|
+
const primaryKeys = getPrimaryKeys(schema);
|
|
255
|
+
if (!primaryKeys) {
|
|
256
|
+
throw new Error(`No primary keys found for schema: ${schema}`);
|
|
392
257
|
}
|
|
393
|
-
return
|
|
258
|
+
return primaryKeys;
|
|
394
259
|
}
|
|
395
260
|
/**
|
|
396
|
-
*
|
|
397
|
-
*
|
|
398
|
-
* @param
|
|
399
|
-
* @
|
|
400
|
-
* @param entity - The entity containing primary key values.
|
|
401
|
-
* @throws If any primary key value is missing.
|
|
261
|
+
* Validates and retrieves version field metadata.
|
|
262
|
+
* @param {string} tableName - The name of the table
|
|
263
|
+
* @param {Record<string, AnyColumn>} columns - The table columns
|
|
264
|
+
* @returns {Object | undefined} Version field metadata if valid, undefined otherwise
|
|
402
265
|
*/
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
266
|
+
validateVersionField(tableName, columns) {
|
|
267
|
+
if (this.options.disableOptimisticLocking) {
|
|
268
|
+
return void 0;
|
|
269
|
+
}
|
|
270
|
+
const versionMetadata = this.options.additionalMetadata?.[tableName]?.versionField;
|
|
271
|
+
if (!versionMetadata) return void 0;
|
|
272
|
+
const versionField = columns[versionMetadata.fieldName];
|
|
273
|
+
if (!versionField) {
|
|
274
|
+
console.warn(
|
|
275
|
+
`Version field "${versionMetadata.fieldName}" not found in table ${tableName}. Versioning will be skipped.`
|
|
276
|
+
);
|
|
277
|
+
return void 0;
|
|
278
|
+
}
|
|
279
|
+
if (!versionField.notNull) {
|
|
280
|
+
console.warn(
|
|
281
|
+
`Version field "${versionMetadata.fieldName}" in table ${tableName} is nullable. Versioning may not work correctly.`
|
|
282
|
+
);
|
|
283
|
+
return void 0;
|
|
284
|
+
}
|
|
285
|
+
const fieldType = versionField.getSQLType();
|
|
286
|
+
const isSupportedType = fieldType === "datetime" || fieldType === "timestamp" || fieldType === "int" || fieldType === "number" || fieldType === "decimal";
|
|
287
|
+
if (!isSupportedType) {
|
|
288
|
+
console.warn(
|
|
289
|
+
`Version field "${versionMetadata.fieldName}" in table ${tableName} has unsupported type "${fieldType}". Only datetime, timestamp, int, and decimal types are supported for versioning. Versioning will be skipped.`
|
|
290
|
+
);
|
|
291
|
+
return void 0;
|
|
292
|
+
}
|
|
293
|
+
return { fieldName: versionMetadata.fieldName, type: fieldType };
|
|
294
|
+
}
|
|
295
|
+
/**
|
|
296
|
+
* Gets the current version of an entity.
|
|
297
|
+
* @template T - The type of the table schema
|
|
298
|
+
* @param {Partial<InferInsertModel<T>>} entity - The entity
|
|
299
|
+
* @param {string} primaryKeyName - The name of the primary key
|
|
300
|
+
* @param {Object | undefined} versionMetadata - Version field metadata
|
|
301
|
+
* @param {Record<string, AnyColumn>} columns - The table columns
|
|
302
|
+
* @param {T} schema - The table schema
|
|
303
|
+
* @returns {Promise<unknown>} The current version value
|
|
304
|
+
*/
|
|
305
|
+
async getCurrentVersion(entity, primaryKeyName, versionMetadata, columns, schema) {
|
|
306
|
+
if (!versionMetadata || !columns) return void 0;
|
|
307
|
+
const versionField = columns[versionMetadata.fieldName];
|
|
308
|
+
if (!versionField) return void 0;
|
|
309
|
+
if (versionMetadata.fieldName in entity) {
|
|
310
|
+
return entity[versionMetadata.fieldName];
|
|
311
|
+
}
|
|
312
|
+
const oldModel = await this.getOldModel(
|
|
313
|
+
{ [primaryKeyName]: entity[primaryKeyName] },
|
|
314
|
+
schema,
|
|
315
|
+
[versionMetadata.fieldName, versionField]
|
|
316
|
+
);
|
|
317
|
+
return oldModel[versionMetadata.fieldName];
|
|
318
|
+
}
|
|
319
|
+
/**
|
|
320
|
+
* Prepares a model for insertion with version field.
|
|
321
|
+
* @template T - The type of the table schema
|
|
322
|
+
* @param {Partial<InferInsertModel<T>>} model - The model to prepare
|
|
323
|
+
* @param {Object | undefined} versionMetadata - Version field metadata
|
|
324
|
+
* @param {Record<string, AnyColumn>} columns - The table columns
|
|
325
|
+
* @returns {InferInsertModel<T>} The prepared model
|
|
326
|
+
*/
|
|
327
|
+
prepareModelWithVersion(model, versionMetadata, columns) {
|
|
328
|
+
if (!versionMetadata || !columns) return model;
|
|
329
|
+
const versionField = columns[versionMetadata.fieldName];
|
|
330
|
+
if (!versionField) return model;
|
|
331
|
+
const modelWithVersion = { ...model };
|
|
332
|
+
const fieldType = versionField.getSQLType();
|
|
333
|
+
const versionValue = fieldType === "datetime" || fieldType === "timestamp" ? /* @__PURE__ */ new Date() : 1;
|
|
334
|
+
modelWithVersion[versionMetadata.fieldName] = versionValue;
|
|
335
|
+
return modelWithVersion;
|
|
336
|
+
}
|
|
337
|
+
/**
|
|
338
|
+
* Prepares update data with version field.
|
|
339
|
+
* @template T - The type of the table schema
|
|
340
|
+
* @param {Partial<InferInsertModel<T>>} entity - The entity to update
|
|
341
|
+
* @param {Object | undefined} versionMetadata - Version field metadata
|
|
342
|
+
* @param {Record<string, AnyColumn>} columns - The table columns
|
|
343
|
+
* @param {unknown} currentVersion - The current version value
|
|
344
|
+
* @returns {Partial<InferInsertModel<T>>} The prepared update data
|
|
345
|
+
*/
|
|
346
|
+
prepareUpdateData(entity, versionMetadata, columns, currentVersion) {
|
|
347
|
+
const updateData = { ...entity };
|
|
348
|
+
if (versionMetadata && columns) {
|
|
349
|
+
const versionField = columns[versionMetadata.fieldName];
|
|
350
|
+
if (versionField) {
|
|
351
|
+
const fieldType = versionField.getSQLType();
|
|
352
|
+
updateData[versionMetadata.fieldName] = fieldType === "datetime" || fieldType === "timestamp" ? /* @__PURE__ */ new Date() : currentVersion + 1;
|
|
409
353
|
}
|
|
410
|
-
|
|
411
|
-
|
|
354
|
+
}
|
|
355
|
+
return updateData;
|
|
412
356
|
}
|
|
413
357
|
/**
|
|
414
|
-
*
|
|
415
|
-
*
|
|
416
|
-
* @param
|
|
417
|
-
* @param
|
|
418
|
-
* @
|
|
358
|
+
* Retrieves an existing model by primary key.
|
|
359
|
+
* @template T - The type of the table schema
|
|
360
|
+
* @param {Record<string, unknown>} primaryKeyValues - The primary key values
|
|
361
|
+
* @param {T} schema - The table schema
|
|
362
|
+
* @param {[string, AnyColumn]} versionField - The version field name and column
|
|
363
|
+
* @returns {Promise<Awaited<T> extends Array<any> ? Awaited<T>[number] | undefined : Awaited<T> | undefined>} The existing model
|
|
364
|
+
* @throws {Error} If the record is not found
|
|
419
365
|
*/
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
366
|
+
async getOldModel(primaryKeyValues, schema, versionField) {
|
|
367
|
+
const [versionFieldName, versionFieldColumn] = versionField;
|
|
368
|
+
const primaryKeys = this.getPrimaryKeys(schema);
|
|
369
|
+
const [primaryKeyName, primaryKeyColumn] = primaryKeys[0];
|
|
370
|
+
const resultQuery = this.forgeOperations.getDrizzleQueryBuilder().select({
|
|
371
|
+
[primaryKeyName]: primaryKeyColumn,
|
|
372
|
+
[versionFieldName]: versionFieldColumn
|
|
373
|
+
}).from(schema).where(drizzleOrm.eq(primaryKeyColumn, primaryKeyValues[primaryKeyName]));
|
|
374
|
+
const model = await this.forgeOperations.fetch().executeQueryOnlyOne(resultQuery);
|
|
375
|
+
if (!model) {
|
|
376
|
+
throw new Error(`Record not found in table ${schema}`);
|
|
423
377
|
}
|
|
424
|
-
return
|
|
425
|
-
}, {});
|
|
426
|
-
/**
|
|
427
|
-
* Transforms and modifies the updated entity model based on the schema.
|
|
428
|
-
*
|
|
429
|
-
* @param updatedEntity - The updated entity.
|
|
430
|
-
* @param schema - The entity schema.
|
|
431
|
-
* @returns The modified entity.
|
|
432
|
-
*/
|
|
433
|
-
modifyModel(updatedEntity, schema) {
|
|
434
|
-
const modifiedModel = {};
|
|
435
|
-
schema.meta.props.filter((p) => p.kind === "scalar").forEach((p) => {
|
|
436
|
-
const value = updatedEntity[p.name];
|
|
437
|
-
if (value !== void 0 && value !== null) {
|
|
438
|
-
const fieldName = this.getRealFieldNameFromSchema(p);
|
|
439
|
-
modifiedModel[fieldName] = transformValue({ value, type: p.type }, false);
|
|
440
|
-
}
|
|
441
|
-
});
|
|
442
|
-
return modifiedModel;
|
|
378
|
+
return model;
|
|
443
379
|
}
|
|
380
|
+
}
|
|
381
|
+
class ForgeSQLSelectOperations {
|
|
382
|
+
options;
|
|
444
383
|
/**
|
|
445
|
-
*
|
|
446
|
-
*
|
|
447
|
-
* @param p - The entity property.
|
|
448
|
-
* @returns The real field name.
|
|
384
|
+
* Creates a new instance of ForgeSQLSelectOperations.
|
|
385
|
+
* @param {ForgeSqlOrmOptions} options - Configuration options for the ORM
|
|
449
386
|
*/
|
|
450
|
-
|
|
451
|
-
|
|
387
|
+
constructor(options) {
|
|
388
|
+
this.options = options;
|
|
452
389
|
}
|
|
453
390
|
/**
|
|
454
|
-
*
|
|
391
|
+
* Executes a Drizzle query and returns the results.
|
|
392
|
+
* Maps the raw database results to the appropriate entity types.
|
|
455
393
|
*
|
|
456
|
-
* @
|
|
457
|
-
* @
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
394
|
+
* @template T - The type of the query builder
|
|
395
|
+
* @param {T} query - The Drizzle query to execute
|
|
396
|
+
* @returns {Promise<Awaited<T>>} The query results mapped to entity types
|
|
397
|
+
*/
|
|
398
|
+
async executeQuery(query) {
|
|
399
|
+
const queryType = query;
|
|
400
|
+
const querySql = queryType.toSQL();
|
|
401
|
+
const datas = await this.executeRawSQL(querySql.sql, querySql.params);
|
|
402
|
+
if (!datas.length) return [];
|
|
403
|
+
return datas.map((r) => {
|
|
404
|
+
const rawModel = r;
|
|
405
|
+
const newModel = {};
|
|
406
|
+
const columns = queryType.config.fields;
|
|
407
|
+
Object.entries(columns).forEach(([name, column]) => {
|
|
408
|
+
const { realColumn, aliasName } = this.extractColumnInfo(column);
|
|
409
|
+
const value = rawModel[aliasName];
|
|
410
|
+
if (value === null || value === void 0) {
|
|
411
|
+
newModel[name] = value;
|
|
412
|
+
return;
|
|
413
|
+
}
|
|
414
|
+
newModel[name] = this.parseColumnValue(realColumn, value);
|
|
415
|
+
});
|
|
416
|
+
return newModel;
|
|
417
|
+
});
|
|
464
418
|
}
|
|
465
|
-
}
|
|
466
|
-
class DynamicEntity {
|
|
467
419
|
/**
|
|
468
|
-
*
|
|
469
|
-
* @param
|
|
470
|
-
* @returns
|
|
420
|
+
* Extracts column information and alias name from a column definition.
|
|
421
|
+
* @param {any} column - The column definition from Drizzle
|
|
422
|
+
* @returns {Object} Object containing the real column and its alias name
|
|
471
423
|
*/
|
|
472
|
-
|
|
473
|
-
|
|
424
|
+
extractColumnInfo(column) {
|
|
425
|
+
if (column instanceof drizzleOrm.SQL) {
|
|
426
|
+
const realColumnSql = column;
|
|
427
|
+
const realColumn = realColumnSql.queryChunks.find(
|
|
428
|
+
(q) => q instanceof drizzleOrm.Column
|
|
429
|
+
);
|
|
430
|
+
let stringChunk = this.findAliasChunk(realColumnSql);
|
|
431
|
+
let withoutAlias = false;
|
|
432
|
+
if (!realColumn && (!stringChunk || !stringChunk.value || !stringChunk.value?.length)) {
|
|
433
|
+
stringChunk = realColumnSql.queryChunks.filter((q) => q instanceof drizzleOrm.StringChunk).find((q) => q.value[0]);
|
|
434
|
+
withoutAlias = true;
|
|
435
|
+
}
|
|
436
|
+
const aliasName = this.resolveAliasName(stringChunk, realColumn, withoutAlias);
|
|
437
|
+
return { realColumn, aliasName };
|
|
438
|
+
}
|
|
439
|
+
return { realColumn: column, aliasName: column.name };
|
|
474
440
|
}
|
|
475
441
|
/**
|
|
476
|
-
*
|
|
477
|
-
* @param
|
|
478
|
-
* @returns The
|
|
442
|
+
* Finds the alias chunk in SQL query chunks.
|
|
443
|
+
* @param {SQL} realColumnSql - The SQL query chunks
|
|
444
|
+
* @returns {StringChunk | undefined} The string chunk containing the alias or undefined
|
|
479
445
|
*/
|
|
480
|
-
|
|
481
|
-
return
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
class EntitySchemaBuilder {
|
|
485
|
-
constructor(entityClass) {
|
|
486
|
-
this.entityClass = entityClass;
|
|
446
|
+
findAliasChunk(realColumnSql) {
|
|
447
|
+
return realColumnSql.queryChunks.filter((q) => q instanceof drizzleOrm.StringChunk).find(
|
|
448
|
+
(q) => q.value.find((f) => f.toLowerCase().includes("as"))
|
|
449
|
+
);
|
|
487
450
|
}
|
|
488
|
-
properties = {};
|
|
489
451
|
/**
|
|
490
|
-
*
|
|
491
|
-
* @param
|
|
492
|
-
* @param
|
|
493
|
-
* @
|
|
452
|
+
* Resolves the alias name from the string chunk or column.
|
|
453
|
+
* @param {StringChunk | undefined} stringChunk - The string chunk containing the alias
|
|
454
|
+
* @param {Column | undefined} realColumn - The real column definition
|
|
455
|
+
* @param {boolean} withoutAlias - Whether the column has no alias
|
|
456
|
+
* @returns {string} The resolved alias name
|
|
494
457
|
*/
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
458
|
+
resolveAliasName(stringChunk, realColumn, withoutAlias) {
|
|
459
|
+
if (stringChunk) {
|
|
460
|
+
if (withoutAlias) {
|
|
461
|
+
return stringChunk.value[0];
|
|
462
|
+
}
|
|
463
|
+
const asClause = stringChunk.value.find((f) => f.toLowerCase().includes("as"));
|
|
464
|
+
return asClause ? extractAlias(asClause.trim()) : realColumn?.name || "";
|
|
465
|
+
}
|
|
466
|
+
return realColumn?.name || "";
|
|
500
467
|
}
|
|
501
468
|
/**
|
|
502
|
-
*
|
|
503
|
-
*
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
options;
|
|
522
|
-
constructor(options) {
|
|
523
|
-
this.options = options;
|
|
469
|
+
* Parses a column value based on its SQL type.
|
|
470
|
+
* Handles datetime, date, and time types with appropriate formatting.
|
|
471
|
+
*
|
|
472
|
+
* @param {Column} column - The column definition
|
|
473
|
+
* @param {unknown} value - The raw value to parse
|
|
474
|
+
* @returns {unknown} The parsed value
|
|
475
|
+
*/
|
|
476
|
+
parseColumnValue(column, value) {
|
|
477
|
+
if (!column) return value;
|
|
478
|
+
switch (column.getSQLType()) {
|
|
479
|
+
case "datetime":
|
|
480
|
+
return parseDateTime(value, "YYYY-MM-DDTHH:mm:ss.SSS");
|
|
481
|
+
case "date":
|
|
482
|
+
return parseDateTime(value, "YYYY-MM-DD");
|
|
483
|
+
case "time":
|
|
484
|
+
return parseDateTime(value, "HH:mm:ss.SSS");
|
|
485
|
+
default:
|
|
486
|
+
return value;
|
|
487
|
+
}
|
|
524
488
|
}
|
|
525
489
|
/**
|
|
526
|
-
*
|
|
527
|
-
*
|
|
528
|
-
*
|
|
529
|
-
* @
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
async
|
|
535
|
-
const results = await this.
|
|
536
|
-
|
|
490
|
+
* Executes a Drizzle query and returns a single result.
|
|
491
|
+
* Throws an error if more than one record is returned.
|
|
492
|
+
*
|
|
493
|
+
* @template T - The type of the query builder
|
|
494
|
+
* @param {T} query - The Drizzle query to execute
|
|
495
|
+
* @returns {Promise<Awaited<T> extends Array<any> ? Awaited<T>[number] | undefined : Awaited<T> | undefined>} A single result object or undefined
|
|
496
|
+
* @throws {Error} If more than one record is returned
|
|
497
|
+
*/
|
|
498
|
+
async executeQueryOnlyOne(query) {
|
|
499
|
+
const results = await this.executeQuery(query);
|
|
500
|
+
const datas = results;
|
|
501
|
+
if (!datas.length) {
|
|
537
502
|
return void 0;
|
|
538
503
|
}
|
|
539
|
-
if (
|
|
540
|
-
throw new Error(
|
|
504
|
+
if (datas.length > 1) {
|
|
505
|
+
throw new Error(`Expected 1 record but returned ${datas.length}`);
|
|
541
506
|
}
|
|
542
|
-
return
|
|
543
|
-
}
|
|
544
|
-
/**
|
|
545
|
-
* Executes a schema-based SQL query and maps the result to the entity schema.
|
|
546
|
-
* @param query - The SQL query to execute.
|
|
547
|
-
* @param schema - The entity schema defining the structure.
|
|
548
|
-
* @returns A list of mapped entity objects.
|
|
549
|
-
*/
|
|
550
|
-
async executeSchemaSQL(query, schema) {
|
|
551
|
-
const datas = await this.executeRawSQL(query);
|
|
552
|
-
if (!datas.length) return [];
|
|
553
|
-
return datas.map((r) => {
|
|
554
|
-
const rawModel = r;
|
|
555
|
-
const newModel = Object.create(schema.meta.prototype);
|
|
556
|
-
schema.meta.props.filter((p) => p.kind === "scalar").forEach((p) => {
|
|
557
|
-
const fieldName = p.name;
|
|
558
|
-
const fieldNames = p.fieldNames;
|
|
559
|
-
const rawFieldName = fieldNames && Array.isArray(fieldNames) ? fieldNames[0] : p.name;
|
|
560
|
-
switch (p.type) {
|
|
561
|
-
case "datetime":
|
|
562
|
-
newModel[fieldName] = parseDateTime(
|
|
563
|
-
rawModel[rawFieldName],
|
|
564
|
-
"YYYY-MM-DDTHH:mm:ss.SSS"
|
|
565
|
-
);
|
|
566
|
-
break;
|
|
567
|
-
case "date":
|
|
568
|
-
newModel[fieldName] = parseDateTime(rawModel[rawFieldName], "YYYY-MM-DD");
|
|
569
|
-
break;
|
|
570
|
-
case "time":
|
|
571
|
-
newModel[fieldName] = parseDateTime(rawModel[rawFieldName], "HH:mm:ss.SSS");
|
|
572
|
-
break;
|
|
573
|
-
default:
|
|
574
|
-
newModel[fieldName] = rawModel[rawFieldName];
|
|
575
|
-
}
|
|
576
|
-
});
|
|
577
|
-
return newModel;
|
|
578
|
-
});
|
|
507
|
+
return datas[0];
|
|
579
508
|
}
|
|
580
509
|
/**
|
|
581
510
|
* Executes a raw SQL query and returns the results.
|
|
582
|
-
*
|
|
583
|
-
*
|
|
511
|
+
* Logs the query if logging is enabled.
|
|
512
|
+
*
|
|
513
|
+
* @template T - The type of the result objects
|
|
514
|
+
* @param {string} query - The raw SQL query to execute
|
|
515
|
+
* @param {SqlParameters[]} [params] - Optional SQL parameters
|
|
516
|
+
* @returns {Promise<T[]>} A list of results as objects
|
|
584
517
|
*/
|
|
585
|
-
async executeRawSQL(query) {
|
|
518
|
+
async executeRawSQL(query, params) {
|
|
586
519
|
if (this.options.logRawSqlQuery) {
|
|
587
520
|
console.debug("Executing raw SQL: " + query);
|
|
588
521
|
}
|
|
589
|
-
const sqlStatement =
|
|
590
|
-
|
|
522
|
+
const sqlStatement = sql.sql.prepare(query);
|
|
523
|
+
if (params) {
|
|
524
|
+
if (this.options.logRawSqlQuery && this.options.logRawSqlQueryParams) {
|
|
525
|
+
console.debug("Executing with SQL Params: " + JSON.stringify(params));
|
|
526
|
+
}
|
|
527
|
+
sqlStatement.bindParams(...params);
|
|
528
|
+
}
|
|
529
|
+
const result = await sqlStatement.execute();
|
|
530
|
+
return result.rows;
|
|
591
531
|
}
|
|
592
532
|
/**
|
|
593
533
|
* Executes a raw SQL update query.
|
|
594
|
-
* @param query - The raw SQL update query
|
|
595
|
-
* @param params -
|
|
596
|
-
* @returns The update response containing affected rows
|
|
534
|
+
* @param {string} query - The raw SQL update query
|
|
535
|
+
* @param {SqlParameters[]} [params] - Optional SQL parameters
|
|
536
|
+
* @returns {Promise<UpdateQueryResponse>} The update response containing affected rows
|
|
597
537
|
*/
|
|
598
538
|
async executeRawUpdateSQL(query, params) {
|
|
599
539
|
const sqlStatement = sql.sql.prepare(query);
|
|
600
540
|
if (params) {
|
|
601
|
-
sqlStatement.bindParams(params);
|
|
541
|
+
sqlStatement.bindParams(...params);
|
|
602
542
|
}
|
|
603
543
|
const updateQueryResponseResults = await sqlStatement.execute();
|
|
604
544
|
return updateQueryResponseResults.rows;
|
|
@@ -606,39 +546,23 @@ class ForgeSQLSelectOperations {
|
|
|
606
546
|
}
|
|
607
547
|
class ForgeSQLORMImpl {
|
|
608
548
|
static instance = null;
|
|
609
|
-
|
|
549
|
+
drizzle;
|
|
610
550
|
crudOperations;
|
|
611
551
|
fetchOperations;
|
|
612
552
|
/**
|
|
613
553
|
* Private constructor to enforce singleton behavior.
|
|
614
|
-
* @param entities - The list of entities for ORM initialization.
|
|
615
554
|
* @param options - Options for configuring ForgeSQL ORM behavior.
|
|
616
555
|
*/
|
|
617
|
-
constructor(
|
|
556
|
+
constructor(options) {
|
|
618
557
|
try {
|
|
619
|
-
const newOptions = options ?? {
|
|
558
|
+
const newOptions = options ?? {
|
|
559
|
+
logRawSqlQuery: false,
|
|
560
|
+
disableOptimisticLocking: false
|
|
561
|
+
};
|
|
620
562
|
if (newOptions.logRawSqlQuery) {
|
|
621
563
|
console.debug("Initializing ForgeSQLORM...");
|
|
622
564
|
}
|
|
623
|
-
this.
|
|
624
|
-
dbName: "inmemory",
|
|
625
|
-
schemaGenerator: {
|
|
626
|
-
disableForeignKeys: false
|
|
627
|
-
},
|
|
628
|
-
discovery: {
|
|
629
|
-
warnWhenNoEntities: true
|
|
630
|
-
},
|
|
631
|
-
resultCache: {
|
|
632
|
-
adapter: mysql.NullCacheAdapter
|
|
633
|
-
},
|
|
634
|
-
metadataCache: {
|
|
635
|
-
enabled: false,
|
|
636
|
-
adapter: mysql.MemoryCacheAdapter
|
|
637
|
-
},
|
|
638
|
-
entities,
|
|
639
|
-
preferTs: false,
|
|
640
|
-
debug: false
|
|
641
|
-
});
|
|
565
|
+
this.drizzle = mysql2.drizzle("");
|
|
642
566
|
this.crudOperations = new ForgeSQLCrudOperations(this, newOptions);
|
|
643
567
|
this.fetchOperations = new ForgeSQLSelectOperations(newOptions);
|
|
644
568
|
} catch (error) {
|
|
@@ -648,13 +572,12 @@ class ForgeSQLORMImpl {
|
|
|
648
572
|
}
|
|
649
573
|
/**
|
|
650
574
|
* Returns the singleton instance of ForgeSQLORMImpl.
|
|
651
|
-
* @param entities - List of entities (required only on first initialization).
|
|
652
575
|
* @param options - Options for configuring ForgeSQL ORM behavior.
|
|
653
576
|
* @returns The singleton instance of ForgeSQLORMImpl.
|
|
654
577
|
*/
|
|
655
|
-
static getInstance(
|
|
578
|
+
static getInstance(options) {
|
|
656
579
|
if (!ForgeSQLORMImpl.instance) {
|
|
657
|
-
ForgeSQLORMImpl.instance = new ForgeSQLORMImpl(
|
|
580
|
+
ForgeSQLORMImpl.instance = new ForgeSQLORMImpl(options);
|
|
658
581
|
}
|
|
659
582
|
return ForgeSQLORMImpl.instance;
|
|
660
583
|
}
|
|
@@ -673,28 +596,22 @@ class ForgeSQLORMImpl {
|
|
|
673
596
|
return this.fetchOperations;
|
|
674
597
|
}
|
|
675
598
|
/**
|
|
676
|
-
*
|
|
677
|
-
*
|
|
678
|
-
*
|
|
679
|
-
*
|
|
680
|
-
*
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
return this.mikroORM.em.createQueryBuilder(entityName, alias, void 0, loggerContext);
|
|
684
|
-
}
|
|
685
|
-
/**
|
|
686
|
-
* Provides access to the underlying Knex instance for building complex query parts.
|
|
687
|
-
* enabling advanced query customization and performance tuning.
|
|
688
|
-
* @returns The Knex instance, which can be used for query building.
|
|
599
|
+
* Returns a Drizzle query builder instance.
|
|
600
|
+
*
|
|
601
|
+
* ⚠️ IMPORTANT: This method should be used ONLY for query building purposes.
|
|
602
|
+
* The returned instance should NOT be used for direct database connections or query execution.
|
|
603
|
+
* All database operations should be performed through Forge SQL's executeRawSQL or executeRawUpdateSQL methods.
|
|
604
|
+
*
|
|
605
|
+
* @returns A Drizzle query builder instance for query construction only.
|
|
689
606
|
*/
|
|
690
|
-
|
|
691
|
-
return this.
|
|
607
|
+
getDrizzleQueryBuilder() {
|
|
608
|
+
return this.drizzle;
|
|
692
609
|
}
|
|
693
610
|
}
|
|
694
611
|
class ForgeSQLORM {
|
|
695
612
|
ormInstance;
|
|
696
|
-
constructor(
|
|
697
|
-
this.ormInstance = ForgeSQLORMImpl.getInstance(
|
|
613
|
+
constructor(options) {
|
|
614
|
+
this.ormInstance = ForgeSQLORMImpl.getInstance(options);
|
|
698
615
|
}
|
|
699
616
|
/**
|
|
700
617
|
* Proxies the `crud` method from `ForgeSQLORMImpl`.
|
|
@@ -710,30 +627,75 @@ class ForgeSQLORM {
|
|
|
710
627
|
fetch() {
|
|
711
628
|
return this.ormInstance.fetch();
|
|
712
629
|
}
|
|
713
|
-
getKnex() {
|
|
714
|
-
return this.ormInstance.getKnex();
|
|
715
|
-
}
|
|
716
630
|
/**
|
|
717
|
-
*
|
|
718
|
-
*
|
|
631
|
+
* Returns a Drizzle query builder instance.
|
|
632
|
+
*
|
|
633
|
+
* ⚠️ IMPORTANT: This method should be used ONLY for query building purposes.
|
|
634
|
+
* The returned instance should NOT be used for direct database connections or query execution.
|
|
635
|
+
* All database operations should be performed through Forge SQL's executeRawSQL or executeRawUpdateSQL methods.
|
|
636
|
+
*
|
|
637
|
+
* @returns A Drizzle query builder instance for query construction only.
|
|
719
638
|
*/
|
|
720
|
-
|
|
721
|
-
return this.ormInstance.
|
|
639
|
+
getDrizzleQueryBuilder() {
|
|
640
|
+
return this.ormInstance.getDrizzleQueryBuilder();
|
|
722
641
|
}
|
|
723
642
|
}
|
|
643
|
+
const mySqlDateTimeString = mysqlCore.customType({
|
|
644
|
+
dataType() {
|
|
645
|
+
return "datetime";
|
|
646
|
+
},
|
|
647
|
+
toDriver(value) {
|
|
648
|
+
return moment$1(value).format("YYYY-MM-DDTHH:mm:ss.SSS");
|
|
649
|
+
},
|
|
650
|
+
fromDriver(value) {
|
|
651
|
+
const format = "YYYY-MM-DDTHH:mm:ss.SSS";
|
|
652
|
+
return parseDateTime(value, format);
|
|
653
|
+
}
|
|
654
|
+
});
|
|
655
|
+
const mySqlTimestampString = mysqlCore.customType({
|
|
656
|
+
dataType() {
|
|
657
|
+
return "timestamp";
|
|
658
|
+
},
|
|
659
|
+
toDriver(value) {
|
|
660
|
+
return moment$1(value).format("YYYY-MM-DDTHH:mm:ss.SSS");
|
|
661
|
+
},
|
|
662
|
+
fromDriver(value) {
|
|
663
|
+
const format = "YYYY-MM-DDTHH:mm:ss.SSS";
|
|
664
|
+
return parseDateTime(value, format);
|
|
665
|
+
}
|
|
666
|
+
});
|
|
667
|
+
const mySqlDateString = mysqlCore.customType({
|
|
668
|
+
dataType() {
|
|
669
|
+
return "date";
|
|
670
|
+
},
|
|
671
|
+
toDriver(value) {
|
|
672
|
+
return moment$1(value).format("YYYY-MM-DD");
|
|
673
|
+
},
|
|
674
|
+
fromDriver(value) {
|
|
675
|
+
const format = "YYYY-MM-DD";
|
|
676
|
+
return parseDateTime(value, format);
|
|
677
|
+
}
|
|
678
|
+
});
|
|
679
|
+
const mySqlTimeString = mysqlCore.customType({
|
|
680
|
+
dataType() {
|
|
681
|
+
return "time";
|
|
682
|
+
},
|
|
683
|
+
toDriver(value) {
|
|
684
|
+
return moment$1(value).format("HH:mm:ss.SSS");
|
|
685
|
+
},
|
|
686
|
+
fromDriver(value) {
|
|
687
|
+
return parseDateTime(value, "HH:mm:ss.SSS");
|
|
688
|
+
}
|
|
689
|
+
});
|
|
724
690
|
exports.ForgeSQLCrudOperations = ForgeSQLCrudOperations;
|
|
725
691
|
exports.ForgeSQLSelectOperations = ForgeSQLSelectOperations;
|
|
726
692
|
exports.default = ForgeSQLORM;
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
enumerable: true,
|
|
736
|
-
get: () => entityGenerator[k]
|
|
737
|
-
});
|
|
738
|
-
});
|
|
693
|
+
exports.extractAlias = extractAlias;
|
|
694
|
+
exports.getPrimaryKeys = getPrimaryKeys;
|
|
695
|
+
exports.getTableMetadata = getTableMetadata;
|
|
696
|
+
exports.mySqlDateString = mySqlDateString;
|
|
697
|
+
exports.mySqlDateTimeString = mySqlDateTimeString;
|
|
698
|
+
exports.mySqlTimeString = mySqlTimeString;
|
|
699
|
+
exports.mySqlTimestampString = mySqlTimestampString;
|
|
700
|
+
exports.parseDateTime = parseDateTime;
|
|
739
701
|
//# sourceMappingURL=ForgeSQLORM.js.map
|