@@ -5,6 +5,7 @@ const knex = require('knex');
5
5
const path = require ( 'path' ) ;
6
6
const util = require ( 'util' ) ;
7
7
const _ = require ( './i18n' ) . text ;
8
+ const exec = util . promisify ( cp . exec ) ;
8
9
9
10
const NULL_MARKER = '__null__' ;
10
11
const SQL_ERROR = {
@@ -57,6 +58,12 @@ function typeOf(x) {
57
58
return Object . prototype . toString . call ( x ) . slice ( 8 , - 1 ) . toLowerCase ( ) ;
58
59
}
59
60
61
+ async function sleep ( ms ) {
62
+ return new Promise ( ( res , rej ) => {
63
+ setTimeout ( ( ) => res ( ) , ms ) ;
64
+ } ) ;
65
+ }
66
+
60
67
class SQLiteCasette {
61
68
async newConnection ( options ) {
62
69
const dbfile = ( options || { } ) . file || 'db.sqlite' ;
@@ -71,6 +78,9 @@ class SQLiteCasette {
71
78
useNullAsDefault : true
72
79
} ) ;
73
80
}
81
+ rows ( result ) {
82
+ return result ;
83
+ }
74
84
explainSql ( s ) {
75
85
return `EXPLAIN QUERY PLAN ${ s } ` ;
76
86
}
@@ -131,13 +141,6 @@ class SQLiteCasette {
131
141
132
142
class PostgreSQLCasette {
133
143
async newConnection ( options ) {
134
- const exec = util . promisify ( cp . exec ) ;
135
- async function sleep ( ms ) {
136
- return new Promise ( ( res , rej ) => {
137
- setTimeout ( ( ) => res ( ) , ms ) ;
138
- } ) ;
139
- }
140
-
141
144
async function startServer ( ) {
142
145
return new Promise ( ( _res , _rej ) => {
143
146
let done = false ;
@@ -223,6 +226,9 @@ class PostgreSQLCasette {
223
226
}
224
227
throw Error ( "Failed to start PostgreSQL server" ) ;
225
228
}
229
+ rows ( result ) {
230
+ return result . rows ;
231
+ }
226
232
explainSql ( s ) {
227
233
return `EXPLAIN ${ s } ` ;
228
234
}
@@ -262,8 +268,81 @@ class PostgreSQLCasette {
262
268
}
263
269
}
264
270
271
+ class MySQLCassette {
272
+ async newConnection ( options ) {
273
+ for ( let retries = 10 ; retries > 0 ; retries -- ) {
274
+ try {
275
+ exec ( 'service mysql start' ) ;
276
+ const conn = knex ( {
277
+ client : 'mysql' ,
278
+ connection : {
279
+ host : '127.0.0.1' ,
280
+ port : 3306 ,
281
+ user : 'track' ,
282
+ password : 'password' ,
283
+ database : 'track' ,
284
+ } ,
285
+ } ) ;
286
+ await conn . raw ( "SELECT 1" ) ;
287
+ if ( options && options . clean ) {
288
+ try {
289
+ let tables = await conn . raw ( "SHOW TABLES" ) ;
290
+ for ( let table of tables [ 0 ] ) {
291
+ await conn . raw ( `DROP TABLE IF EXISTS ${ table . Tables_in_track } CASCADE` ) ;
292
+ }
293
+ } catch ( e2 ) { console . error ( e2 ) ; }
294
+ }
295
+ return conn ;
296
+ } catch ( e ) {
297
+ await sleep ( 500 ) ;
298
+ }
299
+ }
300
+ throw Error ( "Failed to start MySQL server" ) ;
301
+ }
302
+ rows ( result ) {
303
+ return result [ 0 ] ;
304
+ }
305
+ explainSql ( s ) {
306
+ return `EXPLAIN ${ s } ` ;
307
+ }
308
+ tableSchemaSql ( ) {
309
+ return `
310
+ SELECT
311
+ ordinal_position AS "order",
312
+ column_name AS name,
313
+ data_type AS raw_type
314
+ FROM information_schema.columns
315
+ WHERE
316
+ table_schema = 'track' AND
317
+ table_name = ?
318
+ ORDER BY ordinal_position
319
+ ` ;
320
+ }
321
+ updateAutoIncrementSql ( table , column ) {
322
+ return `ALTER TABLE ${ table } AUTO_INCREMENT = ? + 1` ;
323
+ }
324
+ lastValueSql ( table , idCol ) {
325
+ return `SELECT * FROM ${ table } WHERE ${ idCol } = LAST_INSERT_ID()` ;
326
+ }
327
+ async listFk ( table , conn ) {
328
+ return await conn . query ( `
329
+ SELECT
330
+ kcu.column_name AS column,
331
+ ccu.table_name AS foreign_table,
332
+ ccu.column_name AS foreign_column
333
+ FROM information_schema.table_constraints AS tc
334
+ INNER JOIN information_schema.key_column_usage AS kcu ON tc.constraint_name = kcu.constraint_name
335
+ INNER JOIN information_schema.constraint_column_usage AS ccu ON ccu.constraint_name = tc.constraint_name
336
+ WHERE tc.constraint_type = 'FOREIGN KEY' AND tc.table_name = '${ table } '
337
+ ` ) ;
338
+ }
339
+ errorOf ( e ) {
340
+ return [ SQL_ERROR . UNKNOWN ] ;
341
+ }
342
+ }
343
+
265
344
/**
266
- * Handles SQLite connection
345
+ * Handles connection
267
346
*/
268
347
class Connection {
269
348
static timeout ( client , extra ) {
@@ -272,6 +351,9 @@ class Connection {
272
351
case "postgres" :
273
352
case "postgresql" :
274
353
return 12000 + ( extra || 0 ) ;
354
+ case "my" :
355
+ case "mysql" :
356
+ return 12000 + ( extra || 0 ) ;
275
357
default :
276
358
return 2000 + ( extra || 0 ) ;
277
359
}
@@ -284,6 +366,10 @@ class Connection {
284
366
case "postgresql" :
285
367
casette = new PostgreSQLCasette ( options ) ;
286
368
break ;
369
+ case "my" :
370
+ case "mysql" :
371
+ casette = new MySQLCassette ( options ) ;
372
+ break ;
287
373
default :
288
374
casette = new SQLiteCasette ( options ) ;
289
375
break ;
@@ -417,24 +503,19 @@ class Connection {
417
503
* @returns {Promise<Array<object>?> }
418
504
*/
419
505
async query ( sql , opt_args ) {
420
-
421
- function rows ( records ) {
422
- return ! ! records . rows ? records . rows /* postgres */ : records /* sqlite */
423
- }
424
-
425
506
if ( sql . trim ( ) . length === 0 ) {
426
507
throw 'Empty query' ;
427
508
}
428
509
429
510
const isSelectStatement = / ^ \s * S E L E C T / i. test ( sql ) ;
430
511
if ( isSelectStatement ) {
431
- const count = rows ( await this . _conn . raw ( `SELECT count(1) AS count FROM (${ sql . replace ( / ; \s * $ / , "" ) } ) AS x` , opt_args ) ) . length ;
512
+ const count = this . _cassette . rows ( await this . _conn . raw ( `SELECT count(1) AS count FROM (${ sql . replace ( / ; \s * $ / , "" ) } ) AS x` , opt_args ) ) . length ;
432
513
if ( count > ( this . _options . maxRows || 10000 ) ) {
433
514
throw 'Too many records' ;
434
515
}
435
516
}
436
517
437
- return rows ( await this . _conn . raw ( sql , opt_args ) ) ;
518
+ return this . _cassette . rows ( await this . _conn . raw ( sql , opt_args ) ) ;
438
519
}
439
520
440
521
/**
0 commit comments