Skip to content

Commit b0a158c

Browse files
author
MIYASHITA, Akihiro
committed
MySQL support
1 parent fd517a5 commit b0a158c

File tree

4 files changed

+141
-11
lines changed

4 files changed

+141
-11
lines changed

README.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,10 @@ This software is released under the [MIT License](LICENSE).
1515

1616
## Release Note
1717

18+
### Version 2.6.0
19+
20+
* [Feat] MySQL support
21+
1822
### Version 2.5.0
1923

2024
* [Feat] Allow multiple checks in a single testcase (Runner)

lib/connection.js

Lines changed: 134 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ const knex = require('knex');
55
const path = require('path');
66
const util = require('util');
77
const _ = require('./i18n').text;
8+
const exec = util.promisify(cp.exec);
89

910
const NULL_MARKER = '__null__';
1011
const SQL_ERROR = {
@@ -57,6 +58,12 @@ function typeOf(x) {
5758
return Object.prototype.toString.call(x).slice(8, -1).toLowerCase();
5859
}
5960

61+
async function sleep(ms) {
62+
return new Promise((res, rej) => {
63+
setTimeout(() => res(), ms);
64+
});
65+
}
66+
6067
class SQLiteCasette {
6168
async newConnection(options) {
6269
const dbfile = (options || {}).file || 'db.sqlite';
@@ -131,13 +138,6 @@ class SQLiteCasette {
131138

132139
class PostgreSQLCasette {
133140
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-
141141
async function startServer() {
142142
return new Promise((_res, _rej) => {
143143
let done = false;
@@ -262,8 +262,130 @@ class PostgreSQLCasette {
262262
}
263263
}
264264

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+
265387
/**
266-
* Handles SQLite connection
388+
* Handles connection
267389
*/
268390
class Connection {
269391
static timeout(client, extra) {
@@ -284,6 +406,10 @@ class Connection {
284406
case "postgresql":
285407
casette = new PostgreSQLCasette(options);
286408
break;
409+
case "my":
410+
case "mysql":
411+
casette = new MySQLCassette(options);
412+
break;
287413
default:
288414
casette = new SQLiteCasette(options);
289415
break;

package-lock.json

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "track-db-test-library",
3-
"version": "2.5.0",
3+
"version": "2.6.0-rc1",
44
"description": "Test utility for Track database challenges",
55
"main": "index.js",
66
"scripts": {

0 commit comments

Comments
 (0)