Skip to content
This repository has been archived by the owner on Mar 15, 2021. It is now read-only.

Commit

Permalink
Add support to execute a query with multiple statements
Browse files Browse the repository at this point in the history
There is no breaking changes. Executing a query with a single statement
still have the same result.

Although executing a query with multiple statements will return
something like this:
```js
{
  fiels: [ [<fields first query>], [<fields second query>] ],
  rows: [ [<rows first query>], [<rows second query>] ]
}
```

To help with tests I also included the knex module. Otherwise I would
have to test the `executeQuery` function using itself.
  • Loading branch information
maxcnunes committed Nov 22, 2015
1 parent 1881c49 commit 28d99a3
Show file tree
Hide file tree
Showing 11 changed files with 130 additions and 14 deletions.
4 changes: 2 additions & 2 deletions docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ mysql:
MYSQL_PASSWORD: password
MYSQL_DATABASE: sqlectron
volumes:
- ./spec/databases/mysql:/docker-entrypoint-initdb.d
- ./spec/databases/mysql/schema:/docker-entrypoint-initdb.d

postgres:
image: postgres:9.4.5
Expand All @@ -19,7 +19,7 @@ postgres:
POSTGRES_PASSWORD: password
POSTGRES_DB: sqlectron
volumes:
- ./spec/databases/postgres:/docker-entrypoint-initdb.d
- ./spec/databases/postgresql/schema:/docker-entrypoint-initdb.d

test:
image: node:0.12.7-onbuild
Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@
"chai-as-promised": "^5.1.0",
"eslint": "^1.7.3",
"eslint-config-airbnb": "^0.1.0",
"knex": "^0.9.0",
"mocha": "^2.3.3",
"sinon": "^1.17.2",
"sqlite3": "^3.1.0"
Expand Down
1 change: 1 addition & 0 deletions spec/databases/config.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,5 +15,6 @@ export default {
user: process.env.MYSQL_ENV_MYSQL_USER,
password: process.env.MYSQL_ENV_MYSQL_PASSWORD,
database: process.env.MYSQL_ENV_MYSQL_DATABASE,
multipleStatements: true,
},
};
24 changes: 24 additions & 0 deletions spec/databases/helper.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { join } from 'path';
import { readFileSync } from 'fs';
import knexInit from 'knex';


export function readSQLScript(client) {
const scriptPath = join(__dirname, `./${client}/truncate-all-tables.sql`);
return readFileSync(scriptPath, { encoding: 'utf8' });
}


export function truncateAllTables(serverInfo, { truncateTablesSQL }) {
const { name: client, ...connection } = serverInfo;
const knex = knexInit({ client, connection });

return knex.raw(truncateTablesSQL).then((sql) => {
// with knex depending on the DB client
// the rows may come in a "rows" property
const statements = (sql.rows || sql[0]);
return knex.raw(statements.map((sqlQuery) => {
return sqlQuery.trucate_table_cmd;
}).join(''));
});
}
File renamed without changes.
7 changes: 7 additions & 0 deletions spec/databases/mysql/truncate-all-tables.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
SELECT 'SET FOREIGN_KEY_CHECKS = 0;' trucate_table_cmd
UNION
SELECT Concat('TRUNCATE TABLE `' ,table_schema, '`.`', TABLE_NAME, '`;') trucate_table_cmd
FROM INFORMATION_SCHEMA.TABLES
WHERE table_schema = (SELECT database())
UNION
SELECT 'SET FOREIGN_KEY_CHECKS = 1;' trucate_table_cmd
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
CREATE TABLE users(
id INT PRIMARY KEY NOT NULL,
id SERIAL PRIMARY KEY,
username TEXT NOT NULL,
email TEXT NOT NULL,
password TEXT NOT NULL
);

CREATE TABLE roles(
id INT PRIMARY KEY NOT NULL,
id SERIAL PRIMARY KEY,
name TEXT NOT NULL
);
3 changes: 3 additions & 0 deletions spec/databases/postgresql/truncate-all-tables.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
SELECT Concat('TRUNCATE TABLE "' ,table_schema, '"."', TABLE_NAME, '" RESTART IDENTITY CASCADE;') trucate_table_cmd
FROM INFORMATION_SCHEMA.TABLES
WHERE table_schema = (SELECT current_schema())
64 changes: 55 additions & 9 deletions spec/db.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,18 @@ import chai, { expect } from 'chai';
import chaiAsPromised from 'chai-as-promised';
import { db } from '../src';
import config from './databases/config';
import * as helper from './databases/helper';
chai.use(chaiAsPromised);


const SUPPORTED_DB_CLIENTS = {
mysql: {
defaulTables: [ 'information_schema' ],
truncateTablesSQL: helper.readSQLScript('mysql'),
},
postgresql: {
defaulTables: [ 'postgres' ],
truncateTablesSQL: helper.readSQLScript('postgresql'),
},
};

Expand All @@ -34,13 +37,13 @@ describe('db', () => {
});

describe('given is already connected', () => {
beforeEach(() => {
const serverInfo = {
...config[dbClient],
name: dbClient,
client: dbClient,
};
const serverInfo = {
...config[dbClient],
name: dbClient,
client: dbClient,
};

beforeEach(() => {
return db.connect(serverInfo, serverInfo.database);
});

Expand All @@ -65,14 +68,57 @@ describe('db', () => {
});

describe('.executeQuery', () => {
it('should execute a select', async () => {
const wrapQuery = require(`../src/db/clients/${dbClient}`).wrapQuery;
const wrapQuery = require(`../src/db/clients/${dbClient}`).wrapQuery;

beforeEach(() => {
return Promise.all([
db.executeQuery(`
INSERT INTO ${wrapQuery('users')} (username, email, password)
VALUES ('maxcnunes', '[email protected]', '123456')
`),
db.executeQuery(`
INSERT INTO ${wrapQuery('roles')} (name)
VALUES ('developer')
`),
]);
});

afterEach(() => helper.truncateAllTables(serverInfo, dbClientOpts));

it('should execute a single select query', async () => {
const result = await db.executeQuery(`select * from ${wrapQuery('users')}`);
expect(result).to.have.property('rows').to.eql([]);
expect(result).to.have.deep.property('fields[0].name').to.eql('id');
expect(result).to.have.deep.property('fields[1].name').to.eql('username');
expect(result).to.have.deep.property('fields[2].name').to.eql('email');
expect(result).to.have.deep.property('fields[3].name').to.eql('password');

expect(result).to.have.deep.property('rows[0].id').to.eql(1);
expect(result).to.have.deep.property('rows[0].username').to.eql('maxcnunes');
expect(result).to.have.deep.property('rows[0].password').to.eql('123456');
expect(result).to.have.deep.property('rows[0].email').to.eql('[email protected]');
});

it('should execute multiple select queries', async () => {
const result = await db.executeQuery(`
select * from ${wrapQuery('users')};
select * from ${wrapQuery('roles')};
`);

expect(result).to.have.deep.property('fields[0][0].name').to.eql('id');
expect(result).to.have.deep.property('fields[0][1].name').to.eql('username');
expect(result).to.have.deep.property('fields[0][2].name').to.eql('email');
expect(result).to.have.deep.property('fields[0][3].name').to.eql('password');

expect(result).to.have.deep.property('rows[0][0].id').to.eql(1);
expect(result).to.have.deep.property('rows[0][0].username').to.eql('maxcnunes');
expect(result).to.have.deep.property('rows[0][0].password').to.eql('123456');
expect(result).to.have.deep.property('rows[0][0].email').to.eql('[email protected]');

expect(result).to.have.deep.property('fields[1][0].name').to.eql('id');
expect(result).to.have.deep.property('fields[1][1].name').to.eql('name');

expect(result).to.have.deep.property('rows[1][0].id').to.eql(1);
expect(result).to.have.deep.property('rows[1][0].name').to.eql('developer');
});
});
});
Expand Down
1 change: 1 addition & 0 deletions src/db/clients/mysql.js
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,7 @@ function _configDatabase(serverInfo, databaseName, localPort) {
user: serverInfo.user,
password: serverInfo.password,
database: databaseName,
multipleStatements: true,
};

return config;
Expand Down
35 changes: 34 additions & 1 deletion src/db/clients/postgresql.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,9 @@ import connectTunnel from '../tunnel';


const debug = require('../../debug')('db:clients:postgresql');
const REGEX_BEGIN_QUERY = /^(\n|\s)*/g;
const REGEX_BETWEEN_QUERIES = /;(\n|\s)*/g;
const REGEX_END_QUERY = /;/g;


export default function(serverInfo, databaseName) {
Expand Down Expand Up @@ -78,7 +81,37 @@ export function listTables(client) {
}


export function executeQuery(client, query) {
export async function executeQuery(client, query) {
const statements = query
.replace(REGEX_BEGIN_QUERY, '')
.replace(REGEX_BETWEEN_QUERIES, ';')
.split(REGEX_END_QUERY)
.filter(text => text.length);

const queries = statements.map(statement =>
executePromiseQuery(client, statement)
);

// Execute each statement in a different query
// while node-postgres does not have support for multiple query results
// https://github.com/brianc/node-postgres/pull/776
const results = await Promise.all(queries);
if (statements.length === 1) {
return results[0];
}

return results.reduce((allResults, result) => {
allResults.rows.push(result.rows);
allResults.fields.push(result.fields);
allResults.rowCount.push(result.rowCount);
return allResults;
}, { rows: [], fields: [], rowCount: [] });
}


function executePromiseQuery(client, query) {
// node-postgres has support for Promise query
// but that always returns the "fields" property empty
return new Promise((resolve, reject) => {
client.query(query, (err, data) => {
if (err) return reject(err);
Expand Down

0 comments on commit 28d99a3

Please sign in to comment.