Skip to content

Commit c25f2e1

Browse files
authored
Merge pull request #48 from givery-technology/mysql-support
[CIT-949] MySQL support
2 parents fd517a5 + b5803d6 commit c25f2e1

File tree

4 files changed

+1214
-3743
lines changed

4 files changed

+1214
-3743
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: 96 additions & 15 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';
@@ -71,6 +78,9 @@ class SQLiteCasette {
7178
useNullAsDefault: true
7279
});
7380
}
81+
rows(result) {
82+
return result;
83+
}
7484
explainSql(s) {
7585
return `EXPLAIN QUERY PLAN ${s}`;
7686
}
@@ -131,13 +141,6 @@ class SQLiteCasette {
131141

132142
class PostgreSQLCasette {
133143
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-
141144
async function startServer() {
142145
return new Promise((_res, _rej) => {
143146
let done = false;
@@ -223,6 +226,9 @@ class PostgreSQLCasette {
223226
}
224227
throw Error("Failed to start PostgreSQL server");
225228
}
229+
rows(result) {
230+
return result.rows;
231+
}
226232
explainSql(s) {
227233
return `EXPLAIN ${s}`;
228234
}
@@ -262,8 +268,81 @@ class PostgreSQLCasette {
262268
}
263269
}
264270

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+
265344
/**
266-
* Handles SQLite connection
345+
* Handles connection
267346
*/
268347
class Connection {
269348
static timeout(client, extra) {
@@ -272,6 +351,9 @@ class Connection {
272351
case "postgres":
273352
case "postgresql":
274353
return 12000 + (extra || 0);
354+
case "my":
355+
case "mysql":
356+
return 12000 + (extra || 0);
275357
default:
276358
return 2000 + (extra || 0);
277359
}
@@ -284,6 +366,10 @@ class Connection {
284366
case "postgresql":
285367
casette = new PostgreSQLCasette(options);
286368
break;
369+
case "my":
370+
case "mysql":
371+
casette = new MySQLCassette(options);
372+
break;
287373
default:
288374
casette = new SQLiteCasette(options);
289375
break;
@@ -417,24 +503,19 @@ class Connection {
417503
* @returns {Promise<Array<object>?>}
418504
*/
419505
async query(sql, opt_args) {
420-
421-
function rows(records) {
422-
return !!records.rows ? records.rows /* postgres */ : records /* sqlite */
423-
}
424-
425506
if (sql.trim().length === 0) {
426507
throw 'Empty query';
427508
}
428509

429510
const isSelectStatement = /^\s*SELECT /i.test(sql);
430511
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;
432513
if (count > (this._options.maxRows || 10000)) {
433514
throw 'Too many records';
434515
}
435516
}
436517

437-
return rows(await this._conn.raw(sql, opt_args));
518+
return this._cassette.rows(await this._conn.raw(sql, opt_args));
438519
}
439520

440521
/**

0 commit comments

Comments
 (0)