masterrecord 0.3.16 → 0.3.18
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/Migrations/cli.js +7 -7
- package/Migrations/schema.js +16 -15
- package/SQLLiteEngine.js +72 -48
- package/context.js +48 -38
- package/deleteManager.js +11 -11
- package/insertManager.js +7 -7
- package/mySQLAsyncConnect.js +44 -0
- package/package.json +2 -2
- package/realMySQLEngine.js +836 -0
- package/test/parameterizedPlaceholderTest.js +1 -1
- package/mySQLEngine.js +0 -1105
- package/mySQLSyncConnect.js +0 -82
package/Migrations/cli.js
CHANGED
|
@@ -153,7 +153,7 @@ program.option('-V', 'output the version');
|
|
|
153
153
|
|
|
154
154
|
if(typeof mig.createdatabase === 'function'){
|
|
155
155
|
try{
|
|
156
|
-
mig.createdatabase();
|
|
156
|
+
await mig.createdatabase();
|
|
157
157
|
console.log('✓ Database ensured');
|
|
158
158
|
await __cleanupAndExit(contextInstance, 0);
|
|
159
159
|
}catch(err){
|
|
@@ -163,7 +163,7 @@ program.option('-V', 'output the version');
|
|
|
163
163
|
}
|
|
164
164
|
} else if(typeof mig.createDatabase === 'function'){
|
|
165
165
|
try{
|
|
166
|
-
mig.createDatabase();
|
|
166
|
+
await mig.createDatabase();
|
|
167
167
|
console.log('✓ Database ensured');
|
|
168
168
|
await __cleanupAndExit(contextInstance, 0);
|
|
169
169
|
}catch(err){
|
|
@@ -447,7 +447,7 @@ program.option('-V', 'output the version');
|
|
|
447
447
|
try{
|
|
448
448
|
var newMigrationProjectInstance = new migrationProjectFile(ContextCtor);
|
|
449
449
|
var tableObj = migration.buildUpObject(contextSnapshot.schema, cleanEntities);
|
|
450
|
-
newMigrationProjectInstance.up(tableObj);
|
|
450
|
+
await newMigrationProjectInstance.up(tableObj);
|
|
451
451
|
}catch(err){
|
|
452
452
|
console.error(`\n❌ Error - Migration failed during execution`);
|
|
453
453
|
console.error(`\nMigration file: ${mFile}`);
|
|
@@ -571,7 +571,7 @@ program.option('-V', 'output the version');
|
|
|
571
571
|
var MigCtor = require(latestFile);
|
|
572
572
|
var migInstance = new MigCtor(ContextCtor);
|
|
573
573
|
if(typeof migInstance.down === 'function'){
|
|
574
|
-
migInstance.down(tableObj);
|
|
574
|
+
await migInstance.down(tableObj);
|
|
575
575
|
}else{
|
|
576
576
|
console.log(`Warning - Migration '${path.basename(latestFile)}' has no down method; skipping.`);
|
|
577
577
|
}
|
|
@@ -668,7 +668,7 @@ program.option('-V', 'output the version');
|
|
|
668
668
|
var migrationProjectFile = require(migFile);
|
|
669
669
|
var newMigrationProjectInstance = new migrationProjectFile(ContextCtor);
|
|
670
670
|
var tableObj = migration.buildUpObject(contextSnapshot.schema, cleanEntities);
|
|
671
|
-
newMigrationProjectInstance.up(tableObj);
|
|
671
|
+
await newMigrationProjectInstance.up(tableObj);
|
|
672
672
|
}
|
|
673
673
|
const snap = {
|
|
674
674
|
file : contextAbs,
|
|
@@ -820,7 +820,7 @@ program.option('-V', 'output the version');
|
|
|
820
820
|
var MigCtor = require(migFile);
|
|
821
821
|
var migInstance = new MigCtor(ContextCtor);
|
|
822
822
|
if(typeof migInstance.down === 'function'){
|
|
823
|
-
migInstance.down(tableObj);
|
|
823
|
+
await migInstance.down(tableObj);
|
|
824
824
|
} else {
|
|
825
825
|
console.log(`Warning - Migration '${path.basename(migFile)}' has no down method; skipping.`);
|
|
826
826
|
}
|
|
@@ -1004,7 +1004,7 @@ program.option('-V', 'output the version');
|
|
|
1004
1004
|
var newMigrationProjectInstance = new migrationProjectFile(ContextCtor);
|
|
1005
1005
|
var cleanEntities = migration.cleanEntities(contextInstance.__entities);
|
|
1006
1006
|
var tableObj = migration.buildUpObject(entry.cs.schema, cleanEntities);
|
|
1007
|
-
newMigrationProjectInstance.up(tableObj);
|
|
1007
|
+
await newMigrationProjectInstance.up(tableObj);
|
|
1008
1008
|
var snap = {
|
|
1009
1009
|
file : entry.contextAbs,
|
|
1010
1010
|
executedLocation : executedLocation,
|
package/Migrations/schema.js
CHANGED
|
@@ -82,13 +82,13 @@ class schema{
|
|
|
82
82
|
}
|
|
83
83
|
}
|
|
84
84
|
|
|
85
|
-
createTable(table){
|
|
85
|
+
async createTable(table){
|
|
86
86
|
|
|
87
87
|
if(table){
|
|
88
88
|
// If table exists, run sync instead of blind create
|
|
89
89
|
const tableName = table.__name;
|
|
90
|
-
if(this.context._SQLEngine.tableExists && this.context._SQLEngine.tableExists(tableName)){
|
|
91
|
-
this.syncTable(table);
|
|
90
|
+
if(this.context._SQLEngine.tableExists && await this.context._SQLEngine.tableExists(tableName)){
|
|
91
|
+
await this.syncTable(table);
|
|
92
92
|
} else {
|
|
93
93
|
if(this.context.isSQLite){
|
|
94
94
|
var sqliteQuery = require("./migrationSQLiteQuery");
|
|
@@ -117,10 +117,10 @@ class schema{
|
|
|
117
117
|
}
|
|
118
118
|
|
|
119
119
|
// Compute diffs and apply minimal changes
|
|
120
|
-
syncTable(table){
|
|
120
|
+
async syncTable(table){
|
|
121
121
|
const engine = this.context._SQLEngine;
|
|
122
122
|
const tableName = table.__name;
|
|
123
|
-
const existing = engine.getTableInfo ? engine.getTableInfo(tableName) : [];
|
|
123
|
+
const existing = engine.getTableInfo ? await engine.getTableInfo(tableName) : [];
|
|
124
124
|
// Build a set of existing columns (sqlite: name, mysql: name)
|
|
125
125
|
const existingNames = new Set((existing || []).map(c => (c.name || c.COLUMN_NAME))); // both engines map to name
|
|
126
126
|
// Add missing columns only (safe path)
|
|
@@ -254,7 +254,7 @@ class schema{
|
|
|
254
254
|
const create = queryBuilder.createTable(table);
|
|
255
255
|
this.context._execute(create);
|
|
256
256
|
// compute common columns
|
|
257
|
-
const oldInfo = engine.getTableInfo(tableName.replace(/.*/, '_temp_alter_column_update')) || engine.getTableInfo("_temp_alter_column_update");
|
|
257
|
+
const oldInfo = await engine.getTableInfo(tableName.replace(/.*/, '_temp_alter_column_update')) || await engine.getTableInfo("_temp_alter_column_update");
|
|
258
258
|
const oldNames = new Set((oldInfo || existing).map(r => r.name));
|
|
259
259
|
const newNames = desiredCols.map(d => d.name);
|
|
260
260
|
const common = newNames.filter(n => oldNames.has(n));
|
|
@@ -295,32 +295,33 @@ class schema{
|
|
|
295
295
|
}
|
|
296
296
|
|
|
297
297
|
// EnsureCreated equivalent for MySQL: create DB if missing
|
|
298
|
-
createDatabase(){
|
|
298
|
+
async createDatabase(){
|
|
299
299
|
try{
|
|
300
300
|
if(!(this.context && this.context.isMySQL)){ return; }
|
|
301
|
-
const
|
|
301
|
+
const MySQLAsyncClient = require('masterrecord/mySQLAsyncConnect');
|
|
302
302
|
const client = this.context.db; // main client (may not be connected yet)
|
|
303
303
|
if(!client || !client.config || !client.config.database){ return; }
|
|
304
304
|
const dbName = client.config.database;
|
|
305
305
|
// Build server-level connection (no database)
|
|
306
306
|
const baseConfig = { ...client.config };
|
|
307
307
|
delete baseConfig.database;
|
|
308
|
-
const admin = new
|
|
309
|
-
admin.connect();
|
|
310
|
-
|
|
308
|
+
const admin = new MySQLAsyncClient(baseConfig);
|
|
309
|
+
await admin.connect();
|
|
310
|
+
const pool = admin.getPool();
|
|
311
|
+
if(!pool){ return; }
|
|
311
312
|
|
|
312
313
|
// Use parameterized query for checking database existence
|
|
313
|
-
const
|
|
314
|
-
const exists = Array.isArray(
|
|
314
|
+
const [rows] = await pool.execute(`SELECT SCHEMA_NAME FROM INFORMATION_SCHEMA.SCHEMATA WHERE SCHEMA_NAME = ?`, [dbName]);
|
|
315
|
+
const exists = Array.isArray(rows) && rows.length > 0;
|
|
315
316
|
if(!exists){
|
|
316
317
|
// Validate database name (alphanumeric, underscore, hyphen only)
|
|
317
318
|
if(!/^[a-zA-Z0-9_-]+$/.test(dbName)){
|
|
318
319
|
throw new Error(`Invalid database name: ${dbName}. Only alphanumeric characters, underscores, and hyphens are allowed.`);
|
|
319
320
|
}
|
|
320
321
|
// CREATE DATABASE doesn't support placeholders, but we've validated the name
|
|
321
|
-
|
|
322
|
+
await pool.execute(`CREATE DATABASE \`${dbName}\` DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci`);
|
|
322
323
|
}
|
|
323
|
-
admin.close();
|
|
324
|
+
await admin.close();
|
|
324
325
|
this._dbEnsured = true;
|
|
325
326
|
}catch(err){
|
|
326
327
|
// Non-fatal: migrations may still proceed if DB already exists or permissions blocked
|
package/SQLLiteEngine.js
CHANGED
|
@@ -6,7 +6,7 @@ class SQLLiteEngine {
|
|
|
6
6
|
|
|
7
7
|
unsupportedWords = ["order"]
|
|
8
8
|
|
|
9
|
-
update(query){
|
|
9
|
+
async update(query){
|
|
10
10
|
// Security: ONLY use parameterized queries - no fallback to string concatenation
|
|
11
11
|
// query.arg must contain {sql, params} from _buildSQLEqualToParameterized
|
|
12
12
|
if(!query.arg || typeof query.arg !== 'object' || !query.arg.sql || !query.arg.params){
|
|
@@ -18,17 +18,17 @@ class SQLLiteEngine {
|
|
|
18
18
|
WHERE [${query.tableName}].[${query.primaryKey}] = ?`;
|
|
19
19
|
// Add primaryKeyValue to params array
|
|
20
20
|
var params = [...query.arg.params, query.primaryKeyValue];
|
|
21
|
-
return this._runWithParams(sqlQuery, params);
|
|
21
|
+
return Promise.resolve(this._runWithParams(sqlQuery, params));
|
|
22
22
|
}
|
|
23
23
|
|
|
24
|
-
delete(queryObject){
|
|
24
|
+
async delete(queryObject){
|
|
25
25
|
var sqlObject = this._buildDeleteObject(queryObject);
|
|
26
26
|
// Use parameterized query to prevent SQL injection
|
|
27
27
|
var sqlQuery = `DELETE FROM [${sqlObject.tableName}] WHERE [${sqlObject.tableName}].[${sqlObject.primaryKey}] = ?`;
|
|
28
|
-
return this._executeWithParams(sqlQuery, [sqlObject.value]);
|
|
28
|
+
return Promise.resolve(this._executeWithParams(sqlQuery, [sqlObject.value]));
|
|
29
29
|
}
|
|
30
30
|
|
|
31
|
-
insert(queryObject){
|
|
31
|
+
async insert(queryObject){
|
|
32
32
|
// Use NEW SECURE parameterized version
|
|
33
33
|
var sqlObject = this._buildSQLInsertObjectParameterized(queryObject, queryObject.__entity);
|
|
34
34
|
if(sqlObject === -1){
|
|
@@ -40,28 +40,35 @@ class SQLLiteEngine {
|
|
|
40
40
|
var open = {
|
|
41
41
|
"id": queryObj.lastInsertRowid
|
|
42
42
|
};
|
|
43
|
-
return open;
|
|
43
|
+
return Promise.resolve(open);
|
|
44
44
|
}
|
|
45
45
|
|
|
46
46
|
/**
|
|
47
47
|
* Batch insert multiple entities in a single transaction
|
|
48
48
|
* Performance: 100x faster than N separate inserts
|
|
49
49
|
*/
|
|
50
|
-
bulkInsert(entities) {
|
|
51
|
-
if (!entities || entities.length === 0) return [];
|
|
50
|
+
async bulkInsert(entities) {
|
|
51
|
+
if (!entities || entities.length === 0) return Promise.resolve([]);
|
|
52
52
|
|
|
53
53
|
const results = [];
|
|
54
|
-
// SQLite: Use transaction for batch inserts
|
|
55
|
-
this.
|
|
54
|
+
// SQLite: Use transaction for batch inserts (only if not already in one)
|
|
55
|
+
const needsTransaction = !this.db.inTransaction;
|
|
56
|
+
if (needsTransaction) {
|
|
57
|
+
await this.startTransaction();
|
|
58
|
+
}
|
|
56
59
|
try {
|
|
57
60
|
for (const entity of entities) {
|
|
58
|
-
const result = this.insert(entity);
|
|
61
|
+
const result = await this.insert(entity);
|
|
59
62
|
results.push(result);
|
|
60
63
|
}
|
|
61
|
-
|
|
62
|
-
|
|
64
|
+
if (needsTransaction) {
|
|
65
|
+
await this.endTransaction();
|
|
66
|
+
}
|
|
67
|
+
return Promise.resolve(results);
|
|
63
68
|
} catch (error) {
|
|
64
|
-
|
|
69
|
+
if (needsTransaction) {
|
|
70
|
+
await this.errorTransaction();
|
|
71
|
+
}
|
|
65
72
|
throw error;
|
|
66
73
|
}
|
|
67
74
|
}
|
|
@@ -69,17 +76,25 @@ class SQLLiteEngine {
|
|
|
69
76
|
/**
|
|
70
77
|
* Batch update multiple entities
|
|
71
78
|
*/
|
|
72
|
-
bulkUpdate(updateQueries) {
|
|
73
|
-
if (!updateQueries || updateQueries.length === 0) return;
|
|
79
|
+
async bulkUpdate(updateQueries) {
|
|
80
|
+
if (!updateQueries || updateQueries.length === 0) return Promise.resolve();
|
|
74
81
|
|
|
75
|
-
|
|
82
|
+
// Only start transaction if not already in one
|
|
83
|
+
const needsTransaction = !this.db.inTransaction;
|
|
84
|
+
if (needsTransaction) {
|
|
85
|
+
await this.startTransaction();
|
|
86
|
+
}
|
|
76
87
|
try {
|
|
77
88
|
for (const query of updateQueries) {
|
|
78
|
-
this.update(query);
|
|
89
|
+
await this.update(query);
|
|
90
|
+
}
|
|
91
|
+
if (needsTransaction) {
|
|
92
|
+
await this.endTransaction();
|
|
79
93
|
}
|
|
80
|
-
this.endTransaction();
|
|
81
94
|
} catch (error) {
|
|
82
|
-
|
|
95
|
+
if (needsTransaction) {
|
|
96
|
+
await this.errorTransaction();
|
|
97
|
+
}
|
|
83
98
|
throw error;
|
|
84
99
|
}
|
|
85
100
|
}
|
|
@@ -87,15 +102,15 @@ class SQLLiteEngine {
|
|
|
87
102
|
/**
|
|
88
103
|
* Batch delete multiple entities using WHERE IN
|
|
89
104
|
*/
|
|
90
|
-
bulkDelete(tableName, ids) {
|
|
91
|
-
if (!ids || ids.length === 0) return;
|
|
105
|
+
async bulkDelete(tableName, ids) {
|
|
106
|
+
if (!ids || ids.length === 0) return Promise.resolve();
|
|
92
107
|
|
|
93
108
|
const placeholders = ids.map(() => '?').join(', ');
|
|
94
109
|
const query = `DELETE FROM [${tableName}] WHERE id IN (${placeholders})`;
|
|
95
|
-
return this._runWithParams(query, ids);
|
|
110
|
+
return Promise.resolve(this._runWithParams(query, ids));
|
|
96
111
|
}
|
|
97
112
|
|
|
98
|
-
get(query, entity, context){
|
|
113
|
+
async get(query, entity, context){
|
|
99
114
|
var queryString = {};
|
|
100
115
|
try {
|
|
101
116
|
if(query.raw){
|
|
@@ -117,26 +132,26 @@ class SQLLiteEngine {
|
|
|
117
132
|
console.debug("[Params]", params);
|
|
118
133
|
}
|
|
119
134
|
var queryReturn = this.db.prepare(queryString.query).get(...params);
|
|
120
|
-
return queryReturn;
|
|
135
|
+
return Promise.resolve(queryReturn);
|
|
121
136
|
}
|
|
122
|
-
return null;
|
|
137
|
+
return Promise.resolve(null);
|
|
123
138
|
} catch (err) {
|
|
124
139
|
console.error(err);
|
|
125
|
-
return null;
|
|
140
|
+
return Promise.resolve(null);
|
|
126
141
|
}
|
|
127
142
|
}
|
|
128
143
|
|
|
129
144
|
// Introspection helpers
|
|
130
|
-
tableExists(tableName){
|
|
145
|
+
async tableExists(tableName){
|
|
131
146
|
try{
|
|
132
147
|
// Use parameterized query to prevent SQL injection
|
|
133
148
|
const sql = `SELECT name FROM sqlite_master WHERE type='table' AND name=?`;
|
|
134
149
|
const row = this.db.prepare(sql).get(tableName);
|
|
135
|
-
return !!row;
|
|
136
|
-
}catch(_){ return false; }
|
|
150
|
+
return Promise.resolve(!!row);
|
|
151
|
+
}catch(_){ return Promise.resolve(false); }
|
|
137
152
|
}
|
|
138
153
|
|
|
139
|
-
getTableInfo(tableName){
|
|
154
|
+
async getTableInfo(tableName){
|
|
140
155
|
try{
|
|
141
156
|
// Security: Validate table name to prevent SQL injection
|
|
142
157
|
// PRAGMA statements don't support parameterized queries
|
|
@@ -149,11 +164,11 @@ class SQLLiteEngine {
|
|
|
149
164
|
}
|
|
150
165
|
const sql = `PRAGMA table_info(${tableName})`;
|
|
151
166
|
const rows = this.db.prepare(sql).all();
|
|
152
|
-
return rows || [];
|
|
153
|
-
}catch(_){ return []; }
|
|
167
|
+
return Promise.resolve(rows || []);
|
|
168
|
+
}catch(_){ return Promise.resolve([]); }
|
|
154
169
|
}
|
|
155
170
|
|
|
156
|
-
getCount(queryObject, entity, context){
|
|
171
|
+
async getCount(queryObject, entity, context){
|
|
157
172
|
var query = queryObject.script;
|
|
158
173
|
var queryString = {};
|
|
159
174
|
try {
|
|
@@ -176,23 +191,23 @@ class SQLLiteEngine {
|
|
|
176
191
|
console.debug("[Params]", params);
|
|
177
192
|
}
|
|
178
193
|
var queryReturn = this.db.prepare(queryCount).get(...params);
|
|
179
|
-
return queryReturn;
|
|
194
|
+
return Promise.resolve(queryReturn);
|
|
180
195
|
}
|
|
181
|
-
return null;
|
|
196
|
+
return Promise.resolve(null);
|
|
182
197
|
} catch (err) {
|
|
183
198
|
console.error(err);
|
|
184
|
-
return null;
|
|
199
|
+
return Promise.resolve(null);
|
|
185
200
|
}
|
|
186
201
|
}
|
|
187
202
|
|
|
188
|
-
all(query, entity, context){
|
|
203
|
+
async all(query, entity, context){
|
|
189
204
|
var selectQuery = {};
|
|
190
205
|
try {
|
|
191
206
|
if(query.raw){
|
|
192
207
|
selectQuery.query = query.raw;
|
|
193
208
|
}
|
|
194
209
|
else{
|
|
195
|
-
|
|
210
|
+
|
|
196
211
|
selectQuery = this.buildQuery(query, entity, context);
|
|
197
212
|
}
|
|
198
213
|
if(selectQuery.query){
|
|
@@ -203,12 +218,12 @@ class SQLLiteEngine {
|
|
|
203
218
|
console.debug("[Params]", params);
|
|
204
219
|
}
|
|
205
220
|
var queryReturn = this.db.prepare(selectQuery.query).all(...params);
|
|
206
|
-
return queryReturn;
|
|
221
|
+
return Promise.resolve(queryReturn);
|
|
207
222
|
}
|
|
208
|
-
return null;
|
|
223
|
+
return Promise.resolve(null);
|
|
209
224
|
} catch (err) {
|
|
210
225
|
console.error(err);
|
|
211
|
-
return null;
|
|
226
|
+
return Promise.resolve(null);
|
|
212
227
|
}
|
|
213
228
|
}
|
|
214
229
|
|
|
@@ -623,16 +638,25 @@ class SQLLiteEngine {
|
|
|
623
638
|
return false;
|
|
624
639
|
}
|
|
625
640
|
|
|
626
|
-
startTransaction(){
|
|
627
|
-
|
|
641
|
+
async startTransaction(){
|
|
642
|
+
// Prevent nested transactions (SQLite limitation)
|
|
643
|
+
return Promise.resolve(
|
|
644
|
+
this.db.inTransaction ? null : this.db.prepare('BEGIN').run()
|
|
645
|
+
);
|
|
628
646
|
}
|
|
629
647
|
|
|
630
|
-
endTransaction(){
|
|
631
|
-
|
|
648
|
+
async endTransaction(){
|
|
649
|
+
// Only commit if transaction is active
|
|
650
|
+
return Promise.resolve(
|
|
651
|
+
this.db.inTransaction ? this.db.prepare('COMMIT').run() : null
|
|
652
|
+
);
|
|
632
653
|
}
|
|
633
654
|
|
|
634
|
-
errorTransaction(){
|
|
635
|
-
|
|
655
|
+
async errorTransaction(){
|
|
656
|
+
// Only rollback if transaction is active
|
|
657
|
+
return Promise.resolve(
|
|
658
|
+
this.db.inTransaction ? this.db.prepare('ROLLBACK').run() : null
|
|
659
|
+
);
|
|
636
660
|
}
|
|
637
661
|
|
|
638
662
|
_buildSQLEqualTo(model){
|
package/context.js
CHANGED
|
@@ -20,7 +20,7 @@ const modelBuilder = require('./Entity/entityModelBuilder');
|
|
|
20
20
|
const query = require('masterrecord/QueryLanguage/queryMethods');
|
|
21
21
|
const tools = require('./Tools');
|
|
22
22
|
const SQLLiteEngine = require('masterrecord/SQLLiteEngine');
|
|
23
|
-
const
|
|
23
|
+
const MySQLEngine = require('masterrecord/realMySQLEngine');
|
|
24
24
|
const PostgresEngine = require('masterrecord/postgresEngine');
|
|
25
25
|
const insertManager = require('./insertManager');
|
|
26
26
|
const deleteManager = require('./deleteManager');
|
|
@@ -28,7 +28,7 @@ const globSearch = require('glob');
|
|
|
28
28
|
const fs = require('fs');
|
|
29
29
|
const path = require('path');
|
|
30
30
|
const appRoot = require('app-root-path');
|
|
31
|
-
const
|
|
31
|
+
const MySQLAsyncClient = require('masterrecord/mySQLAsyncConnect');
|
|
32
32
|
const PostgresClient = require('masterrecord/postgresSyncConnect');
|
|
33
33
|
const QueryCache = require('./Cache/QueryCache');
|
|
34
34
|
|
|
@@ -284,7 +284,7 @@ class context {
|
|
|
284
284
|
* @returns {object} MySQL connection instance
|
|
285
285
|
* @throws {DatabaseConnectionError} If connection fails
|
|
286
286
|
*/
|
|
287
|
-
__mysqlInit(env, sqlName) {
|
|
287
|
+
async __mysqlInit(env, sqlName) {
|
|
288
288
|
try {
|
|
289
289
|
// Validate required MySQL configuration
|
|
290
290
|
if (!env.database || typeof env.database !== 'string') {
|
|
@@ -303,11 +303,18 @@ class context {
|
|
|
303
303
|
);
|
|
304
304
|
}
|
|
305
305
|
|
|
306
|
-
|
|
307
|
-
|
|
306
|
+
console.log('[MySQL] Initializing async connection pool...');
|
|
307
|
+
const client = new MySQLAsyncClient(env);
|
|
308
|
+
await client.connect();
|
|
309
|
+
|
|
310
|
+
const pool = client.getPool();
|
|
311
|
+
this._SQLEngine = new MySQLEngine();
|
|
312
|
+
this._SQLEngine.setDB(pool);
|
|
308
313
|
this._SQLEngine.__name = sqlName;
|
|
314
|
+
this.isMySQL = true;
|
|
309
315
|
|
|
310
|
-
|
|
316
|
+
console.log('[MySQL] Connection pool ready');
|
|
317
|
+
return client;
|
|
311
318
|
} catch (error) {
|
|
312
319
|
// Preserve original error if it's already a ContextError
|
|
313
320
|
if (error instanceof ContextError) {
|
|
@@ -624,15 +631,18 @@ class context {
|
|
|
624
631
|
return this;
|
|
625
632
|
}
|
|
626
633
|
|
|
627
|
-
// MySQL initialization
|
|
634
|
+
// MySQL initialization (async)
|
|
628
635
|
if (type === DB_TYPES.MYSQL) {
|
|
629
636
|
this.isMySQL = true;
|
|
630
637
|
this.isSQLite = false;
|
|
631
638
|
this.isPostgres = false;
|
|
632
639
|
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
640
|
+
// MySQL is async - caller must await env()
|
|
641
|
+
return (async () => {
|
|
642
|
+
this.db = await this.__mysqlInit(options, 'mysql2');
|
|
643
|
+
// Note: engine is already set in __mysqlInit
|
|
644
|
+
return this;
|
|
645
|
+
})();
|
|
636
646
|
}
|
|
637
647
|
|
|
638
648
|
// PostgreSQL initialization (async)
|
|
@@ -833,7 +843,7 @@ class context {
|
|
|
833
843
|
* @example
|
|
834
844
|
* context.useMySql('./config/environments');
|
|
835
845
|
*/
|
|
836
|
-
useMySql(rootFolderLocation) {
|
|
846
|
+
async useMySql(rootFolderLocation) {
|
|
837
847
|
try {
|
|
838
848
|
this.isMySQL = true;
|
|
839
849
|
this.isSQLite = false;
|
|
@@ -857,8 +867,8 @@ class context {
|
|
|
857
867
|
}
|
|
858
868
|
|
|
859
869
|
this.validateSQLiteOptions(options);
|
|
860
|
-
this.db = this.__mysqlInit(options, 'mysql2');
|
|
861
|
-
|
|
870
|
+
this.db = await this.__mysqlInit(options, 'mysql2');
|
|
871
|
+
// Note: engine is already set in __mysqlInit
|
|
862
872
|
return this;
|
|
863
873
|
} catch (error) {
|
|
864
874
|
// Preserve original error if it's already a ContextError
|
|
@@ -966,7 +976,7 @@ class context {
|
|
|
966
976
|
* @private
|
|
967
977
|
* @param {Array<object>} tracked - Array of tracked entities
|
|
968
978
|
*/
|
|
969
|
-
_processTrackedEntities(tracked) {
|
|
979
|
+
async _processTrackedEntities(tracked) {
|
|
970
980
|
// Group entities by state for batch operations (single pass)
|
|
971
981
|
const toInsert = [];
|
|
972
982
|
const toUpdate = [];
|
|
@@ -998,17 +1008,17 @@ class context {
|
|
|
998
1008
|
|
|
999
1009
|
// Batch insert operations
|
|
1000
1010
|
if (toInsert.length > 0) {
|
|
1001
|
-
this._processBatchInserts(toInsert);
|
|
1011
|
+
await this._processBatchInserts(toInsert);
|
|
1002
1012
|
}
|
|
1003
1013
|
|
|
1004
1014
|
// Batch update operations
|
|
1005
1015
|
if (toUpdate.length > 0) {
|
|
1006
|
-
this._processBatchUpdates(toUpdate);
|
|
1016
|
+
await this._processBatchUpdates(toUpdate);
|
|
1007
1017
|
}
|
|
1008
1018
|
|
|
1009
1019
|
// Batch delete operations
|
|
1010
1020
|
if (toDelete.length > 0) {
|
|
1011
|
-
this._processBatchDeletes(toDelete);
|
|
1021
|
+
await this._processBatchDeletes(toDelete);
|
|
1012
1022
|
}
|
|
1013
1023
|
}
|
|
1014
1024
|
|
|
@@ -1018,21 +1028,21 @@ class context {
|
|
|
1018
1028
|
* @private
|
|
1019
1029
|
* @param {Array<object>} entities - Entities to insert
|
|
1020
1030
|
*/
|
|
1021
|
-
_processBatchInserts(entities) {
|
|
1031
|
+
async _processBatchInserts(entities) {
|
|
1022
1032
|
if (entities.length === 1) {
|
|
1023
1033
|
// Single insert - use existing insertManager
|
|
1024
1034
|
const insert = new insertManager(this._SQLEngine, this._isModelValid, this.__entities);
|
|
1025
|
-
insert.init(entities[0]);
|
|
1035
|
+
await insert.init(entities[0]);
|
|
1026
1036
|
} else {
|
|
1027
1037
|
// Batch insert - 100x faster for multiple records
|
|
1028
1038
|
try {
|
|
1029
|
-
this._SQLEngine.bulkInsert(entities);
|
|
1039
|
+
await this._SQLEngine.bulkInsert(entities);
|
|
1030
1040
|
} catch (error) {
|
|
1031
1041
|
console.error('[Context] Bulk insert failed, falling back to individual inserts:', error.message);
|
|
1032
1042
|
// Fallback to individual inserts
|
|
1033
1043
|
for (const entity of entities) {
|
|
1034
1044
|
const insert = new insertManager(this._SQLEngine, this._isModelValid, this.__entities);
|
|
1035
|
-
insert.init(entity);
|
|
1045
|
+
await insert.init(entity);
|
|
1036
1046
|
}
|
|
1037
1047
|
}
|
|
1038
1048
|
}
|
|
@@ -1044,7 +1054,7 @@ class context {
|
|
|
1044
1054
|
* @private
|
|
1045
1055
|
* @param {Array<object>} entities - Entities to update
|
|
1046
1056
|
*/
|
|
1047
|
-
_processBatchUpdates(entities) {
|
|
1057
|
+
async _processBatchUpdates(entities) {
|
|
1048
1058
|
if (entities.length === 1) {
|
|
1049
1059
|
// Single update - use existing logic
|
|
1050
1060
|
const currentModel = entities[0];
|
|
@@ -1059,7 +1069,7 @@ class context {
|
|
|
1059
1069
|
primaryKey: primaryKey,
|
|
1060
1070
|
primaryKeyValue: cleanCurrentModel[primaryKey]
|
|
1061
1071
|
};
|
|
1062
|
-
this._SQLEngine.update(sqlUpdate);
|
|
1072
|
+
await this._SQLEngine.update(sqlUpdate);
|
|
1063
1073
|
} else {
|
|
1064
1074
|
console.warn('[Context] Entity marked for update but no changes detected');
|
|
1065
1075
|
}
|
|
@@ -1084,12 +1094,12 @@ class context {
|
|
|
1084
1094
|
|
|
1085
1095
|
if (updateQueries.length > 0) {
|
|
1086
1096
|
try {
|
|
1087
|
-
this._SQLEngine.bulkUpdate(updateQueries);
|
|
1097
|
+
await this._SQLEngine.bulkUpdate(updateQueries);
|
|
1088
1098
|
} catch (error) {
|
|
1089
1099
|
console.error('[Context] Bulk update failed, falling back to individual updates:', error.message);
|
|
1090
1100
|
// Fallback to individual updates
|
|
1091
1101
|
for (const query of updateQueries) {
|
|
1092
|
-
this._SQLEngine.update(query);
|
|
1102
|
+
await this._SQLEngine.update(query);
|
|
1093
1103
|
}
|
|
1094
1104
|
}
|
|
1095
1105
|
}
|
|
@@ -1102,11 +1112,11 @@ class context {
|
|
|
1102
1112
|
* @private
|
|
1103
1113
|
* @param {Array<object>} entities - Entities to delete
|
|
1104
1114
|
*/
|
|
1105
|
-
_processBatchDeletes(entities) {
|
|
1115
|
+
async _processBatchDeletes(entities) {
|
|
1106
1116
|
if (entities.length === 1) {
|
|
1107
1117
|
// Single delete - use existing deleteManager
|
|
1108
1118
|
const deleteObject = new deleteManager(this._SQLEngine, this.__entities);
|
|
1109
|
-
deleteObject.init(entities[0]);
|
|
1119
|
+
await deleteObject.init(entities[0]);
|
|
1110
1120
|
} else {
|
|
1111
1121
|
// Batch delete - group by table for efficiency
|
|
1112
1122
|
const deletesByTable = new Map(); // Use Map instead of object for better performance
|
|
@@ -1125,14 +1135,14 @@ class context {
|
|
|
1125
1135
|
try {
|
|
1126
1136
|
// Performance: Use for...of with Map entries
|
|
1127
1137
|
for (const [tableName, ids] of deletesByTable.entries()) {
|
|
1128
|
-
this._SQLEngine.bulkDelete(tableName, ids);
|
|
1138
|
+
await this._SQLEngine.bulkDelete(tableName, ids);
|
|
1129
1139
|
}
|
|
1130
1140
|
} catch (error) {
|
|
1131
1141
|
console.error('[Context] Bulk delete failed, falling back to individual deletes:', error.message);
|
|
1132
1142
|
// Fallback to individual deletes
|
|
1133
1143
|
for (const entity of entities) {
|
|
1134
1144
|
const deleteObject = new deleteManager(this._SQLEngine, this.__entities);
|
|
1135
|
-
deleteObject.init(entity);
|
|
1145
|
+
await deleteObject.init(entity);
|
|
1136
1146
|
}
|
|
1137
1147
|
}
|
|
1138
1148
|
}
|
|
@@ -1152,7 +1162,7 @@ class context {
|
|
|
1152
1162
|
* user.name = 'Alice';
|
|
1153
1163
|
* db.saveChanges();
|
|
1154
1164
|
*/
|
|
1155
|
-
saveChanges() {
|
|
1165
|
+
async saveChanges() {
|
|
1156
1166
|
try {
|
|
1157
1167
|
const tracked = this.__trackedEntities;
|
|
1158
1168
|
|
|
@@ -1171,22 +1181,22 @@ class context {
|
|
|
1171
1181
|
|
|
1172
1182
|
// Handle transactions based on database type
|
|
1173
1183
|
if (this.isSQLite) {
|
|
1174
|
-
this._SQLEngine.startTransaction();
|
|
1184
|
+
await this._SQLEngine.startTransaction();
|
|
1175
1185
|
try {
|
|
1176
|
-
this._processTrackedEntities(tracked);
|
|
1186
|
+
await this._processTrackedEntities(tracked);
|
|
1177
1187
|
this.__clearErrorHandler();
|
|
1178
|
-
this._SQLEngine.endTransaction();
|
|
1188
|
+
await this._SQLEngine.endTransaction();
|
|
1179
1189
|
} catch (error) {
|
|
1180
|
-
this._SQLEngine.errorTransaction();
|
|
1190
|
+
await this._SQLEngine.errorTransaction();
|
|
1181
1191
|
throw error;
|
|
1182
1192
|
}
|
|
1183
1193
|
} else if (this.isMySQL) {
|
|
1184
|
-
// MySQL:
|
|
1185
|
-
this._processTrackedEntities(tracked);
|
|
1194
|
+
// MySQL: Async operations
|
|
1195
|
+
await this._processTrackedEntities(tracked);
|
|
1186
1196
|
this.__clearErrorHandler();
|
|
1187
1197
|
} else if (this.isPostgres) {
|
|
1188
|
-
// PostgreSQL: Async operations
|
|
1189
|
-
this._processTrackedEntities(tracked);
|
|
1198
|
+
// PostgreSQL: Async operations
|
|
1199
|
+
await this._processTrackedEntities(tracked);
|
|
1190
1200
|
this.__clearErrorHandler();
|
|
1191
1201
|
}
|
|
1192
1202
|
|