Skip to content

Commit

Permalink
ensure semi-colon appears at end of all create scripts (#22)
Browse files Browse the repository at this point in the history
Signed-off-by: Matthew Peveler <[email protected]>
  • Loading branch information
MasterOdin authored Feb 24, 2021
1 parent 33aa400 commit 39cc562
Show file tree
Hide file tree
Showing 8 changed files with 85 additions and 48 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ jobs:
sqlserver-collation: Latin1_General_CS_AS
- node-version: 14.x
mysql-version: 8
maraidb-version: 10
maraidb-version: 10.5
cassandra-version: 3
postgres-version: 13
sqlserver-collation: Latin1_General_CI_AS
Expand Down
74 changes: 43 additions & 31 deletions spec/db.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -353,7 +353,7 @@ describe('db', () => {
const [createScript] = await dbConn.getTableCreateScript('users');

if (dbAdapter === 'mysql' && versionCompare(dbConn.getVersion().version, '8') >= 0) {
expect(createScript).to.contain('CREATE TABLE `users` (\n' +
expect(createScript).to.eql('CREATE TABLE `users` (\n' +
' `id` int NOT NULL AUTO_INCREMENT,\n' +
' `username` varchar(45) DEFAULT NULL,\n' +
' `email` varchar(150) DEFAULT NULL,\n' +
Expand All @@ -363,9 +363,10 @@ describe('db', () => {
' PRIMARY KEY (`id`),\n' +
' KEY `role_id` (`role_id`),\n' +
' CONSTRAINT `users_ibfk_1` FOREIGN KEY (`role_id`) REFERENCES `roles` (`id`) ON DELETE CASCADE\n' +
') ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci');
') ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;');
} else if (mysqlAdapters.includes(dbAdapter)) {
expect(createScript).to.contain('CREATE TABLE `users` (\n' +
const charset = dbAdapter === 'mariadb' && versionCompare(dbConn.getVersion().version, '10.1') > 0 ? 'utf8mb4' : 'latin1';
expect(createScript).to.eql('CREATE TABLE `users` (\n' +
' `id` int(11) NOT NULL AUTO_INCREMENT,\n' +
' `username` varchar(45) DEFAULT NULL,\n' +
' `email` varchar(150) DEFAULT NULL,\n' +
Expand All @@ -375,9 +376,10 @@ describe('db', () => {
' PRIMARY KEY (`id`),\n' +
' KEY `role_id` (`role_id`),\n' +
' CONSTRAINT `users_ibfk_1` FOREIGN KEY (`role_id`) REFERENCES `roles` (`id`) ON DELETE CASCADE\n' +
') ENGINE=InnoDB');
`) ENGINE=InnoDB DEFAULT CHARSET=${charset};`);
} else if (postgresAdapters.includes(dbAdapter)) {
expect(createScript).to.eql('CREATE TABLE public.users (\n' +
expect(createScript).to.eql(
'CREATE TABLE public.users (\n' +
' id integer NOT NULL,\n' +
' username text NOT NULL,\n' +
' email text NOT NULL,\n' +
Expand All @@ -386,19 +388,21 @@ describe('db', () => {
' createdat date NULL\n' +
');\n' +
'\n' +
'ALTER TABLE public.users ADD CONSTRAINT users_pkey PRIMARY KEY (id)',
'ALTER TABLE public.users ADD CONSTRAINT users_pkey PRIMARY KEY (id);',
);
} else if (dbAdapter === 'sqlserver') {
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' +
' role_id int NULL,\r\n' +
' createdat datetime NULL,\r\n' +
')\r\n');
expect(createScript).to.contain('ALTER TABLE users ADD CONSTRAINT PK__users');
expect(createScript).to.contain('PRIMARY KEY (id)');
expect(createScript).to.match(new RegExp(
'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' +
' role_id int NULL,\\r\\n' +
' createdat datetime NULL,\\r\\n' +
'\\);\\r\\n' +
'\\r\\n' +
'ALTER TABLE users ADD CONSTRAINT PK__users__[a-zA-Z0-9]+ PRIMARY KEY \\(id\\);'
));
} else if (dbAdapter === 'sqlite') {
expect(createScript).to.eql('CREATE TABLE users (\n' +
' id INTEGER NOT NULL,\n' +
Expand All @@ -408,7 +412,8 @@ describe('db', () => {
' role_id INT,\n' +
' createdat DATETIME NULL,\n' +
' PRIMARY KEY (id),\n' +
' FOREIGN KEY (role_id) REFERENCES roles (id)\n)',
' FOREIGN KEY (role_id) REFERENCES roles (id)\n' +
');',
);
} else if (dbAdapter === 'cassandra') {
expect(createScript).to.eql(undefined);
Expand Down Expand Up @@ -569,10 +574,11 @@ describe('db', () => {
it('should return CREATE VIEW script', async () => {
const [createScript] = await dbConn.getViewCreateScript('email_view');
if (mysqlAdapters.includes(dbAdapter)) {
expect(createScript).to.contain([
expect(createScript).to.eql([
'CREATE ALGORITHM=UNDEFINED DEFINER=`root`@`localhost` SQL SECURITY DEFINER',
'VIEW `email_view`',
'AS select `users`.`email` AS `email`,`users`.`password` AS `password`',
'from `users`',
'from `users`;',
].join(' '));
} else if (dbAdapter === 'postgresql') {
expect(createScript).to.eql([
Expand All @@ -589,15 +595,15 @@ describe('db', () => {
].join('\n'));
} else if (dbAdapter === 'sqlserver') {
expect(createScript).to.eql([
'\nCREATE VIEW dbo.email_view AS',
'CREATE VIEW dbo.email_view AS',
'SELECT dbo.users.email, dbo.users.password',
'FROM dbo.users;\n',
'FROM dbo.users;',
].join('\n'));
} else if (dbAdapter === 'sqlite') {
expect(createScript).to.eql([
'CREATE VIEW email_view AS',
' SELECT users.email, users.password',
' FROM users',
' FROM users;',
].join('\n'));
} else if (dbAdapter === 'cassandra') {
expect(createScript).to.eql(undefined);
Expand All @@ -611,12 +617,11 @@ describe('db', () => {
it('should return CREATE PROCEDURE/FUNCTION script', async () => {
const [createScript] = await dbConn.getRoutineCreateScript('users_count', 'Procedure');
if (mysqlAdapters.includes(dbAdapter)) {
expect(createScript).to.contain('CREATE DEFINER=');
expect(createScript).to.contain([
'PROCEDURE `users_count`()',
expect(createScript).to.eql([
'CREATE DEFINER=`root`@`localhost` PROCEDURE `users_count`()',
'BEGIN',
' SELECT COUNT(*) FROM users;',
'END',
'END;',
].join('\n'));
} else if (dbAdapter === 'postgresql') {
expect(createScript).to.eql([
Expand All @@ -625,19 +630,26 @@ describe('db', () => {
' LANGUAGE sql',
'AS $function$',
' SELECT COUNT(*) FROM users AS total;',
'$function$\n',
'$function$;',
].join('\n'));
} else if (dbAdapter === 'redshift') {
expect(createScript).to.eql([
'CREATE OR REPLACE FUNCTION public.users_count()',
' RETURNS bigint AS $$',
' SELECT COUNT(*) FROM users AS total;',
'$$ LANGUAGE sql VOLATILE',
'$$ LANGUAGE sql VOLATILE;',
].join('\n'));
} else if (dbAdapter === 'sqlserver') {
expect(createScript).to.contain('CREATE PROCEDURE dbo.users_count');
expect(createScript).to.contain('@Count int OUTPUT');
expect(createScript).to.contain('SELECT @Count = COUNT(*) FROM dbo.users');
expect(createScript).to.eql([
'CREATE PROCEDURE dbo.users_count',
'(',
' @Count int OUTPUT',
')',
'AS',
' BEGIN',
' SELECT @Count = COUNT(*) FROM dbo.users',
' END;'
].join('\n'));
} else if (dbAdapter === 'cassandra' || dbAdapter === 'sqlite') {
expect(createScript).to.eql(undefined);
} else {
Expand Down
18 changes: 17 additions & 1 deletion spec/utils.spec.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { expect } from 'chai';
import { versionCompare } from '../src/utils';
import { appendSemiColon, versionCompare } from '../src/utils';

describe('utils', () => {
describe('.versionCompare', () => {
Expand All @@ -23,4 +23,20 @@ describe('utils', () => {
});
});
});

describe('.appendSemiColon', () => {
const parameters: [string][] = [
['test'],
['test;'],
['\ntest'],
['test\n'],
['\ntest\n'],
['\ntest;\n'],
];
parameters.forEach(([inputString]) => {
it(`.appendSemiColon(${JSON.stringify(inputString)}) === 'test;'`, () => {
expect(appendSemiColon(inputString)).to.eql('test;');
});
})
});
});
8 changes: 4 additions & 4 deletions src/adapters/mysql.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import mysql from 'mysql2';
import { identify } from 'sql-query-identifier';

import createLogger from '../logger';
import { createCancelablePromise } from '../utils';
import { appendSemiColon, createCancelablePromise } from '../utils';
import { AbstractAdapter } from './abstract_adapter';

import type { Result } from 'sql-query-identifier';
Expand Down Expand Up @@ -388,23 +388,23 @@ export default class MysqlAdapter extends AbstractAdapter {

const { data } = await this.driverExecuteQuery({ query: sql });

return (<mysql.RowDataPacket[]>data).map((row) => row['Create Table'] as string);
return (<mysql.RowDataPacket[]>data).map((row) => appendSemiColon(row['Create Table'] as string));
}

async getViewCreateScript(view: string): Promise<string[]> {
const sql = `SHOW CREATE VIEW ${view}`;

const { data } = await this.driverExecuteQuery({ query: sql });

return (<mysql.RowDataPacket[]>data).map((row) => row['Create View'] as string);
return (<mysql.RowDataPacket[]>data).map((row) => appendSemiColon(row['Create View'] as string));
}

async getRoutineCreateScript(routine: string, type: string): Promise<string[]> {
const sql = `SHOW CREATE ${type.toUpperCase()} ${routine}`;

const { data } = await this.driverExecuteQuery({ query: sql });

return (<mysql.RowDataPacket[]>data).map((row) => row[`Create ${type}`] as string);
return (<mysql.RowDataPacket[]>data).map((row) => appendSemiColon(row[`Create ${type}`] as string));
}

async getSchema(connection?: mysql.PoolConnection): Promise<string> {
Expand Down
8 changes: 4 additions & 4 deletions src/adapters/postgresql.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { identify } from 'sql-query-identifier';

import { buildDatabaseFilter, buildSchemaFilter } from '../filters';
import createLogger from '../logger';
import { createCancelablePromise, versionCompare } from '../utils';
import { appendSemiColon, createCancelablePromise, versionCompare } from '../utils';
import { Adapter, ADAPTERS } from './';
import { AbstractAdapter, QueryArgs, QueryRowResult, TableKeysResult } from './abstract_adapter';

Expand Down Expand Up @@ -398,7 +398,7 @@ export default class PostgresqlAdapter extends AbstractAdapter {
params,
})).rows[0];
if (constraintResult.constraint.length > 0) {
createTable += `\n${constraintResult.constraint}`;
createTable += `\n${constraintResult.constraint};`;
}
return [createTable];
}
Expand Down Expand Up @@ -430,7 +430,7 @@ export default class PostgresqlAdapter extends AbstractAdapter {
WHERE proname = $1
AND n.nspname = $2
`;
mapFunction = (row: {[column: string]: unknown}): string => row.pg_get_functiondef as string;
mapFunction = (row: {[column: string]: unknown}): string => appendSemiColon(row.pg_get_functiondef as string);
} else {
// -- pg_catalog.array_to_string(p.proacl, '\n') AS "Access privileges",
sql = `
Expand Down Expand Up @@ -477,7 +477,7 @@ export default class PostgresqlAdapter extends AbstractAdapter {
(val: string, idx: number) => `${(row.proargnames as string[])[idx]} ${val}`
).join(', ');
}
return `CREATE OR REPLACE FUNCTION ${row.nspname as string}.${row.proname as string}(${args})\n RETURNS ${row.prorettype as string} AS $$${row.prosrc as string}$$ LANGUAGE ${row.lanname as string} ${row.provolatility as string}`;
return `CREATE OR REPLACE FUNCTION ${row.nspname as string}.${row.proname as string}(${args})\n RETURNS ${row.prorettype as string} AS $$${row.prosrc as string}$$ LANGUAGE ${row.lanname as string} ${row.provolatility as string};`;
};
}

Expand Down
5 changes: 3 additions & 2 deletions src/adapters/sqlite.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import sqlite3 from 'sqlite3';
import { identify, Result } from 'sql-query-identifier';

import createLogger from '../logger';
import { appendSemiColon } from '../utils';
import { Adapter, ADAPTERS } from './';
import { AbstractAdapter } from './abstract_adapter';

Expand Down Expand Up @@ -187,7 +188,7 @@ export default class SqliteAdapter extends AbstractAdapter {

const { data } = <QueryResult>await this.driverExecuteQuery({ query: sql });

return (<{sql: string}[]>data).map((row) => row.sql);
return (<{sql: string}[]>data).map((row) => appendSemiColon(row.sql));
}

async getViewCreateScript(view: string): Promise<string[]> {
Expand All @@ -199,7 +200,7 @@ export default class SqliteAdapter extends AbstractAdapter {

const { data } = <QueryResult>await this.driverExecuteQuery({ query: sql });

return (<{sql: string}[]>data).map((row) => row.sql);
return (<{sql: string}[]>data).map((row) => appendSemiColon(row.sql));
}

async truncateAllTables(): Promise<void> {
Expand Down
10 changes: 5 additions & 5 deletions src/adapters/sqlserver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { ConnectionPool } from 'mssql';

import { buildDatabaseFilter, buildSchemaFilter } from '../filters';
import createLogger from '../logger';
import { identifyCommands } from '../utils';
import { identifyCommands, appendSemiColon } from '../utils';
import { AbstractAdapter, QueryArgs, QueryRowResult } from './abstract_adapter';

import type { config, Request, IResult, IRecordSet } from 'mssql';
Expand Down Expand Up @@ -298,11 +298,11 @@ export default class SqlServerAdapter extends AbstractAdapter {
const sql = `
SELECT ('CREATE TABLE ' + so.name + ' (' +
CHAR(13)+CHAR(10) + REPLACE(o.list, '&#x0D;', CHAR(13)) +
')' + CHAR(13)+CHAR(10) +
');' + CHAR(13)+CHAR(10) +
CASE WHEN tc.constraint_name IS NULL THEN ''
ELSE + CHAR(13)+CHAR(10) + 'ALTER TABLE ' + so.Name +
' ADD CONSTRAINT ' + tc.constraint_name +
' PRIMARY KEY ' + '(' + LEFT(j.list, Len(j.list)-1) + ')'
' PRIMARY KEY ' + '(' + LEFT(j.list, Len(j.list)-1) + ');'
END) AS createtable
FROM sysobjects so
CROSS APPLY
Expand Down Expand Up @@ -369,7 +369,7 @@ export default class SqlServerAdapter extends AbstractAdapter {

const { data } = await this.driverExecuteSingleQuery<{ViewDefinition: string}>({ query: sql });

return data.map((row) => row.ViewDefinition);
return data.map((row) => appendSemiColon(row.ViewDefinition));
}

async getRoutineCreateScript(routine: string): Promise<string[]> {
Expand All @@ -381,7 +381,7 @@ export default class SqlServerAdapter extends AbstractAdapter {

const { data } = await this.driverExecuteSingleQuery<{routine_definition: string}>({ query: sql });

return data.map((row) => row.routine_definition);
return data.map((row) => appendSemiColon(row.routine_definition));
}

async truncateAllTables(): Promise<void> {
Expand Down
8 changes: 8 additions & 0 deletions src/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -94,3 +94,11 @@ export function identifyCommands(queryText: string): Result[] {
return [];
}
}

export function appendSemiColon(query: string): string {
let result = query.trim()
if (result[result.length - 1] !== ';') {
result += ';';
}
return result;
}

0 comments on commit 39cc562

Please sign in to comment.