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

Commit

Permalink
Merge pull request #15 from BornaP/125-objects-sql-definition
Browse files Browse the repository at this point in the history
#125 Objects SQL definition
  • Loading branch information
maxcnunes committed May 5, 2016
2 parents 7cd0a55 + 5f379d6 commit 05553d2
Show file tree
Hide file tree
Showing 6 changed files with 308 additions and 7 deletions.
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' +
' `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

0 comments on commit 05553d2

Please sign in to comment.