@aetherframework/database 1.1.1 → 1.1.2
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/examples/mysql-test-pressure.js +1530 -0
- package/examples/test-direct.js +116 -0
- package/examples/transaction_example.js +127 -0
- package/package.json +3 -1
- package/src/DatabaseManager.js +565 -0
- package/src/core/ConnectionManager.js +351 -0
- package/src/core/DatabaseFactory.js +188 -0
- package/src/core/MongoQueryBuilder.js +576 -0
- package/src/core/PluginManager.js +968 -0
- package/src/core/QueryBuilder.js +4394 -0
- package/src/core/TransactionManager.js +40 -0
- package/src/drivers/clickhouse-driver.js +272 -0
- package/src/drivers/index.js +273 -0
- package/src/drivers/mongodb-driver.js +87 -0
- package/src/drivers/mssql-driver.js +117 -0
- package/src/drivers/mysql-driver.js +169 -0
- package/src/drivers/oracle-driver.js +101 -0
- package/src/drivers/postgres-driver.js +234 -0
- package/src/drivers/redis-driver.js +52 -0
- package/src/drivers/sqlite-driver.js +67 -0
- package/src/middleware/connection-pool.js +455 -0
- package/src/middleware/performance-monitor.js +652 -0
- package/src/middleware/query-cache.js +500 -0
- package/src/middleware/query-logger.js +262 -0
- package/src/plugins/AuditPlugin.js +447 -0
- package/src/plugins/BasePlugin.js +418 -0
- package/src/plugins/BatchOperationPlugin.js +165 -0
- package/src/plugins/CachePlugin.js +407 -0
- package/src/plugins/CtePlugin.js +523 -0
- package/src/plugins/DistributedPlugin.js +543 -0
- package/src/plugins/EncryptionPlugin.js +211 -0
- package/src/plugins/FullTextSearchPlugin.js +164 -0
- package/src/plugins/GeospatialPlugin.js +219 -0
- package/src/plugins/GraphQLPlugin.js +162 -0
- package/src/plugins/HookPlugin.js +211 -0
- package/src/plugins/JsonPlugin.js +366 -0
- package/src/plugins/OptimisticLockPlugin.js +374 -0
- package/src/plugins/PerformancePlugin.js +175 -0
- package/src/plugins/ResiliencePlugin.js +114 -0
- package/src/plugins/ShardingPlugin.js +227 -0
- package/src/plugins/SoftDeletePlugin.js +258 -0
- package/src/plugins/SyncPlugin.js +373 -0
- package/src/plugins/VersioningPlugin.js +314 -0
- package/src/plugins/WindowFunctionPlugin.js +343 -0
- package/src/utils/config-loader.js +632 -0
- package/src/utils/error-handler.js +724 -0
- package/src/utils/migration-runner.js +1066 -0
|
@@ -0,0 +1,418 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license MIT
|
|
3
|
+
* Copyright (c) 2026-present AetherFramework Contributors.
|
|
4
|
+
* SPDX-License-Identifier: MIT
|
|
5
|
+
* @module @aetherframework/database/plugin/BasePlugin
|
|
6
|
+
*/
|
|
7
|
+
/**
|
|
8
|
+
* Base Plugin - Provides common functionality for all plugins
|
|
9
|
+
* All plugins should extend this class
|
|
10
|
+
*/
|
|
11
|
+
export class BasePlugin {
|
|
12
|
+
/**
|
|
13
|
+
* Constructor for BasePlugin
|
|
14
|
+
* @param {Object} queryBuilder - QueryBuilder instance
|
|
15
|
+
*/
|
|
16
|
+
constructor(queryBuilder) {
|
|
17
|
+
this.queryBuilder = queryBuilder;
|
|
18
|
+
this.pluginName = this.constructor.name;
|
|
19
|
+
this.initialized = false;
|
|
20
|
+
this.methods = {}; // Store methods to register
|
|
21
|
+
this.hooks = {}; // Store hook callbacks
|
|
22
|
+
this.middlewares = {}; // Store middleware functions
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Initialize the plugin
|
|
27
|
+
* This method should be called after plugin instantiation
|
|
28
|
+
* @returns {Promise<void>}
|
|
29
|
+
*/
|
|
30
|
+
async init() {
|
|
31
|
+
if (this.initialized) return;
|
|
32
|
+
|
|
33
|
+
// Validate queryBuilder
|
|
34
|
+
if (!this.queryBuilder) {
|
|
35
|
+
throw new Error(`${this.pluginName}: queryBuilder is required but was not provided.`);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
this.initialized = true;
|
|
39
|
+
this._registerMethods();
|
|
40
|
+
this._bindMethods();
|
|
41
|
+
this._registerHooks();
|
|
42
|
+
this._registerMiddlewares();
|
|
43
|
+
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Register plugin methods to QueryBuilder
|
|
48
|
+
* This method must be implemented by child classes
|
|
49
|
+
* @protected
|
|
50
|
+
*/
|
|
51
|
+
_registerMethods() {
|
|
52
|
+
throw new Error('_registerMethods() must be implemented by plugin');
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Bind plugin methods to QueryBuilder instance
|
|
57
|
+
* @protected
|
|
58
|
+
*/
|
|
59
|
+
_bindMethods() {
|
|
60
|
+
// Bind methods to QueryBuilder
|
|
61
|
+
Object.entries(this.methods).forEach(([methodName, method]) => {
|
|
62
|
+
if (typeof method === 'function') {
|
|
63
|
+
// Check if method already exists
|
|
64
|
+
if (this.queryBuilder[methodName]) {
|
|
65
|
+
console.warn(`Method ${methodName} already exists in QueryBuilder, overriding with plugin method`);
|
|
66
|
+
}
|
|
67
|
+
// Bind to QueryBuilder instance
|
|
68
|
+
this.queryBuilder[methodName] = method.bind(this);
|
|
69
|
+
}
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Register hooks for the plugin
|
|
75
|
+
* Override this method to register hooks
|
|
76
|
+
* @protected
|
|
77
|
+
*/
|
|
78
|
+
_registerHooks() {
|
|
79
|
+
// Default implementation - can be overridden by child classes
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Register middlewares for the plugin
|
|
84
|
+
* Override this method to register middlewares
|
|
85
|
+
* @protected
|
|
86
|
+
*/
|
|
87
|
+
_registerMiddlewares() {
|
|
88
|
+
// Default implementation - can be overridden by child classes
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Add method to plugin
|
|
93
|
+
* @param {string} name - Method name
|
|
94
|
+
* @param {Function} method - Method function
|
|
95
|
+
* @returns {BasePlugin} Plugin instance for chaining
|
|
96
|
+
*/
|
|
97
|
+
addMethod(name, method) {
|
|
98
|
+
this.methods[name] = method;
|
|
99
|
+
return this;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Remove method from plugin
|
|
104
|
+
* @param {string} name - Method name
|
|
105
|
+
* @returns {BasePlugin} Plugin instance for chaining
|
|
106
|
+
*/
|
|
107
|
+
removeMethod(name) {
|
|
108
|
+
delete this.methods[name];
|
|
109
|
+
return this;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Register a hook
|
|
114
|
+
* @param {string} event - Hook event name
|
|
115
|
+
* @param {Function} handler - Hook handler function
|
|
116
|
+
* @param {Object} options - Hook options
|
|
117
|
+
* @param {number} options.priority - Hook priority (higher number = higher priority)
|
|
118
|
+
* @param {boolean} options.once - Whether the hook should run only once
|
|
119
|
+
* @returns {BasePlugin} Plugin instance for chaining
|
|
120
|
+
*/
|
|
121
|
+
registerHook(event, handler, options = {}) {
|
|
122
|
+
if (!this.hooks[event]) {
|
|
123
|
+
this.hooks[event] = [];
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
this.hooks[event].push({
|
|
127
|
+
handler,
|
|
128
|
+
priority: options.priority || 0,
|
|
129
|
+
once: options.once || false,
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
// Sort hooks by priority (higher priority first)
|
|
133
|
+
this.hooks[event].sort((a, b) => b.priority - a.priority);
|
|
134
|
+
|
|
135
|
+
return this;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* Trigger a hook
|
|
140
|
+
* @param {string} event - Hook event name
|
|
141
|
+
* @param {...any} args - Arguments to pass to hook handlers
|
|
142
|
+
* @returns {Promise<Array>} Results from all hook handlers
|
|
143
|
+
*/
|
|
144
|
+
async triggerHook(event, ...args) {
|
|
145
|
+
if (!this.hooks[event]) {
|
|
146
|
+
return [];
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
const results = [];
|
|
150
|
+
const hooksToRemove = [];
|
|
151
|
+
|
|
152
|
+
for (let i = 0; i < this.hooks[event].length; i++) {
|
|
153
|
+
const hook = this.hooks[event][i];
|
|
154
|
+
try {
|
|
155
|
+
const result = await hook.handler(...args);
|
|
156
|
+
results.push(result);
|
|
157
|
+
|
|
158
|
+
if (hook.once) {
|
|
159
|
+
hooksToRemove.push(i);
|
|
160
|
+
}
|
|
161
|
+
} catch (error) {
|
|
162
|
+
console.error(`Error in hook "${event}" for plugin "${this.pluginName}":`, error);
|
|
163
|
+
// Continue with other hooks even if one fails
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
// Remove once hooks
|
|
168
|
+
if (hooksToRemove.length > 0) {
|
|
169
|
+
for (let i = hooksToRemove.length - 1; i >= 0; i--) {
|
|
170
|
+
this.hooks[event].splice(hooksToRemove[i], 1);
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
return results;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
/**
|
|
178
|
+
* Register a middleware
|
|
179
|
+
* @param {string} type - Middleware type
|
|
180
|
+
* @param {Function} middleware - Middleware function
|
|
181
|
+
* @param {Object} options - Middleware options
|
|
182
|
+
* @param {number} options.priority - Middleware priority (higher number = higher priority)
|
|
183
|
+
* @returns {BasePlugin} Plugin instance for chaining
|
|
184
|
+
*/
|
|
185
|
+
registerMiddleware(type, middleware, options = {}) {
|
|
186
|
+
if (!this.middlewares[type]) {
|
|
187
|
+
this.middlewares[type] = [];
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
this.middlewares[type].push({
|
|
191
|
+
middleware,
|
|
192
|
+
priority: options.priority || 0,
|
|
193
|
+
});
|
|
194
|
+
|
|
195
|
+
// Sort middlewares by priority (higher priority first)
|
|
196
|
+
this.middlewares[type].sort((a, b) => b.priority - a.priority);
|
|
197
|
+
|
|
198
|
+
return this;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
/**
|
|
202
|
+
* Execute middlewares
|
|
203
|
+
* @param {string} type - Middleware type
|
|
204
|
+
* @param {*} context - Context object
|
|
205
|
+
* @param {...any} args - Additional arguments
|
|
206
|
+
* @returns {Promise<*>} Result after middleware processing
|
|
207
|
+
*/
|
|
208
|
+
async executeMiddlewares(type, context, ...args) {
|
|
209
|
+
if (!this.middlewares[type]) {
|
|
210
|
+
return context;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
let index = 0;
|
|
214
|
+
const middlewares = this.middlewares[type];
|
|
215
|
+
|
|
216
|
+
const next = async () => {
|
|
217
|
+
if (index >= middlewares.length) {
|
|
218
|
+
return context;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
const mw = middlewares[index++];
|
|
222
|
+
try {
|
|
223
|
+
return await mw.middleware(context, next, ...args);
|
|
224
|
+
} catch (error) {
|
|
225
|
+
console.error(`Error in middleware "${type}" for plugin "${this.pluginName}":`, error);
|
|
226
|
+
throw error;
|
|
227
|
+
}
|
|
228
|
+
};
|
|
229
|
+
|
|
230
|
+
return await next();
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
/**
|
|
234
|
+
* Cleanup plugin resources
|
|
235
|
+
* This method should be called when plugin is being removed
|
|
236
|
+
* @returns {Promise<void>}
|
|
237
|
+
*/
|
|
238
|
+
async cleanup() {
|
|
239
|
+
// Remove bound methods from QueryBuilder
|
|
240
|
+
Object.keys(this.methods).forEach(methodName => {
|
|
241
|
+
if (this.queryBuilder[methodName]) {
|
|
242
|
+
delete this.queryBuilder[methodName];
|
|
243
|
+
}
|
|
244
|
+
});
|
|
245
|
+
|
|
246
|
+
// Clear hooks and middlewares
|
|
247
|
+
this.hooks = {};
|
|
248
|
+
this.middlewares = {};
|
|
249
|
+
this.methods = {};
|
|
250
|
+
this.initialized = false;
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
/**
|
|
254
|
+
* Get plugin metadata
|
|
255
|
+
* @returns {Object} Plugin metadata
|
|
256
|
+
*/
|
|
257
|
+
getMetadata() {
|
|
258
|
+
return {
|
|
259
|
+
name: this.pluginName,
|
|
260
|
+
version: '1.0.0',
|
|
261
|
+
description: 'Base plugin class',
|
|
262
|
+
dependencies: [],
|
|
263
|
+
methods: Object.keys(this.methods),
|
|
264
|
+
hooks: Object.keys(this.hooks),
|
|
265
|
+
middlewares: Object.keys(this.middlewares),
|
|
266
|
+
};
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
/**
|
|
270
|
+
* Validate plugin configuration
|
|
271
|
+
* @param {Object} config - Plugin configuration
|
|
272
|
+
* @returns {Object} Validation result
|
|
273
|
+
*/
|
|
274
|
+
validateConfig(config) {
|
|
275
|
+
const errors = [];
|
|
276
|
+
const warnings = [];
|
|
277
|
+
|
|
278
|
+
// Check required methods
|
|
279
|
+
if (typeof this._registerMethods !== 'function') {
|
|
280
|
+
errors.push('Plugin must implement _registerMethods() method');
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
// Check plugin name
|
|
284
|
+
if (!this.pluginName || this.pluginName === 'BasePlugin') {
|
|
285
|
+
warnings.push('Plugin should have a unique name');
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
return {
|
|
289
|
+
valid: errors.length === 0,
|
|
290
|
+
errors,
|
|
291
|
+
warnings,
|
|
292
|
+
};
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
/**
|
|
296
|
+
* Get plugin configuration
|
|
297
|
+
* @returns {Object} Plugin configuration
|
|
298
|
+
*/
|
|
299
|
+
getConfig() {
|
|
300
|
+
return {
|
|
301
|
+
name: this.pluginName,
|
|
302
|
+
enabled: this.initialized,
|
|
303
|
+
methods: Object.keys(this.methods),
|
|
304
|
+
hooks: Object.keys(this.hooks),
|
|
305
|
+
middlewares: Object.keys(this.middlewares),
|
|
306
|
+
};
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
/**
|
|
310
|
+
* Check if plugin is initialized
|
|
311
|
+
* @returns {boolean} True if plugin is initialized
|
|
312
|
+
*/
|
|
313
|
+
isInitialized() {
|
|
314
|
+
return this.initialized;
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
/**
|
|
318
|
+
* Get plugin name
|
|
319
|
+
* @returns {string} Plugin name
|
|
320
|
+
*/
|
|
321
|
+
getName() {
|
|
322
|
+
return this.pluginName;
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
/**
|
|
326
|
+
* Get all registered methods
|
|
327
|
+
* @returns {Array<string>} Array of method names
|
|
328
|
+
*/
|
|
329
|
+
getMethods() {
|
|
330
|
+
return Object.keys(this.methods);
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
/**
|
|
334
|
+
* Get all registered hooks
|
|
335
|
+
* @returns {Object} Object containing hook events and handlers
|
|
336
|
+
*/
|
|
337
|
+
getHooks() {
|
|
338
|
+
return this.hooks;
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
/**
|
|
342
|
+
* Get all registered middlewares
|
|
343
|
+
* @returns {Object} Object containing middleware types and handlers
|
|
344
|
+
*/
|
|
345
|
+
getMiddlewares() {
|
|
346
|
+
return this.middlewares;
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
/**
|
|
350
|
+
* Check if a method exists
|
|
351
|
+
* @param {string} methodName - Method name to check
|
|
352
|
+
* @returns {boolean} True if method exists
|
|
353
|
+
*/
|
|
354
|
+
hasMethod(methodName) {
|
|
355
|
+
return this.methods.hasOwnProperty(methodName);
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
/**
|
|
359
|
+
* Check if a hook exists for an event
|
|
360
|
+
* @param {string} event - Hook event name
|
|
361
|
+
* @returns {boolean} True if hook exists for the event
|
|
362
|
+
*/
|
|
363
|
+
hasHook(event) {
|
|
364
|
+
return this.hooks.hasOwnProperty(event) && this.hooks[event].length > 0;
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
/**
|
|
368
|
+
* Check if a middleware exists for a type
|
|
369
|
+
* @param {string} type - Middleware type
|
|
370
|
+
* @returns {boolean} True if middleware exists for the type
|
|
371
|
+
*/
|
|
372
|
+
hasMiddleware(type) {
|
|
373
|
+
return this.middlewares.hasOwnProperty(type) && this.middlewares[type].length > 0;
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
/**
|
|
377
|
+
* Get QueryBuilder instance
|
|
378
|
+
* @returns {Object} QueryBuilder instance
|
|
379
|
+
*/
|
|
380
|
+
getQueryBuilder() {
|
|
381
|
+
return this.queryBuilder;
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
/**
|
|
385
|
+
* Set QueryBuilder instance
|
|
386
|
+
* @param {Object} queryBuilder - QueryBuilder instance
|
|
387
|
+
* @returns {BasePlugin} Plugin instance for chaining
|
|
388
|
+
*/
|
|
389
|
+
setQueryBuilder(queryBuilder) {
|
|
390
|
+
this.queryBuilder = queryBuilder;
|
|
391
|
+
return this;
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
/**
|
|
395
|
+
* Reload plugin
|
|
396
|
+
* This method can be used to reload plugin configuration
|
|
397
|
+
* @returns {Promise<void>}
|
|
398
|
+
*/
|
|
399
|
+
async reload() {
|
|
400
|
+
await this.cleanup();
|
|
401
|
+
await this.init();
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
/**
|
|
405
|
+
* Get plugin status
|
|
406
|
+
* @returns {Object} Plugin status information
|
|
407
|
+
*/
|
|
408
|
+
getStatus() {
|
|
409
|
+
return {
|
|
410
|
+
name: this.pluginName,
|
|
411
|
+
initialized: this.initialized,
|
|
412
|
+
methodsCount: Object.keys(this.methods).length,
|
|
413
|
+
hooksCount: Object.keys(this.hooks).length,
|
|
414
|
+
middlewaresCount: Object.keys(this.middlewares).length,
|
|
415
|
+
queryBuilder: this.queryBuilder ? 'Connected' : 'Not connected'
|
|
416
|
+
};
|
|
417
|
+
}
|
|
418
|
+
}
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license MIT
|
|
3
|
+
* Copyright (c) 2026-present AetherFramework Contributors.
|
|
4
|
+
* SPDX-License-Identifier: MIT
|
|
5
|
+
* @module @aetherframework/database/plugin/BatchOperationPlugin
|
|
6
|
+
*/
|
|
7
|
+
import { BasePlugin } from "./BasePlugin.js";
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Batch Operation Plugin - Optimizes performance and reliability for bulk data operations
|
|
11
|
+
*/
|
|
12
|
+
export class BatchOperationPlugin extends BasePlugin {
|
|
13
|
+
constructor(queryBuilder) {
|
|
14
|
+
super(queryBuilder);
|
|
15
|
+
this.pluginName = "BatchOperationPlugin";
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
_registerMethods() {
|
|
19
|
+
// Register batch operation methods to QueryBuilder
|
|
20
|
+
this.queryBuilder.batchUpdate = this.batchUpdate.bind(this);
|
|
21
|
+
this.queryBuilder.batchDelete = this.batchDelete.bind(this);
|
|
22
|
+
this.queryBuilder.batchUpsert = this.batchUpsert.bind(this);
|
|
23
|
+
this.queryBuilder.batchIncrementDecrement = this.batchIncrementDecrement.bind(this);
|
|
24
|
+
this.queryBuilder.insertBatch = this.insertBatch.bind(this);
|
|
25
|
+
this.queryBuilder._optimizeChunkSize = this._optimizeChunkSize.bind(this);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Batch update multiple records
|
|
30
|
+
* @param {Array} data - Array of data objects for update
|
|
31
|
+
* @param {string} keyField - Key field name (default: 'id')
|
|
32
|
+
* @returns {Promise<Array>} Update results
|
|
33
|
+
*/
|
|
34
|
+
async batchUpdate(data, keyField = "id") {
|
|
35
|
+
if (!Array.isArray(data) || data.length === 0) {
|
|
36
|
+
throw new Error("Data must be a non-empty array for batch update");
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// Execute updates in parallel
|
|
40
|
+
const promises = data.map((item) => {
|
|
41
|
+
const keyValue = item[keyField];
|
|
42
|
+
const updateData = { ...item };
|
|
43
|
+
delete updateData[keyField];
|
|
44
|
+
|
|
45
|
+
return this.queryBuilder.clone()
|
|
46
|
+
.where(keyField, "=", keyValue)
|
|
47
|
+
.update(updateData)
|
|
48
|
+
.execute();
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
return Promise.all(promises);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Batch delete multiple records by key values
|
|
56
|
+
* @param {Array} values - Array of key values to delete
|
|
57
|
+
* @param {string} keyField - Key field name (default: 'id')
|
|
58
|
+
* @returns {Promise<Object>} Delete result
|
|
59
|
+
*/
|
|
60
|
+
async batchDelete(values, keyField = "id") {
|
|
61
|
+
return this.queryBuilder.whereIn(keyField, values).delete().execute();
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Batch upsert (insert or update)
|
|
66
|
+
* @param {Array} data - Array of data objects
|
|
67
|
+
* @param {Array} uniqueColumns - Unique constraint columns
|
|
68
|
+
* @param {number} chunkSize - Chunk size for batch processing
|
|
69
|
+
* @returns {Promise<Array>} Upsert results
|
|
70
|
+
*/
|
|
71
|
+
async batchUpsert(data, uniqueColumns = ["id"], chunkSize = 100) {
|
|
72
|
+
if (!Array.isArray(data) || data.length === 0) {
|
|
73
|
+
throw new Error("Data must be a non-empty array");
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
const results = [];
|
|
77
|
+
|
|
78
|
+
// Process in chunks using Promise.all for parallel execution
|
|
79
|
+
for (let i = 0; i < data.length; i += chunkSize) {
|
|
80
|
+
const chunk = data.slice(i, i + chunkSize);
|
|
81
|
+
const chunkPromises = chunk.map((item) =>
|
|
82
|
+
this.queryBuilder.clone().upsert(item, uniqueColumns),
|
|
83
|
+
);
|
|
84
|
+
|
|
85
|
+
const chunkResults = await Promise.all(chunkPromises);
|
|
86
|
+
results.push(...chunkResults);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
return results;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Batch increment/decrement
|
|
94
|
+
* @param {string} field - Field to increment/decrement
|
|
95
|
+
* @param {Object} values - Object with key: amount pairs
|
|
96
|
+
* @param {string} keyField - Key field name (default: 'id')
|
|
97
|
+
* @param {boolean} increment - True for increment, false for decrement
|
|
98
|
+
* @returns {Promise<Array>} Update results
|
|
99
|
+
*/
|
|
100
|
+
async batchIncrementDecrement(
|
|
101
|
+
field,
|
|
102
|
+
values,
|
|
103
|
+
keyField = "id",
|
|
104
|
+
increment = true,
|
|
105
|
+
) {
|
|
106
|
+
const results = [];
|
|
107
|
+
for (const [keyValue, amount] of Object.entries(values)) {
|
|
108
|
+
const query = this.queryBuilder.clone().where(keyField, "=", keyValue);
|
|
109
|
+
const result = increment
|
|
110
|
+
? await query.increment(field, amount)
|
|
111
|
+
: await query.decrement(field, amount);
|
|
112
|
+
results.push(result);
|
|
113
|
+
}
|
|
114
|
+
return results;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* Batch insert with optimized chunk size
|
|
119
|
+
* @param {Array} data - Array of data objects
|
|
120
|
+
* @param {number} chunkSize - Chunk size for batch processing
|
|
121
|
+
* @returns {Promise<Object>} Insert results
|
|
122
|
+
*/
|
|
123
|
+
async insertBatch(data, chunkSize = 1000) {
|
|
124
|
+
if (!Array.isArray(data) || data.length === 0) {
|
|
125
|
+
throw new Error("insertBatch: data must be a non-empty array");
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// Optimize chunk size based on data structure
|
|
129
|
+
const optimizedChunkSize = this._optimizeChunkSize(chunkSize, data[0]);
|
|
130
|
+
|
|
131
|
+
const results = [];
|
|
132
|
+
for (let i = 0; i < data.length; i += optimizedChunkSize) {
|
|
133
|
+
const chunk = data.slice(i, i + optimizedChunkSize);
|
|
134
|
+
const result = await this.queryBuilder.clone().insert(chunk).execute();
|
|
135
|
+
results.push(result);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
return {
|
|
139
|
+
success: true,
|
|
140
|
+
totalInserted: data.length,
|
|
141
|
+
affectedRows: results.reduce((sum, r) => sum + (r.affectedRows || 0), 0),
|
|
142
|
+
chunks: results.length,
|
|
143
|
+
details: results,
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* Optimize chunk size based on data structure
|
|
149
|
+
* @param {number} defaultSize - Default chunk size
|
|
150
|
+
* @param {Object} sampleRow - Sample data row
|
|
151
|
+
* @returns {number} Optimized chunk size
|
|
152
|
+
*/
|
|
153
|
+
_optimizeChunkSize(defaultSize, sampleRow) {
|
|
154
|
+
if (!sampleRow) return Math.min(defaultSize, 1000);
|
|
155
|
+
|
|
156
|
+
const columnCount = Object.keys(sampleRow).length;
|
|
157
|
+
|
|
158
|
+
// MySQL recommendation: each SQL should not exceed 4~16MB
|
|
159
|
+
if (columnCount > 35) return Math.min(defaultSize, 350); // Very wide table
|
|
160
|
+
if (columnCount > 20) return Math.min(defaultSize, 650);
|
|
161
|
+
if (columnCount > 12) return Math.min(defaultSize, 1100);
|
|
162
|
+
if (columnCount > 6) return Math.min(defaultSize, 1800);
|
|
163
|
+
return Math.min(defaultSize, 2500); // Narrow table can be larger
|
|
164
|
+
}
|
|
165
|
+
}
|