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