@@ -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' ;
@@ -131,13 +138,6 @@ class SQLiteCasette {
131
138
132
139
class PostgreSQLCasette {
133
140
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
141
async function startServer ( ) {
142
142
return new Promise ( ( _res , _rej ) => {
143
143
let done = false ;
@@ -262,8 +262,130 @@ class PostgreSQLCasette {
262
262
}
263
263
}
264
264
265
+ class MySQLCassette {
266
+ async newConnection ( options ) {
267
+ async function startServer ( ) {
268
+ return new Promise ( ( _res , _rej ) => {
269
+ let done = false ;
270
+ const res = ( ...args ) => {
271
+ if ( ! done ) {
272
+ done = true ;
273
+ _res ( ...args ) ;
274
+ }
275
+ }
276
+ const rej = ( ...args ) => {
277
+ if ( ! done ) {
278
+ done = true ;
279
+ _rej ( ...args ) ;
280
+ }
281
+ }
282
+ sleep ( 10000 ) . then ( ( ) => rej ( new Error ( 'Timeout database connection' ) ) ) ;
283
+ const p = cp . spawn ( 'mysqld_safe' , [ `--datadir=${ process . env . MYSQL_DATA } ` ] , {
284
+ detached : true ,
285
+ } ) ;
286
+ p . stdout . on ( 'data' , data => {
287
+ const message = data . toString ( ) ;
288
+ if ( options . verbose ) {
289
+ console . log ( message ) ;
290
+ }
291
+ if ( message . includes ( 'database system is ready to accept connections' ) ) {
292
+ res ( ) ;
293
+ }
294
+ } ) ;
295
+ p . stderr . on ( 'data' , data => {
296
+ const message = data . toString ( ) ;
297
+ if ( options . verbose ) {
298
+ console . error ( message ) ;
299
+ }
300
+ if ( message . includes ( 'database system is ready to accept connections' ) ) {
301
+ res ( ) ;
302
+ }
303
+ } ) ;
304
+ } ) ;
305
+ }
306
+
307
+ if ( ! fs . existsSync ( '/run/mysqld/mysqld.sock' ) ) {
308
+ for ( let retries = 5 ; retries > 0 ; retries -- ) {
309
+ try {
310
+ await startServer ( ) ;
311
+ break ;
312
+ } catch ( e ) {
313
+ if ( options . verbose ) {
314
+ console . error ( e ) ;
315
+ }
316
+ }
317
+ await sleep ( 1000 ) ;
318
+ }
319
+ }
320
+ for ( let retries = 20 ; retries > 0 ; retries -- ) {
321
+ try {
322
+ await exec ( "mysql -e 'CREATE DATABASE IF NOT EXISTS track'" ) ;
323
+ const conn = knex ( {
324
+ client : 'mysql' ,
325
+ connection : {
326
+ host : '127.0.0.1' ,
327
+ port : 3306 ,
328
+ user : 'root' ,
329
+ database : 'track' ,
330
+ } ,
331
+ } ) ;
332
+ await conn . raw ( "SELECT 1" ) ;
333
+ if ( options && options . clean ) {
334
+ try {
335
+ let tables = await conn . raw ( "SHOW TABLES" ) ;
336
+ for ( let table of tables . rows ) {
337
+ await conn . raw ( `DROP TABLE IF EXISTS ${ table . Tables_in_track } CASCADE` ) ;
338
+ }
339
+ } catch ( e2 ) { console . error ( e2 ) ; }
340
+ }
341
+ return conn ;
342
+ } catch ( e ) {
343
+ await sleep ( 500 ) ;
344
+ }
345
+ }
346
+ throw Error ( "Failed to start MySQL server" ) ;
347
+ }
348
+ explainSql ( s ) {
349
+ // return `EXPLAIN ${s}`;
350
+ }
351
+ tableSchemaSql ( ) {
352
+ return `
353
+ SELECT
354
+ ordinal_position AS "order",
355
+ column_name AS name,
356
+ data_type AS raw_type
357
+ FROM information_schema.columns
358
+ WHERE
359
+ table_schema = 'track' AND
360
+ table_name = ?
361
+ ORDER BY ordinal_position
362
+ ` ;
363
+ }
364
+ updateAutoIncrementSql ( table , column ) {
365
+ return `ALTER TABLE ${ table } AUTO_INCREMENT = ? + 1` ;
366
+ }
367
+ lastValueSql ( table , idCol ) {
368
+ return `SELECT * FROM ${ table } WHERE ${ idCol } = LAST_INSERT_ID()` ;
369
+ }
370
+ async listFk ( table , conn ) {
371
+ return await conn . query ( `
372
+ SELECT
373
+ kcu.column_name AS column,
374
+ ccu.table_name AS foreign_table,
375
+ ccu.column_name AS foreign_column
376
+ FROM information_schema.table_constraints AS tc
377
+ INNER JOIN information_schema.key_column_usage AS kcu ON tc.constraint_name = kcu.constraint_name
378
+ INNER JOIN information_schema.constraint_column_usage AS ccu ON ccu.constraint_name = tc.constraint_name
379
+ WHERE tc.constraint_type = 'FOREIGN KEY' AND tc.table_name = '${ table } '
380
+ ` ) ;
381
+ }
382
+ errorOf ( e ) {
383
+ return [ SQL_ERROR . UNKNOWN ] ;
384
+ }
385
+ }
386
+
265
387
/**
266
- * Handles SQLite connection
388
+ * Handles connection
267
389
*/
268
390
class Connection {
269
391
static timeout ( client , extra ) {
@@ -284,6 +406,10 @@ class Connection {
284
406
case "postgresql" :
285
407
casette = new PostgreSQLCasette ( options ) ;
286
408
break ;
409
+ case "my" :
410
+ case "mysql" :
411
+ casette = new MySQLCassette ( options ) ;
412
+ break ;
287
413
default :
288
414
casette = new SQLiteCasette ( options ) ;
289
415
break ;
0 commit comments