1
1
import { ICommonObject , INode , INodeData , INodeParams } from '../../../src/Interface'
2
2
import { getBaseClasses , getCredentialData , getCredentialParam } from '../../../src/utils'
3
3
import { ListKeyOptions , RecordManagerInterface , UpdateOptions } from '@langchain/community/indexes/base'
4
- import { DataSource , QueryRunner } from 'typeorm'
4
+ import { DataSource } from 'typeorm'
5
5
6
6
class MySQLRecordManager_RecordManager implements INode {
7
7
label : string
@@ -167,47 +167,58 @@ type MySQLRecordManagerOptions = {
167
167
168
168
class MySQLRecordManager implements RecordManagerInterface {
169
169
lc_namespace = [ 'langchain' , 'recordmanagers' , 'mysql' ]
170
-
171
- datasource : DataSource
172
-
173
- queryRunner : QueryRunner
174
-
170
+ config : MySQLRecordManagerOptions
175
171
tableName : string
176
-
177
172
namespace : string
178
173
179
174
constructor ( namespace : string , config : MySQLRecordManagerOptions ) {
180
- const { mysqlOptions , tableName } = config
175
+ const { tableName } = config
181
176
this . namespace = namespace
182
177
this . tableName = tableName || 'upsertion_records'
183
- this . datasource = new DataSource ( mysqlOptions )
178
+ this . config = config
179
+ }
180
+
181
+ private async getDataSource ( ) : Promise < DataSource > {
182
+ const { mysqlOptions } = this . config
183
+ if ( ! mysqlOptions ) {
184
+ throw new Error ( 'No datasource options provided' )
185
+ }
186
+ // Prevent using default Postgres port, otherwise will throw uncaught error and crashing the app
187
+ if ( mysqlOptions . port === 5432 ) {
188
+ throw new Error ( 'Invalid port number' )
189
+ }
190
+ const dataSource = new DataSource ( mysqlOptions )
191
+ await dataSource . initialize ( )
192
+ return dataSource
184
193
}
185
194
186
195
async createSchema ( ) : Promise < void > {
187
196
try {
188
- const appDataSource = await this . datasource . initialize ( )
189
-
190
- this . queryRunner = appDataSource . createQueryRunner ( )
197
+ const dataSource = await this . getDataSource ( )
198
+ const queryRunner = dataSource . createQueryRunner ( )
191
199
192
- await this . queryRunner . manager . query ( `create table if not exists \`${ this . tableName } \` (
200
+ await queryRunner . manager . query ( `create table if not exists \`${ this . tableName } \` (
193
201
\`uuid\` varchar(36) primary key default (UUID()),
194
202
\`key\` varchar(255) not null,
195
203
\`namespace\` varchar(255) not null,
196
204
\`updated_at\` DOUBLE precision not null,
197
205
\`group_id\` longtext,
198
206
unique key \`unique_key_namespace\` (\`key\`,
199
207
\`namespace\`));` )
208
+
200
209
const columns = [ `updated_at` , `key` , `namespace` , `group_id` ]
201
210
for ( const column of columns ) {
202
211
// MySQL does not support 'IF NOT EXISTS' function for Index
203
- const Check = await this . queryRunner . manager . query (
212
+ const Check = await queryRunner . manager . query (
204
213
`SELECT COUNT(1) IndexIsThere FROM INFORMATION_SCHEMA.STATISTICS
205
214
WHERE table_schema=DATABASE() AND table_name='${ this . tableName } ' AND index_name='${ column } _index';`
206
215
)
207
216
if ( Check [ 0 ] . IndexIsThere === 0 )
208
- await this . queryRunner . manager . query ( `CREATE INDEX \`${ column } _index\`
217
+ await queryRunner . manager . query ( `CREATE INDEX \`${ column } _index\`
209
218
ON \`${ this . tableName } \` (\`${ column } \`);` )
210
219
}
220
+
221
+ await queryRunner . release ( )
211
222
} catch ( e : any ) {
212
223
// This error indicates that the table already exists
213
224
// Due to asynchronous nature of the code, it is possible that
@@ -221,12 +232,17 @@ class MySQLRecordManager implements RecordManagerInterface {
221
232
}
222
233
223
234
async getTime ( ) : Promise < number > {
235
+ const dataSource = await this . getDataSource ( )
224
236
try {
225
- const res = await this . queryRunner . manager . query ( `SELECT UNIX_TIMESTAMP(NOW()) AS epoch` )
237
+ const queryRunner = dataSource . createQueryRunner ( )
238
+ const res = await queryRunner . manager . query ( `SELECT UNIX_TIMESTAMP(NOW()) AS epoch` )
239
+ await queryRunner . release ( )
226
240
return Number . parseFloat ( res [ 0 ] . epoch )
227
241
} catch ( error ) {
228
242
console . error ( 'Error getting time in MySQLRecordManager:' )
229
243
throw error
244
+ } finally {
245
+ await dataSource . destroy ( )
230
246
}
231
247
}
232
248
@@ -235,6 +251,9 @@ class MySQLRecordManager implements RecordManagerInterface {
235
251
return
236
252
}
237
253
254
+ const dataSource = await this . getDataSource ( )
255
+ const queryRunner = dataSource . createQueryRunner ( )
256
+
238
257
const updatedAt = await this . getTime ( )
239
258
const { timeAtLeast, groupIds : _groupIds } = updateOptions ?? { }
240
259
@@ -261,9 +280,18 @@ class MySQLRecordManager implements RecordManagerInterface {
261
280
ON DUPLICATE KEY UPDATE \`updated_at\` = VALUES(\`updated_at\`)`
262
281
263
282
// To handle multiple files upsert
264
- for ( const record of recordsToUpsert ) {
265
- // Consider using a transaction for batch operations
266
- await this . queryRunner . manager . query ( query , record . flat ( ) )
283
+ try {
284
+ for ( const record of recordsToUpsert ) {
285
+ // Consider using a transaction for batch operations
286
+ await queryRunner . manager . query ( query , record . flat ( ) )
287
+ }
288
+
289
+ await queryRunner . release ( )
290
+ } catch ( error ) {
291
+ console . error ( 'Error updating in MySQLRecordManager:' )
292
+ throw error
293
+ } finally {
294
+ await dataSource . destroy ( )
267
295
}
268
296
}
269
297
@@ -272,6 +300,9 @@ class MySQLRecordManager implements RecordManagerInterface {
272
300
return [ ]
273
301
}
274
302
303
+ const dataSource = await this . getDataSource ( )
304
+ const queryRunner = dataSource . createQueryRunner ( )
305
+
275
306
// Prepare the placeholders and the query
276
307
const placeholders = keys . map ( ( ) => `?` ) . join ( ', ' )
277
308
const query = `
@@ -284,21 +315,27 @@ class MySQLRecordManager implements RecordManagerInterface {
284
315
285
316
try {
286
317
// Execute the query
287
- const rows = await this . queryRunner . manager . query ( query , [ this . namespace , ...keys . flat ( ) ] )
318
+ const rows = await queryRunner . manager . query ( query , [ this . namespace , ...keys . flat ( ) ] )
288
319
// Create a set of existing keys for faster lookup
289
320
const existingKeysSet = new Set ( rows . map ( ( row : { key : string } ) => row . key ) )
290
321
// Map the input keys to booleans indicating if they exist
291
322
keys . forEach ( ( key , index ) => {
292
323
existsArray [ index ] = existingKeysSet . has ( key )
293
324
} )
325
+ await queryRunner . release ( )
294
326
return existsArray
295
327
} catch ( error ) {
296
328
console . error ( 'Error checking existence of keys' )
297
- throw error // Allow the caller to handle the error
329
+ throw error
330
+ } finally {
331
+ await dataSource . destroy ( )
298
332
}
299
333
}
300
334
301
335
async listKeys ( options ?: ListKeyOptions ) : Promise < string [ ] > {
336
+ const dataSource = await this . getDataSource ( )
337
+ const queryRunner = dataSource . createQueryRunner ( )
338
+
302
339
try {
303
340
const { before, after, limit, groupIds } = options ?? { }
304
341
let query = `SELECT \`key\` FROM \`${ this . tableName } \` WHERE \`namespace\` = ?`
@@ -330,11 +367,14 @@ class MySQLRecordManager implements RecordManagerInterface {
330
367
query += ';'
331
368
332
369
// Directly using try/catch with async/await for cleaner flow
333
- const result = await this . queryRunner . manager . query ( query , values )
370
+ const result = await queryRunner . manager . query ( query , values )
371
+ await queryRunner . release ( )
334
372
return result . map ( ( row : { key : string } ) => row . key )
335
373
} catch ( error ) {
336
374
console . error ( 'MySQLRecordManager listKeys Error: ' )
337
- throw error // Re-throw the error to be handled by the caller
375
+ throw error
376
+ } finally {
377
+ await dataSource . destroy ( )
338
378
}
339
379
}
340
380
@@ -343,16 +383,22 @@ class MySQLRecordManager implements RecordManagerInterface {
343
383
return
344
384
}
345
385
386
+ const dataSource = await this . getDataSource ( )
387
+ const queryRunner = dataSource . createQueryRunner ( )
388
+
346
389
const placeholders = keys . map ( ( ) => '?' ) . join ( ', ' )
347
390
const query = `DELETE FROM \`${ this . tableName } \` WHERE \`namespace\` = ? AND \`key\` IN (${ placeholders } );`
348
391
const values = [ this . namespace , ...keys ] . map ( ( v ) => ( typeof v !== 'string' ? `${ v } ` : v ) )
349
392
350
393
// Directly using try/catch with async/await for cleaner flow
351
394
try {
352
- await this . queryRunner . manager . query ( query , values )
395
+ await queryRunner . manager . query ( query , values )
396
+ await queryRunner . release ( )
353
397
} catch ( error ) {
354
398
console . error ( 'Error deleting keys' )
355
- throw error // Re-throw the error to be handled by the caller
399
+ throw error
400
+ } finally {
401
+ await dataSource . destroy ( )
356
402
}
357
403
}
358
404
}
0 commit comments