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

#125 Objects SQL definition #15

Merged
merged 11 commits into from
May 5, 2016
4 changes: 2 additions & 2 deletions appveyor.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ environment:
MYSQL_ENV_MYSQL_USER: root
MYSQL_ENV_MYSQL_PASSWORD: Password12!
MYSQL_ENV_MYSQL_DATABASE: sqlectron
MYSQL_PATH: C:\Program Files\MySql\MySQL Server 5.6
MYSQL_PATH: C:\Program Files\MySql\MySQL Server 5.7
MYSQL_PWD: Password12!
# sql server
SQLSERVER_ENV_SQLSERVER_HOST: localhost
Expand Down Expand Up @@ -46,7 +46,7 @@ build_script:
- createdb sqlectron
- psql -d sqlectron -a -f spec/databases/postgresql/schema/schema.sql
# mysql
- mysql -e "drop database test; create database sqlectron;" --user=root
- mysql -e "create database sqlectron;" --user=root
- mysql sqlectron < spec/databases/mysql/schema/schema.sql --user=root
# sqlserver
- ps: ./appveyor-sqlserver.ps1 SQL2008R2SP2
Expand Down
89 changes: 87 additions & 2 deletions spec/db.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -92,9 +92,9 @@ describe('db', () => {
});

describe('.listRoutines', () => {
it('should list all routines and their type', async() =>{
it('should list all routines with their type and definition', async() =>{
const routines = await dbConn.listRoutines();
const [routine] = routines;
const routine = dbClient === 'postgresql' ? routines[1] : routines[0];

// Postgresql routine type is always function. SP do not exist
// Futhermore, PostgreSQL is expected to have two functions in schema, because
Expand All @@ -106,6 +106,13 @@ describe('db', () => {
expect(routines).to.have.length(1);
expect(routine).to.have.deep.property('routineType').to.eql('PROCEDURE');
}

// Check routine definition
if (dbClient === 'sqlserver') {
expect(routine).to.have.deep.property('routineDefinition').to.contain('SELECT @Count = COUNT(*) FROM dbo.users');
} else {
expect(routine).to.have.deep.property('routineDefinition').to.contain('SELECT COUNT(*) FROM users');
}
});
});

Expand Down Expand Up @@ -144,6 +151,84 @@ describe('db', () => {
});
});

describe('.getTableCreateScript', () => {
it('should return table create script', async() => {
const [createScript] = await dbConn.getTableCreateScript('users');

if (dbClient === 'mysql') {
expect(createScript).to.contain('CREATE TABLE `users` (\n' +
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since we are using ES6. Couldn't you use template string instead of these concatenations? Template string keeps the code more readable.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I can, but than the code won't be indented, since it will count tab spaces as a part of the template string, so the test will fail. I find that way less readable, since it's moved to the left, but if others think differently I'll change it, no problem 👍

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You are right. If it depends on the output format then template string is a bad choice. Is gonna look to weird with all content moved to left.

' `id` int(11) NOT NULL AUTO_INCREMENT,\n' +
' `username` varchar(45) DEFAULT NULL,\n' +
' `email` varchar(150) DEFAULT NULL,\n' +
' `password` varchar(45) DEFAULT NULL,\n' +
' PRIMARY KEY (`id`)\n' +
') ENGINE=InnoDB');
} else if (dbClient === 'postgresql') {
expect(createScript).to.eql('CREATE TABLE users (\n' +
' id integer NOT NULL,\n' +
' username text NOT NULL,\n' +
' email text NOT NULL,\n' +
' password text NOT NULL\n' +
');\n' +
'\n' +
'ALTER TABLE users ADD CONSTRAINT users_pkey PRIMARY KEY (id)'
);
} else { // dbClient === SQL Server
expect(createScript).to.contain('CREATE TABLE users (\r\n' +
' id int IDENTITY(1,1) NOT NULL,\r\n' +
' username varchar(45) NULL,\r\n' +
' email varchar(150) NULL,\r\n' +
' password varchar(45) NULL,\r\n' +
')\r\n');
expect(createScript).to.contain('ALTER TABLE users ADD CONSTRAINT PK__users');
expect(createScript).to.contain('PRIMARY KEY (id)');
}
});
});

describe('.getTableSelectScript', () => {
it('should return SELECT table script', async() => {
const selectQuery = await dbConn.getTableSelectScript('users');
expect(selectQuery).to.eql('SELECT id, username, email, password FROM users;');
});
});


describe('.getTableInsertScript', () => {
it('should return INSERT INTO table script', async() => {
const insertQuery = await dbConn.getTableInsertScript('users');
expect(insertQuery).to.eql(`INSERT INTO users (id, username, email, password)\n VALUES (?, ?, ?, ?);`);
});
});

describe('.getTableUpdateScript', () => {
it('should return UPDATE table script', async() => {
const updateQuery = await dbConn.getTableUpdateScript('users');
expect(updateQuery).to.eql(`UPDATE users\n SET id=?, username=?, email=?, password=?\n WHERE <condition>;`);
});
});

describe('.getTableDeleteScript', () => {
it('should return table DELETE script', async() => {
const deleteQuery = await dbConn.getTableDeleteScript('roles');
expect(deleteQuery).to.eql('DELETE FROM roles WHERE <condition>;');
});
});

describe('.getViewCreateScript', () => {
it('should return CREATE VIEW script', async() => {
const [createScript] = await dbConn.getViewCreateScript('email_view');

if (dbClient === 'mysql') {
expect(createScript).to.contain('VIEW `email_view` AS select `users`.`email` AS `email`,`users`.`password` AS `password` from `users`');
} else if (dbClient === 'postgresql') {
expect(createScript).to.eql(`CREATE OR REPLACE VIEW email_view AS\n SELECT users.email,\n users.password\n FROM users;`);
} else { // dbClient === SQL Server
expect(createScript).to.eql(`\nCREATE VIEW dbo.email_view AS\nSELECT dbo.users.email, dbo.users.password\nFROM dbo.users;\n`);
}
});
});

describe('.executeQuery', () => {
beforeEach(() => Promise.all([
dbConn.executeQuery(`
Expand Down
44 changes: 44 additions & 0 deletions src/db/client.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,12 @@ export function createConnection(server, database) {
executeQuery: executeQuery.bind(null, server, database),
listDatabases: listDatabases.bind(null, server, database),
getQuerySelectTop: getQuerySelectTop.bind(null, server, database),
getTableCreateScript: getTableCreateScript.bind(null, server, database),
getTableSelectScript: getTableSelectScript.bind(null, server, database),
getTableInsertScript: getTableInsertScript.bind(null, server, database),
getTableUpdateScript: getTableUpdateScript.bind(null, server, database),
getTableDeleteScript: getTableDeleteScript.bind(null, server, database),
getViewCreateScript: getViewCreateScript.bind(null, server, database),
truncateAllTables: truncateAllTables.bind(null, server, database),
};
}
Expand Down Expand Up @@ -153,10 +159,48 @@ async function getQuerySelectTop(server, database, table, limit) {
return database.connection.getQuerySelectTop(table, _limit);
}

async function getTableCreateScript(server, database, table) {
checkIsConnected(server, database);
return database.connection.getTableCreateScript(table);
}

async function getTableSelectScript(server, database, table) {
const columnNames = await getTableColumnNames(server, database, table);
return `SELECT ${columnNames.join(', ')} FROM ${table};`;
}


async function getTableInsertScript(server, database, table) {
const columnNames = await getTableColumnNames(server, database, table);
return `INSERT INTO ${table} (${columnNames.join(', ')})\n VALUES (${columnNames.fill('?').join(', ')});`;
}

async function getTableUpdateScript(server, database, table) {
const columnNames = await getTableColumnNames(server, database, table);
const setColumnForm = columnNames.map(columnName => `${columnName}=?`).join(', ');
const condition = '<condition>';
return `UPDATE ${table}\n SET ${setColumnForm}\n WHERE ${condition};`;
}

async function getTableDeleteScript(server, database, table) {
const condition = '<condition>';
return `DELETE FROM ${table} WHERE ${condition};`;
}

async function getViewCreateScript(server, database, view) {
checkIsConnected(server, database);
return database.connection.getViewCreateScript(view);
}

function truncateAllTables(server, database) {
return database.connection.truncateAllTables();
}

async function getTableColumnNames(server, database, table) {
checkIsConnected(server, database);
const columns = await database.connection.listTableColumns(table);
return columns.map(column => column.columnName);
}

async function loadConfigLimit() {
if (limitSelect === null) {
Expand Down
26 changes: 25 additions & 1 deletion src/db/clients/mysql.js
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,8 @@ export default function(server, database) {
executeQuery: (query) => executeQuery(client, query),
listDatabases: () => listDatabases(client),
getQuerySelectTop: (table, limit) => getQuerySelectTop(client, table, limit),
getTableCreateScript: (table) => getTableCreateScript(client, table),
getViewCreateScript: (view) => getViewCreateScript(client, view),
truncateAllTables: () => truncateAllTables(client),
});
});
Expand Down Expand Up @@ -84,7 +86,7 @@ export function listViews(client) {
export function listRoutines(client) {
return new Promise((resolve, reject) => {
const sql = `
SELECT routine_name, routine_type
SELECT routine_name, routine_type, routine_definition
FROM information_schema.routines
WHERE routine_schema = database()
ORDER BY routine_name
Expand All @@ -95,6 +97,7 @@ export function listRoutines(client) {
resolve(data.map(row => ({
routineName: row.routine_name,
routineType: row.routine_type,
routineDefinition: row.routine_definition,
})));
});
});
Expand Down Expand Up @@ -172,6 +175,27 @@ export function getQuerySelectTop(client, table, limit) {
return `SELECT * FROM ${wrapQuery(table)} LIMIT ${limit}`;
}

export function getTableCreateScript(client, table) {
return new Promise((resolve, reject) => {
const sql = `SHOW CREATE TABLE ${table}`;
const params = [];
client.query(sql, params, (err, data) => {
if (err) return reject(_getRealError(client, err));
resolve(data.map(row => row['Create Table']));
});
});
}

export function getViewCreateScript(client, view) {
return new Promise((resolve, reject) => {
const sql = `SHOW CREATE VIEW ${view}`;
const params = [];
client.query(sql, params, (err, data) => {
if (err) return reject(_getRealError(client, err));
resolve(data.map(row => row['Create View']));
});
});
}

export function wrapQuery(item) {
return `\`${item}\``;
Expand Down
73 changes: 72 additions & 1 deletion src/db/clients/postgresql.js
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@ export default function(server, database) {
executeQuery: (query) => executeQuery(client, query),
listDatabases: () => listDatabases(client),
getQuerySelectTop: (table, limit) => getQuerySelectTop(client, table, limit),
getTableCreateScript: (table) => getTableCreateScript(client, table),
getViewCreateScript: (view) => getViewCreateScript(client, view),
truncateAllTables: () => truncateAllTables(client),
});
});
Expand Down Expand Up @@ -82,7 +84,7 @@ export function listViews(client) {
export function listRoutines(client) {
return new Promise((resolve, reject) => {
const sql = `
SELECT routine_name, routine_type
SELECT routine_name, routine_type, routine_definition
FROM information_schema.routines
WHERE routine_schema = $1
ORDER BY routine_name
Expand All @@ -95,6 +97,7 @@ export function listRoutines(client) {
resolve(data.rows.map(row => ({
routineName: row.routine_name,
routineType: row.routine_type,
routineDefinition: row.routine_definition,
})));
});
});
Expand Down Expand Up @@ -175,6 +178,74 @@ export function getQuerySelectTop(client, table, limit) {
return `SELECT * FROM ${wrapQuery(table)} LIMIT ${limit}`;
}

export function getTableCreateScript(client, table) {
return new Promise((resolve, reject) => {
// Reference http://stackoverflow.com/a/32885178
const sql = `
SELECT
'CREATE TABLE ' || tabdef.table_name || E' (\n' ||
array_to_string(
array_agg(
' ' || tabdef.column_name || ' ' || tabdef.type || ' '|| tabdef.not_null
)
, E',\n'
) || E'\n);\n' ||
CASE WHEN tc.constraint_name IS NULL THEN ''
ELSE E'\nALTER TABLE ' || tabdef.table_name ||
' ADD CONSTRAINT ' || tc.constraint_name ||
' PRIMARY KEY ' || '(' || substring(constr.column_name from 0 for char_length(constr.column_name)-1) || ')'
END AS createtable
FROM
( SELECT
c.relname AS table_name,
a.attname AS column_name,
pg_catalog.format_type(a.atttypid, a.atttypmod) AS type,
CASE
WHEN a.attnotnull THEN 'NOT NULL'
ELSE 'NULL'
END AS not_null
FROM pg_class c,
pg_attribute a,
pg_type t
WHERE c.relname = $1
AND a.attnum > 0
AND a.attrelid = c.oid
AND a.atttypid = t.oid
ORDER BY a.attnum DESC
) AS tabdef
LEFT JOIN information_schema.table_constraints tc
ON tc.table_name = tabdef.table_name
AND tc.constraint_Type = 'PRIMARY KEY'
LEFT JOIN LATERAL (
SELECT column_name || ', ' AS column_name
FROM information_schema.key_column_usage kcu
WHERE kcu.constraint_name = tc.constraint_name
ORDER BY ordinal_position
) AS constr ON true
GROUP BY tabdef.table_name, tc.constraint_name, constr.column_name;
`;
const params = [
table,
];
client.query(sql, params, (err, data) => {
if (err) return reject(err);
resolve(data.rows.map(row => row.createtable));
});
});
}

export function getViewCreateScript(client, view) {
return new Promise((resolve, reject) => {
const createViewSql = `CREATE OR REPLACE VIEW ${view} AS`;
const sql = `SELECT pg_get_viewdef($1::regclass, true)`;
const params = [ view ];
client.query(sql, params, (err, data) => {
if (err) return reject(err);
resolve(data.rows.map(row => `${createViewSql}\n${row.pg_get_viewdef}`));
});
});
}


export function wrapQuery(item) {
return `"${item}"`;
Expand Down
Loading