mysql-orm-lite 1.0.1 → 2.1.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 +394 -0
- package/dist/index.d.ts +183 -0
- package/dist/index.js +106 -0
- package/dist/lib/TransactionCRUD.d.ts +42 -0
- package/dist/lib/TransactionCRUD.js +202 -0
- package/dist/lib/connectionManager.d.ts +47 -0
- package/dist/lib/connectionManager.js +172 -0
- package/dist/lib/crud.d.ts +73 -0
- package/dist/lib/crud.js +435 -0
- package/dist/lib/performanceMonitor.d.ts +50 -0
- package/dist/lib/performanceMonitor.js +134 -0
- package/dist/lib/schema.d.ts +39 -0
- package/dist/lib/schema.js +181 -0
- package/dist/lib/transactionManager.d.ts +9 -0
- package/{lib → dist/lib}/transactionManager.js +7 -7
- package/dist/types.d.ts +124 -0
- package/dist/types.js +2 -0
- package/package.json +10 -4
- package/src/index.ts +76 -0
- package/{lib/TransactionCRUD.js → src/lib/TransactionCRUD.ts} +31 -26
- package/{lib/connectionManager.js → src/lib/connectionManager.ts} +59 -24
- package/{lib/crud.js → src/lib/crud.ts} +217 -45
- package/src/lib/performanceMonitor.ts +147 -0
- package/src/lib/schema.ts +207 -0
- package/src/lib/transactionManager.ts +25 -0
- package/src/types.ts +143 -0
- package/tsconfig.json +25 -0
- package/index.js +0 -42
package/dist/lib/crud.js
ADDED
|
@@ -0,0 +1,435 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.remove = exports.deleteWhere = exports.updateQuery = exports.updateWhere = exports.query = exports.findWhere = exports.select = exports.buildAndExecuteDeleteQuery = exports.buildAndExecuteUpdateQuery = exports.buildAndExecuteSelectQuery = exports.bulkUpsert = exports.upsert = exports.bulkInsert = exports.delete = exports._delete = exports.update = exports.insert = exports.findCount = exports.find = exports.utils = void 0;
|
|
7
|
+
const connectionManager_1 = __importDefault(require("./connectionManager"));
|
|
8
|
+
const performanceMonitor_1 = __importDefault(require("./performanceMonitor"));
|
|
9
|
+
exports.utils = {
|
|
10
|
+
handleConnectionError(err) {
|
|
11
|
+
const errorMessages = {
|
|
12
|
+
PROTOCOL_CONNECTION_LOST: 'Database connection was closed.',
|
|
13
|
+
ER_CON_COUNT_ERROR: 'Database has too many connections.',
|
|
14
|
+
ECONNREFUSED: 'Database connection was refused.',
|
|
15
|
+
POOL_CLOSED: 'Connection pool was closed'
|
|
16
|
+
};
|
|
17
|
+
return new Error(errorMessages[err.code] || err.message);
|
|
18
|
+
},
|
|
19
|
+
logQueryPerformance(query, startTime, params = []) {
|
|
20
|
+
const logger = connectionManager_1.default.getLogger();
|
|
21
|
+
const duration = Date.now() - startTime;
|
|
22
|
+
if (duration > 1000) {
|
|
23
|
+
logger.warn(' Slow Query ================================================================== ');
|
|
24
|
+
logger.warn(` Execution Time: ${duration}ms`);
|
|
25
|
+
logger.warn(` Query: ${query}`);
|
|
26
|
+
if (params.length)
|
|
27
|
+
logger.warn(` Parameters: ${JSON.stringify(params)}`);
|
|
28
|
+
logger.warn(' =========================================================================== ');
|
|
29
|
+
}
|
|
30
|
+
return duration;
|
|
31
|
+
},
|
|
32
|
+
async executeQuery({ query, params = [], dbConfig, operation = 'query' }) {
|
|
33
|
+
const startTime = Date.now();
|
|
34
|
+
const logger = connectionManager_1.default.getLogger();
|
|
35
|
+
try {
|
|
36
|
+
const pool = await connectionManager_1.default.getPool(dbConfig);
|
|
37
|
+
const [results] = await pool.query(query, params);
|
|
38
|
+
const duration = this.logQueryPerformance(query, startTime, params);
|
|
39
|
+
// Record in performance monitor if enabled
|
|
40
|
+
performanceMonitor_1.default.recordQuery(query, duration, params);
|
|
41
|
+
return results;
|
|
42
|
+
}
|
|
43
|
+
catch (error) {
|
|
44
|
+
logger.error(`${operation} Error: ${error.message}`);
|
|
45
|
+
logger.error(`Query: ${query}`);
|
|
46
|
+
if (params.length)
|
|
47
|
+
logger.error('Parameters:', JSON.stringify(params));
|
|
48
|
+
throw error;
|
|
49
|
+
}
|
|
50
|
+
},
|
|
51
|
+
validateUpdateParams(table, data, query) {
|
|
52
|
+
const errors = [];
|
|
53
|
+
if (!table || typeof table !== 'string')
|
|
54
|
+
errors.push('Invalid table name');
|
|
55
|
+
else if (!/^[a-zA-Z0-9_]+$/.test(table))
|
|
56
|
+
errors.push('Table name contains invalid characters');
|
|
57
|
+
if (!data || typeof data !== 'object' || Array.isArray(data))
|
|
58
|
+
errors.push('Data must be a non-null object');
|
|
59
|
+
else if (Object.keys(data).length === 0)
|
|
60
|
+
errors.push('Data object cannot be empty');
|
|
61
|
+
if (!query || typeof query !== 'string')
|
|
62
|
+
errors.push('Invalid WHERE clause');
|
|
63
|
+
else if (!query.toLowerCase().includes('where'))
|
|
64
|
+
errors.push('WHERE clause is required for security');
|
|
65
|
+
return errors;
|
|
66
|
+
},
|
|
67
|
+
prepareUpdateParams(data) {
|
|
68
|
+
const setFields = [];
|
|
69
|
+
const params = [];
|
|
70
|
+
for (const [key, value] of Object.entries(data)) {
|
|
71
|
+
if (value === undefined)
|
|
72
|
+
continue;
|
|
73
|
+
if (value && typeof value === 'object' && value.__raw) {
|
|
74
|
+
setFields.push(`${key} = ${value.value}`);
|
|
75
|
+
continue;
|
|
76
|
+
}
|
|
77
|
+
if (value === null) {
|
|
78
|
+
setFields.push(`${key} = NULL`);
|
|
79
|
+
}
|
|
80
|
+
else {
|
|
81
|
+
setFields.push(`${key} = ?`);
|
|
82
|
+
params.push(value);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
return { setFields, params };
|
|
86
|
+
},
|
|
87
|
+
_buildWhereClause(conditions) {
|
|
88
|
+
const params = [];
|
|
89
|
+
const buildCondition = (key, value) => {
|
|
90
|
+
const column = key;
|
|
91
|
+
if (value === null)
|
|
92
|
+
return `${column} IS NULL`;
|
|
93
|
+
if (typeof value === 'object' && !Array.isArray(value)) {
|
|
94
|
+
return Object.entries(value).map(([op, val]) => {
|
|
95
|
+
switch (op) {
|
|
96
|
+
case '$eq':
|
|
97
|
+
params.push(val);
|
|
98
|
+
return `${column} = ?`;
|
|
99
|
+
case '$ne':
|
|
100
|
+
params.push(val);
|
|
101
|
+
return `${column} != ?`;
|
|
102
|
+
case '$gt':
|
|
103
|
+
params.push(val);
|
|
104
|
+
return `${column} > ?`;
|
|
105
|
+
case '$gte':
|
|
106
|
+
params.push(val);
|
|
107
|
+
return `${column} >= ?`;
|
|
108
|
+
case '$lt':
|
|
109
|
+
params.push(val);
|
|
110
|
+
return `${column} < ?`;
|
|
111
|
+
case '$lte':
|
|
112
|
+
params.push(val);
|
|
113
|
+
return `${column} <= ?`;
|
|
114
|
+
case '$in':
|
|
115
|
+
if (!val.length)
|
|
116
|
+
return 'FALSE';
|
|
117
|
+
params.push(...val);
|
|
118
|
+
return `${column} IN (${val.map(() => '?').join(', ')})`;
|
|
119
|
+
case '$notIn':
|
|
120
|
+
case '$nIn':
|
|
121
|
+
case '$nin':
|
|
122
|
+
if (!val.length)
|
|
123
|
+
return 'TRUE';
|
|
124
|
+
params.push(...val);
|
|
125
|
+
return `${column} NOT IN (${val.map(() => '?').join(', ')})`;
|
|
126
|
+
case '$like':
|
|
127
|
+
params.push(val);
|
|
128
|
+
return `${column} LIKE ?`;
|
|
129
|
+
case '$between':
|
|
130
|
+
if (val.length !== 2)
|
|
131
|
+
throw new Error('$between requires [min, max]');
|
|
132
|
+
params.push(val[0], val[1]);
|
|
133
|
+
return `${column} BETWEEN ? AND ?`;
|
|
134
|
+
default: throw new Error(`Unsupported operator: ${op}`);
|
|
135
|
+
}
|
|
136
|
+
}).join(' AND ');
|
|
137
|
+
}
|
|
138
|
+
params.push(value);
|
|
139
|
+
return `${column} = ?`;
|
|
140
|
+
};
|
|
141
|
+
const walk = (cond) => {
|
|
142
|
+
if (!cond || typeof cond !== 'object')
|
|
143
|
+
return '';
|
|
144
|
+
if (Array.isArray(cond))
|
|
145
|
+
return cond.map(walk).join(' AND ');
|
|
146
|
+
if ('$and' in cond)
|
|
147
|
+
return `(${cond.$and.map(walk).join(' AND ')})`;
|
|
148
|
+
if ('$or' in cond)
|
|
149
|
+
return `(${cond.$or.map(walk).join(' OR ')})`;
|
|
150
|
+
if ('$not' in cond)
|
|
151
|
+
return `(NOT ${walk(cond.$not)})`;
|
|
152
|
+
return Object.entries(cond).map(([k, v]) => buildCondition(k, v)).join(' AND ');
|
|
153
|
+
};
|
|
154
|
+
const clause = walk(conditions);
|
|
155
|
+
return { clause, params };
|
|
156
|
+
},
|
|
157
|
+
_buildSelectQuery(options) {
|
|
158
|
+
let { table, fields = '*', joins = [], where, orderBy, limit, offset, groupBy, having, alias, forUpdate = false } = options;
|
|
159
|
+
if (fields && Array.isArray(fields) && fields.length > 0) {
|
|
160
|
+
fields = fields.filter(field => field && field.trim() !== '').join(', ');
|
|
161
|
+
}
|
|
162
|
+
else if (fields && Array.isArray(fields)) {
|
|
163
|
+
fields = '*';
|
|
164
|
+
}
|
|
165
|
+
let query = `SELECT ${fields} FROM ${table}${alias ? ' ' + alias : ''}`;
|
|
166
|
+
const allParams = [];
|
|
167
|
+
for (const join of joins) {
|
|
168
|
+
let { type = 'INNER', table: joinTable, alias, on } = join;
|
|
169
|
+
const onClause = Array.isArray(on) ? on.join(' AND ') : on;
|
|
170
|
+
query += ` ${type.toUpperCase()} JOIN ${joinTable}${alias ? ' ' + alias : ''} ON ${onClause}`;
|
|
171
|
+
}
|
|
172
|
+
if (where) {
|
|
173
|
+
const { clause, params } = this._buildWhereClause(where);
|
|
174
|
+
if (clause) {
|
|
175
|
+
query += ` WHERE ${clause}`;
|
|
176
|
+
allParams.push(...params);
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
if (groupBy)
|
|
180
|
+
query += ` GROUP BY ${groupBy}`;
|
|
181
|
+
if (having)
|
|
182
|
+
query += ` HAVING ${having}`;
|
|
183
|
+
if (orderBy)
|
|
184
|
+
query += ` ORDER BY ${orderBy}`;
|
|
185
|
+
if (typeof limit === 'number' && limit && limit > 0) {
|
|
186
|
+
query += ` LIMIT ${limit}`;
|
|
187
|
+
if (typeof offset === 'number')
|
|
188
|
+
query += ` OFFSET ${offset}`;
|
|
189
|
+
}
|
|
190
|
+
if (forUpdate) {
|
|
191
|
+
query += ' FOR UPDATE';
|
|
192
|
+
}
|
|
193
|
+
return { query, params: allParams };
|
|
194
|
+
},
|
|
195
|
+
_buildUpdateQuery(options) {
|
|
196
|
+
const { table, data, where } = options;
|
|
197
|
+
const { setFields, params } = this.prepareUpdateParams(data);
|
|
198
|
+
if (setFields.length === 0)
|
|
199
|
+
throw new Error('No valid fields to update');
|
|
200
|
+
let query = `UPDATE ${table} SET ${setFields.join(', ')}`;
|
|
201
|
+
const allParams = [...params];
|
|
202
|
+
if (where) {
|
|
203
|
+
const { clause, params: whereParams } = this._buildWhereClause(where);
|
|
204
|
+
if (clause) {
|
|
205
|
+
query += ` WHERE ${clause}`;
|
|
206
|
+
allParams.push(...whereParams);
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
return { query, params: allParams };
|
|
210
|
+
},
|
|
211
|
+
_buildDeleteQuery(options) {
|
|
212
|
+
const { table, where } = options;
|
|
213
|
+
if (!table)
|
|
214
|
+
throw new Error('Table name is required for delete');
|
|
215
|
+
let query = `DELETE FROM ${table}`;
|
|
216
|
+
const allParams = [];
|
|
217
|
+
if (where) {
|
|
218
|
+
const { clause, params: whereParams } = this._buildWhereClause(where);
|
|
219
|
+
if (clause) {
|
|
220
|
+
query += ` WHERE ${clause}`;
|
|
221
|
+
allParams.push(...whereParams);
|
|
222
|
+
}
|
|
223
|
+
else {
|
|
224
|
+
throw new Error('DELETE requires a valid WHERE clause for safety');
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
else {
|
|
228
|
+
throw new Error('DELETE requires a WHERE clause for safety');
|
|
229
|
+
}
|
|
230
|
+
return { query, params: allParams };
|
|
231
|
+
}
|
|
232
|
+
};
|
|
233
|
+
// Public API
|
|
234
|
+
const find = async function (query, params = [], dbConfig) {
|
|
235
|
+
if (!query)
|
|
236
|
+
return [];
|
|
237
|
+
return await exports.utils.executeQuery({ query, params, dbConfig, operation: 'find' });
|
|
238
|
+
};
|
|
239
|
+
exports.find = find;
|
|
240
|
+
const findCount = async function (query, params = [], dbConfig) {
|
|
241
|
+
if (!query)
|
|
242
|
+
return 0;
|
|
243
|
+
const results = await exports.utils.executeQuery({ query, params, dbConfig, operation: 'findCount' });
|
|
244
|
+
return results && results[0] ? results[0].count : 0;
|
|
245
|
+
};
|
|
246
|
+
exports.findCount = findCount;
|
|
247
|
+
const insert = async function (table, data, dbConfig, debug = false, isIgnore = false) {
|
|
248
|
+
if (!table || !data || typeof data !== 'object')
|
|
249
|
+
throw new Error('Invalid table or data');
|
|
250
|
+
const logger = connectionManager_1.default.getLogger();
|
|
251
|
+
const fields = Object.keys(data);
|
|
252
|
+
const values = Object.values(data);
|
|
253
|
+
const placeholders = fields.map(() => '?').join(', ');
|
|
254
|
+
const sql = isIgnore
|
|
255
|
+
? `INSERT IGNORE INTO ${table} (${fields.join(', ')}) VALUES (${placeholders})`
|
|
256
|
+
: `INSERT INTO ${table} (${fields.join(', ')}) VALUES (${placeholders})`;
|
|
257
|
+
const result = await exports.utils.executeQuery({ query: sql, params: values, dbConfig, operation: 'insert' });
|
|
258
|
+
if (debug) {
|
|
259
|
+
logger.info({ operation: 'INSERT', table, insertId: result.insertId });
|
|
260
|
+
}
|
|
261
|
+
return result.insertId;
|
|
262
|
+
};
|
|
263
|
+
exports.insert = insert;
|
|
264
|
+
const update = async function (table, data, query, dbConfig, debug = false) {
|
|
265
|
+
const errors = exports.utils.validateUpdateParams(table, data, query);
|
|
266
|
+
if (errors.length > 0)
|
|
267
|
+
throw new Error(`Validation failed: ${errors.join(', ')}`);
|
|
268
|
+
const logger = connectionManager_1.default.getLogger();
|
|
269
|
+
const { setFields, params } = exports.utils.prepareUpdateParams(data);
|
|
270
|
+
if (setFields.length === 0)
|
|
271
|
+
throw new Error('No valid fields to update');
|
|
272
|
+
const sql = `UPDATE ${table} SET ${setFields.join(', ')} ${query}`;
|
|
273
|
+
const result = await exports.utils.executeQuery({ query: sql, params, dbConfig, operation: 'update' });
|
|
274
|
+
if (debug) {
|
|
275
|
+
logger.info({ operation: 'UPDATE', table, affectedRows: result.affectedRows });
|
|
276
|
+
}
|
|
277
|
+
return result.affectedRows;
|
|
278
|
+
};
|
|
279
|
+
exports.update = update;
|
|
280
|
+
const _delete = async function (query, table, dbConfig) {
|
|
281
|
+
if (!query || !query.toLowerCase().includes('where'))
|
|
282
|
+
throw new Error('Invalid query or missing WHERE clause');
|
|
283
|
+
const logger = connectionManager_1.default.getLogger();
|
|
284
|
+
const sql = table ? `DELETE FROM ${table} ${query}` : query;
|
|
285
|
+
const result = await exports.utils.executeQuery({ query: sql, params: [], dbConfig, operation: 'delete' });
|
|
286
|
+
logger.info(`Deleted ${result.affectedRows} records from ${table || 'custom query'}`);
|
|
287
|
+
return result.affectedRows;
|
|
288
|
+
};
|
|
289
|
+
exports._delete = _delete;
|
|
290
|
+
exports.delete = exports._delete;
|
|
291
|
+
// Bulk Operations
|
|
292
|
+
const bulkInsert = async function (table, records, options, dbConfig) {
|
|
293
|
+
if (!table || !records || !Array.isArray(records) || records.length === 0) {
|
|
294
|
+
throw new Error('Table name and records are required for bulk insert');
|
|
295
|
+
}
|
|
296
|
+
const batchSize = options?.batchSize || 1000;
|
|
297
|
+
const isIgnore = options?.ignore || false;
|
|
298
|
+
const logger = connectionManager_1.default.getLogger();
|
|
299
|
+
// Validate all records have the same fields
|
|
300
|
+
const fields = Object.keys(records[0]);
|
|
301
|
+
for (const record of records) {
|
|
302
|
+
const recordFields = Object.keys(record);
|
|
303
|
+
if (recordFields.length !== fields.length || !recordFields.every(f => fields.includes(f))) {
|
|
304
|
+
throw new Error('All records must have the same fields for bulk insert');
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
let totalInserted = 0;
|
|
308
|
+
let firstInsertId;
|
|
309
|
+
let lastInsertId;
|
|
310
|
+
const batches = Math.ceil(records.length / batchSize);
|
|
311
|
+
for (let i = 0; i < records.length; i += batchSize) {
|
|
312
|
+
const batch = records.slice(i, i + batchSize);
|
|
313
|
+
const values = [];
|
|
314
|
+
const placeholders = [];
|
|
315
|
+
for (const record of batch) {
|
|
316
|
+
const recordValues = fields.map(field => record[field]);
|
|
317
|
+
values.push(...recordValues);
|
|
318
|
+
placeholders.push(`(${fields.map(() => '?').join(', ')})`);
|
|
319
|
+
}
|
|
320
|
+
const sql = isIgnore
|
|
321
|
+
? `INSERT IGNORE INTO ${table} (${fields.join(', ')}) VALUES ${placeholders.join(', ')}`
|
|
322
|
+
: `INSERT INTO ${table} (${fields.join(', ')}) VALUES ${placeholders.join(', ')}`;
|
|
323
|
+
const result = await exports.utils.executeQuery({ query: sql, params: values, dbConfig, operation: 'bulkInsert' });
|
|
324
|
+
totalInserted += result.affectedRows || 0;
|
|
325
|
+
if (i === 0 && result.insertId) {
|
|
326
|
+
firstInsertId = result.insertId;
|
|
327
|
+
}
|
|
328
|
+
if (result.insertId) {
|
|
329
|
+
lastInsertId = result.insertId + (result.affectedRows || 1) - 1;
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
logger.info(`Bulk inserted ${totalInserted} records into ${table} in ${batches} batches`);
|
|
333
|
+
return {
|
|
334
|
+
totalInserted,
|
|
335
|
+
batches,
|
|
336
|
+
firstInsertId,
|
|
337
|
+
lastInsertId
|
|
338
|
+
};
|
|
339
|
+
};
|
|
340
|
+
exports.bulkInsert = bulkInsert;
|
|
341
|
+
const upsert = async function (table, data, options, dbConfig) {
|
|
342
|
+
if (!table || !data || !options.conflictKey) {
|
|
343
|
+
throw new Error('Table, data, and conflictKey are required for upsert');
|
|
344
|
+
}
|
|
345
|
+
const fields = Object.keys(data);
|
|
346
|
+
const values = Object.values(data);
|
|
347
|
+
const placeholders = fields.map(() => '?').join(', ');
|
|
348
|
+
// Determine which fields to update on duplicate key
|
|
349
|
+
const conflictKeys = Array.isArray(options.conflictKey) ? options.conflictKey : [options.conflictKey];
|
|
350
|
+
const updateFields = options.updateFields || fields.filter(f => !conflictKeys.includes(f));
|
|
351
|
+
if (updateFields.length === 0) {
|
|
352
|
+
throw new Error('No fields to update on duplicate key');
|
|
353
|
+
}
|
|
354
|
+
// Build UPDATE clause
|
|
355
|
+
const updateClause = updateFields.map(field => `${field} = VALUES(${field})`).join(', ');
|
|
356
|
+
const sql = `INSERT INTO ${table} (${fields.join(', ')}) VALUES (${placeholders}) ON DUPLICATE KEY UPDATE ${updateClause}`;
|
|
357
|
+
const result = await exports.utils.executeQuery({ query: sql, params: values, dbConfig, operation: 'upsert' });
|
|
358
|
+
// affectedRows = 1 means inserted, 2 means updated, 0 means no change
|
|
359
|
+
const action = result.affectedRows === 1 ? 'inserted' : 'updated';
|
|
360
|
+
return {
|
|
361
|
+
action,
|
|
362
|
+
affectedRows: result.affectedRows,
|
|
363
|
+
insertId: result.insertId
|
|
364
|
+
};
|
|
365
|
+
};
|
|
366
|
+
exports.upsert = upsert;
|
|
367
|
+
const bulkUpsert = async function (table, records, options, dbConfig) {
|
|
368
|
+
if (!table || !records || records.length === 0) {
|
|
369
|
+
throw new Error('Table name and records are required for bulk upsert');
|
|
370
|
+
}
|
|
371
|
+
const batchSize = options?.batchSize || 1000;
|
|
372
|
+
const logger = connectionManager_1.default.getLogger();
|
|
373
|
+
// Validate all records have the same fields
|
|
374
|
+
const fields = Object.keys(records[0]);
|
|
375
|
+
for (const record of records) {
|
|
376
|
+
const recordFields = Object.keys(record);
|
|
377
|
+
if (recordFields.length !== fields.length || !recordFields.every(f => fields.includes(f))) {
|
|
378
|
+
throw new Error('All records must have the same fields for bulk upsert');
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
// Determine which fields to update on duplicate key
|
|
382
|
+
const conflictKeys = Array.isArray(options.conflictKey) ? options.conflictKey : [options.conflictKey];
|
|
383
|
+
const updateFields = options.updateFields || fields.filter(f => !conflictKeys.includes(f));
|
|
384
|
+
if (updateFields.length === 0) {
|
|
385
|
+
throw new Error('No fields to update on duplicate key');
|
|
386
|
+
}
|
|
387
|
+
const updateClause = updateFields.map(field => `${field} = VALUES(${field})`).join(', ');
|
|
388
|
+
let totalAffected = 0;
|
|
389
|
+
const batches = Math.ceil(records.length / batchSize);
|
|
390
|
+
for (let i = 0; i < records.length; i += batchSize) {
|
|
391
|
+
const batch = records.slice(i, i + batchSize);
|
|
392
|
+
const values = [];
|
|
393
|
+
const placeholders = [];
|
|
394
|
+
for (const record of batch) {
|
|
395
|
+
const recordValues = fields.map(field => record[field]);
|
|
396
|
+
values.push(...recordValues);
|
|
397
|
+
placeholders.push(`(${fields.map(() => '?').join(', ')})`);
|
|
398
|
+
}
|
|
399
|
+
const sql = `INSERT INTO ${table} (${fields.join(', ')}) VALUES ${placeholders.join(', ')} ON DUPLICATE KEY UPDATE ${updateClause}`;
|
|
400
|
+
const result = await exports.utils.executeQuery({ query: sql, params: values, dbConfig, operation: 'bulkUpsert' });
|
|
401
|
+
totalAffected += result.affectedRows || 0;
|
|
402
|
+
}
|
|
403
|
+
logger.info(`Bulk upsert affected ${totalAffected} records in ${table} in ${batches} batches`);
|
|
404
|
+
return {
|
|
405
|
+
totalAffected,
|
|
406
|
+
batches
|
|
407
|
+
};
|
|
408
|
+
};
|
|
409
|
+
exports.bulkUpsert = bulkUpsert;
|
|
410
|
+
// Query Builder Methods
|
|
411
|
+
const buildAndExecuteSelectQuery = async function (options, dbConfig) {
|
|
412
|
+
const { query, params } = exports.utils._buildSelectQuery(options);
|
|
413
|
+
return await exports.utils.executeQuery({ query, params, dbConfig, operation: 'buildAndExecuteSelectQuery' });
|
|
414
|
+
};
|
|
415
|
+
exports.buildAndExecuteSelectQuery = buildAndExecuteSelectQuery;
|
|
416
|
+
const buildAndExecuteUpdateQuery = async function (options, dbConfig) {
|
|
417
|
+
const { query, params } = exports.utils._buildUpdateQuery(options);
|
|
418
|
+
const result = await exports.utils.executeQuery({ query, params, dbConfig, operation: 'buildAndExecuteUpdateQuery' });
|
|
419
|
+
return result.affectedRows;
|
|
420
|
+
};
|
|
421
|
+
exports.buildAndExecuteUpdateQuery = buildAndExecuteUpdateQuery;
|
|
422
|
+
const buildAndExecuteDeleteQuery = async function (options, dbConfig) {
|
|
423
|
+
const { query, params } = exports.utils._buildDeleteQuery(options);
|
|
424
|
+
const result = await exports.utils.executeQuery({ query, params, dbConfig, operation: 'buildAndExecuteDeleteQuery' });
|
|
425
|
+
return result.affectedRows;
|
|
426
|
+
};
|
|
427
|
+
exports.buildAndExecuteDeleteQuery = buildAndExecuteDeleteQuery;
|
|
428
|
+
// Aliases
|
|
429
|
+
exports.select = exports.buildAndExecuteSelectQuery;
|
|
430
|
+
exports.findWhere = exports.buildAndExecuteSelectQuery;
|
|
431
|
+
exports.query = exports.buildAndExecuteSelectQuery;
|
|
432
|
+
exports.updateWhere = exports.buildAndExecuteUpdateQuery;
|
|
433
|
+
exports.updateQuery = exports.buildAndExecuteUpdateQuery;
|
|
434
|
+
exports.deleteWhere = exports.buildAndExecuteDeleteQuery;
|
|
435
|
+
exports.remove = exports.buildAndExecuteDeleteQuery;
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { QueryMetric, PerformanceStats } from '../types';
|
|
2
|
+
declare class PerformanceMonitor {
|
|
3
|
+
private enabled;
|
|
4
|
+
private queries;
|
|
5
|
+
private startTime;
|
|
6
|
+
private queryCountByType;
|
|
7
|
+
/**
|
|
8
|
+
* Enable performance monitoring
|
|
9
|
+
*/
|
|
10
|
+
enable(): void;
|
|
11
|
+
/**
|
|
12
|
+
* Disable performance monitoring
|
|
13
|
+
*/
|
|
14
|
+
disable(): void;
|
|
15
|
+
/**
|
|
16
|
+
* Check if monitoring is enabled
|
|
17
|
+
*/
|
|
18
|
+
isEnabled(): boolean;
|
|
19
|
+
/**
|
|
20
|
+
* Record a query execution
|
|
21
|
+
*/
|
|
22
|
+
recordQuery(query: string, duration: number, params?: any[]): void;
|
|
23
|
+
/**
|
|
24
|
+
* Detect query type from SQL string
|
|
25
|
+
*/
|
|
26
|
+
private detectQueryType;
|
|
27
|
+
/**
|
|
28
|
+
* Get current performance statistics
|
|
29
|
+
*/
|
|
30
|
+
getStats(): PerformanceStats;
|
|
31
|
+
/**
|
|
32
|
+
* Get slowest queries
|
|
33
|
+
*/
|
|
34
|
+
getSlowQueries(limit?: number): QueryMetric[];
|
|
35
|
+
/**
|
|
36
|
+
* Reset all collected metrics
|
|
37
|
+
*/
|
|
38
|
+
reset(): void;
|
|
39
|
+
/**
|
|
40
|
+
* Get queries executed in the last N milliseconds
|
|
41
|
+
*/
|
|
42
|
+
getRecentQueries(milliseconds?: number): QueryMetric[];
|
|
43
|
+
/**
|
|
44
|
+
* Get queries per second
|
|
45
|
+
*/
|
|
46
|
+
getQueriesPerSecond(): number;
|
|
47
|
+
}
|
|
48
|
+
declare const performanceMonitor: PerformanceMonitor;
|
|
49
|
+
export default performanceMonitor;
|
|
50
|
+
export { performanceMonitor, PerformanceMonitor };
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.PerformanceMonitor = exports.performanceMonitor = void 0;
|
|
4
|
+
class PerformanceMonitor {
|
|
5
|
+
enabled = false;
|
|
6
|
+
queries = [];
|
|
7
|
+
startTime = 0;
|
|
8
|
+
queryCountByType = {
|
|
9
|
+
SELECT: 0,
|
|
10
|
+
INSERT: 0,
|
|
11
|
+
UPDATE: 0,
|
|
12
|
+
DELETE: 0,
|
|
13
|
+
OTHER: 0
|
|
14
|
+
};
|
|
15
|
+
/**
|
|
16
|
+
* Enable performance monitoring
|
|
17
|
+
*/
|
|
18
|
+
enable() {
|
|
19
|
+
this.enabled = true;
|
|
20
|
+
this.startTime = Date.now();
|
|
21
|
+
console.log('[Performance Monitor] Enabled');
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Disable performance monitoring
|
|
25
|
+
*/
|
|
26
|
+
disable() {
|
|
27
|
+
this.enabled = false;
|
|
28
|
+
console.log('[Performance Monitor] Disabled');
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Check if monitoring is enabled
|
|
32
|
+
*/
|
|
33
|
+
isEnabled() {
|
|
34
|
+
return this.enabled;
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Record a query execution
|
|
38
|
+
*/
|
|
39
|
+
recordQuery(query, duration, params) {
|
|
40
|
+
if (!this.enabled)
|
|
41
|
+
return;
|
|
42
|
+
const type = this.detectQueryType(query);
|
|
43
|
+
const metric = {
|
|
44
|
+
query,
|
|
45
|
+
duration,
|
|
46
|
+
timestamp: Date.now(),
|
|
47
|
+
type,
|
|
48
|
+
params
|
|
49
|
+
};
|
|
50
|
+
this.queries.push(metric);
|
|
51
|
+
this.queryCountByType[type]++;
|
|
52
|
+
// Keep only last 1000 queries to prevent memory issues
|
|
53
|
+
if (this.queries.length > 1000) {
|
|
54
|
+
this.queries.shift();
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Detect query type from SQL string
|
|
59
|
+
*/
|
|
60
|
+
detectQueryType(query) {
|
|
61
|
+
const normalized = query.trim().toUpperCase();
|
|
62
|
+
if (normalized.startsWith('SELECT'))
|
|
63
|
+
return 'SELECT';
|
|
64
|
+
if (normalized.startsWith('INSERT'))
|
|
65
|
+
return 'INSERT';
|
|
66
|
+
if (normalized.startsWith('UPDATE'))
|
|
67
|
+
return 'UPDATE';
|
|
68
|
+
if (normalized.startsWith('DELETE'))
|
|
69
|
+
return 'DELETE';
|
|
70
|
+
return 'OTHER';
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Get current performance statistics
|
|
74
|
+
*/
|
|
75
|
+
getStats() {
|
|
76
|
+
const totalQueries = this.queries.length;
|
|
77
|
+
const totalDuration = this.queries.reduce((sum, q) => sum + q.duration, 0);
|
|
78
|
+
const averageQueryTime = totalQueries > 0 ? totalDuration / totalQueries : 0;
|
|
79
|
+
const slowestQueries = [...this.queries]
|
|
80
|
+
.sort((a, b) => b.duration - a.duration)
|
|
81
|
+
.slice(0, 10);
|
|
82
|
+
return {
|
|
83
|
+
enabled: this.enabled,
|
|
84
|
+
totalQueries,
|
|
85
|
+
averageQueryTime: Math.round(averageQueryTime * 100) / 100,
|
|
86
|
+
slowestQueries,
|
|
87
|
+
queryCountByType: { ...this.queryCountByType },
|
|
88
|
+
startTime: this.startTime,
|
|
89
|
+
uptime: this.startTime > 0 ? Date.now() - this.startTime : 0
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* Get slowest queries
|
|
94
|
+
*/
|
|
95
|
+
getSlowQueries(limit = 10) {
|
|
96
|
+
return [...this.queries]
|
|
97
|
+
.sort((a, b) => b.duration - a.duration)
|
|
98
|
+
.slice(0, limit);
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* Reset all collected metrics
|
|
102
|
+
*/
|
|
103
|
+
reset() {
|
|
104
|
+
this.queries = [];
|
|
105
|
+
this.queryCountByType = {
|
|
106
|
+
SELECT: 0,
|
|
107
|
+
INSERT: 0,
|
|
108
|
+
UPDATE: 0,
|
|
109
|
+
DELETE: 0,
|
|
110
|
+
OTHER: 0
|
|
111
|
+
};
|
|
112
|
+
this.startTime = Date.now();
|
|
113
|
+
console.log('[Performance Monitor] Reset');
|
|
114
|
+
}
|
|
115
|
+
/**
|
|
116
|
+
* Get queries executed in the last N milliseconds
|
|
117
|
+
*/
|
|
118
|
+
getRecentQueries(milliseconds = 60000) {
|
|
119
|
+
const cutoff = Date.now() - milliseconds;
|
|
120
|
+
return this.queries.filter(q => q.timestamp >= cutoff);
|
|
121
|
+
}
|
|
122
|
+
/**
|
|
123
|
+
* Get queries per second
|
|
124
|
+
*/
|
|
125
|
+
getQueriesPerSecond() {
|
|
126
|
+
const uptime = this.startTime > 0 ? (Date.now() - this.startTime) / 1000 : 0;
|
|
127
|
+
return uptime > 0 ? this.queries.length / uptime : 0;
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
exports.PerformanceMonitor = PerformanceMonitor;
|
|
131
|
+
// Singleton instance
|
|
132
|
+
const performanceMonitor = new PerformanceMonitor();
|
|
133
|
+
exports.performanceMonitor = performanceMonitor;
|
|
134
|
+
exports.default = performanceMonitor;
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { DbConfig, ColumnInfo, IndexInfo, ForeignKeyInfo } from '../types';
|
|
2
|
+
declare const schema: {
|
|
3
|
+
/**
|
|
4
|
+
* Get list of all tables in the database
|
|
5
|
+
* @param dbConfig - Optional database configuration
|
|
6
|
+
* @returns Array of table names
|
|
7
|
+
*/
|
|
8
|
+
tables(dbConfig?: DbConfig): Promise<string[]>;
|
|
9
|
+
/**
|
|
10
|
+
* Get column information for a specific table
|
|
11
|
+
* @param tableName - Name of the table
|
|
12
|
+
* @param dbConfig - Optional database configuration
|
|
13
|
+
* @returns Array of column information
|
|
14
|
+
*/
|
|
15
|
+
columns(tableName: string, dbConfig?: DbConfig): Promise<ColumnInfo[]>;
|
|
16
|
+
/**
|
|
17
|
+
* Get index information for a specific table
|
|
18
|
+
* @param tableName - Name of the table
|
|
19
|
+
* @param dbConfig - Optional database configuration
|
|
20
|
+
* @returns Array of index information
|
|
21
|
+
*/
|
|
22
|
+
indexes(tableName: string, dbConfig?: DbConfig): Promise<IndexInfo[]>;
|
|
23
|
+
/**
|
|
24
|
+
* Get foreign key relationships for a specific table or all tables
|
|
25
|
+
* @param tableName - Name of the table (optional, if not provided returns all foreign keys)
|
|
26
|
+
* @param dbConfig - Optional database configuration
|
|
27
|
+
* @returns Array of foreign key information
|
|
28
|
+
*/
|
|
29
|
+
foreignKeys(tableName?: string, dbConfig?: DbConfig): Promise<ForeignKeyInfo[]>;
|
|
30
|
+
/**
|
|
31
|
+
* Get the correct order to delete/truncate tables based on foreign key dependencies
|
|
32
|
+
* Uses topological sort to ensure child tables are deleted before parent tables
|
|
33
|
+
* @param dbConfig - Optional database configuration
|
|
34
|
+
* @returns Array of table names in deletion order (children first)
|
|
35
|
+
*/
|
|
36
|
+
deleteOrder(dbConfig?: DbConfig): Promise<string[]>;
|
|
37
|
+
};
|
|
38
|
+
export default schema;
|
|
39
|
+
export { schema };
|