spliter options

This commit is contained in:
Jan Prochazka
2021-05-31 20:05:04 +02:00
parent eb78481d70
commit e480e08e0e
3 changed files with 65 additions and 20 deletions

View File

@@ -0,0 +1,35 @@
export interface SplitterOptions {
allowBacktickString: boolean;
allowIndexParenString: boolean;
allowSemicolon: boolean;
allowCustomDelimiter: boolean;
allowGoDelimiter: boolean;
allowDollarDollarString: boolean;
}
export const defaultSplitterOptions = {
allowBacktickString: false,
allowSemicolon: true,
allowCustomDelimiter: false,
allowGoDelimiter: false,
allowDollarDollarString: false,
allowIndexParenString: false,
};
export const mysqlSplitterOptions = {
...defaultSplitterOptions,
allowCustomDelimiter: true,
allowBacktickString: true,
};
export const mssqlSplitterOptions = {
...defaultSplitterOptions,
allowSemicolon: false,
allowGoDelimiter: true,
allowIndexParenString: true,
};
export const postgreSplitterOptions = {
...defaultSplitterOptions,
allowDollarDollarString: true,
};

View File

@@ -1,3 +1,5 @@
import { SplitterOptions, defaultSplitterOptions } from './options';
const SINGLE_QUOTE = "'"; const SINGLE_QUOTE = "'";
const DOUBLE_QUOTE = '"'; const DOUBLE_QUOTE = '"';
const BACKTICK = '`'; const BACKTICK = '`';
@@ -8,14 +10,13 @@ const SEMICOLON = ';';
const LINE_FEED = '\n'; const LINE_FEED = '\n';
const DELIMITER_KEYWORD = 'DELIMITER'; const DELIMITER_KEYWORD = 'DELIMITER';
export interface SplitQueryOptions {}
interface SplitExecutionContext { interface SplitExecutionContext {
options: SplitQueryOptions; options: SplitterOptions;
unread: string; unread: string;
currentDelimiter: string; currentDelimiter: string;
currentStatement: string; currentStatement: string;
output: string[]; output: string[];
semicolonKeyTokenRegex: RegExp;
} }
interface FindExpResult { interface FindExpResult {
@@ -35,7 +36,7 @@ const newLineRegex = /(?:[\r\n]+|$)/;
const delimiterStartRegex = /(?:^|[\n\r]+)[ \f\t\v]*DELIMITER[ \t]+/i; const delimiterStartRegex = /(?:^|[\n\r]+)[ \f\t\v]*DELIMITER[ \t]+/i;
// Best effort only, unable to find a syntax specification on delimiter // Best effort only, unable to find a syntax specification on delimiter
const delimiterTokenRegex = /^(?:'(.+)'|"(.+)"|`(.+)`|([^\s]+))/; const delimiterTokenRegex = /^(?:'(.+)'|"(.+)"|`(.+)`|([^\s]+))/;
const semicolonKeyTokenRegex = buildKeyTokenRegex(SEMICOLON); // const semicolonKeyTokenRegex = buildKeyTokenRegex(SEMICOLON);
const quoteEndRegexDict: Record<string, RegExp> = { const quoteEndRegexDict: Record<string, RegExp> = {
[SINGLE_QUOTE]: singleQuoteStringEndRegex, [SINGLE_QUOTE]: singleQuoteStringEndRegex,
[DOUBLE_QUOTE]: doubleQuoteStringEndRegex, [DOUBLE_QUOTE]: doubleQuoteStringEndRegex,
@@ -46,19 +47,22 @@ function escapeRegex(value: string): string {
return value.replace(regexEscapeSetRegex, '\\$&'); return value.replace(regexEscapeSetRegex, '\\$&');
} }
function buildKeyTokenRegex(delimiter: string): RegExp { function buildKeyTokenRegex(delimiter: string, context: SplitExecutionContext): RegExp {
const { options } = context;
return new RegExp( return new RegExp(
'(?:' + '(?:' +
[ [
escapeRegex(delimiter), escapeRegex(delimiter),
SINGLE_QUOTE, SINGLE_QUOTE,
DOUBLE_QUOTE, DOUBLE_QUOTE,
BACKTICK, options.allowBacktickString ? BACKTICK : undefined,
doubleDashCommentStartRegex.source, doubleDashCommentStartRegex.source,
HASH_COMMENT_START, HASH_COMMENT_START,
cStyleCommentStartRegex.source, cStyleCommentStartRegex.source,
delimiterStartRegex.source, options.allowCustomDelimiter ? delimiterStartRegex.source : undefined,
].join('|') + ]
.filter(x => x !== undefined)
.join('|') +
')', ')',
'i' 'i'
); );
@@ -83,12 +87,12 @@ function findExp(content: string, regex: RegExp): FindExpResult {
return result; return result;
} }
function findKeyToken(content: string, currentDelimiter: string): FindExpResult { function findKeyToken(content: string, currentDelimiter: string, context: SplitExecutionContext): FindExpResult {
let regex; let regex;
if (currentDelimiter === SEMICOLON) { if (currentDelimiter === SEMICOLON) {
regex = semicolonKeyTokenRegex; regex = context.semicolonKeyTokenRegex;
} else { } else {
regex = buildKeyTokenRegex(currentDelimiter); regex = buildKeyTokenRegex(currentDelimiter, context);
} }
return findExp(content, regex); return findExp(content, regex);
} }
@@ -187,14 +191,19 @@ function handleKeyTokenFindResult(context: SplitExecutionContext, findResult: Fi
} }
} }
export function splitQuery(sql: string, options: SplitQueryOptions = {}): string[] { export function splitQuery(sql: string, options: SplitterOptions = null): string[] {
const context: SplitExecutionContext = { const context: SplitExecutionContext = {
unread: sql, unread: sql,
currentDelimiter: SEMICOLON, currentDelimiter: SEMICOLON,
currentStatement: '', currentStatement: '',
output: [], output: [],
options, semicolonKeyTokenRegex: null,
options: {
...defaultSplitterOptions,
...options,
},
}; };
context.semicolonKeyTokenRegex = buildKeyTokenRegex(SEMICOLON, context);
let findResult: FindExpResult = { let findResult: FindExpResult = {
expIndex: -1, expIndex: -1,
exp: null, exp: null,
@@ -204,7 +213,7 @@ export function splitQuery(sql: string, options: SplitQueryOptions = {}): string
do { do {
// console.log('context.unread', context.unread); // console.log('context.unread', context.unread);
lastUnreadLength = context.unread.length; lastUnreadLength = context.unread.length;
findResult = findKeyToken(context.unread, context.currentDelimiter); findResult = findKeyToken(context.unread, context.currentDelimiter, context);
handleKeyTokenFindResult(context, findResult); handleKeyTokenFindResult(context, findResult);
// Prevent infinite loop by returning incorrect result // Prevent infinite loop by returning incorrect result
if (lastUnreadLength === context.unread.length) { if (lastUnreadLength === context.unread.length) {

View File

@@ -1,3 +1,4 @@
import { mysqlSplitterOptions } from './options';
import { splitQuery } from './splitQuery'; import { splitQuery } from './splitQuery';
test('simple query', () => { test('simple query', () => {
@@ -6,28 +7,28 @@ test('simple query', () => {
}); });
test('correct split 2 queries', () => { test('correct split 2 queries', () => {
const output = splitQuery('SELECT * FROM `table1`;SELECT * FROM `table2`;'); const output = splitQuery('SELECT * FROM `table1`;SELECT * FROM `table2`;', mysqlSplitterOptions);
expect(output).toEqual(['SELECT * FROM `table1`', 'SELECT * FROM `table2`']); expect(output).toEqual(['SELECT * FROM `table1`', 'SELECT * FROM `table2`']);
}); });
test('correct split 2 queries - no end semicolon', () => { test('correct split 2 queries - no end semicolon', () => {
const output = splitQuery('SELECT * FROM `table1`;SELECT * FROM `table2`'); const output = splitQuery('SELECT * FROM `table1`;SELECT * FROM `table2`', mysqlSplitterOptions);
expect(output).toEqual(['SELECT * FROM `table1`', 'SELECT * FROM `table2`']); expect(output).toEqual(['SELECT * FROM `table1`', 'SELECT * FROM `table2`']);
}); });
test('delete empty query', () => { test('delete empty query', () => {
const output = splitQuery(';;;\n;;SELECT * FROM `table1`;;;;;SELECT * FROM `table2`;;; ;;;'); const output = splitQuery(';;;\n;;SELECT * FROM `table1`;;;;;SELECT * FROM `table2`;;; ;;;', mysqlSplitterOptions);
expect(output).toEqual(['SELECT * FROM `table1`', 'SELECT * FROM `table2`']); expect(output).toEqual(['SELECT * FROM `table1`', 'SELECT * FROM `table2`']);
}); });
test('should handle double backtick', () => { test('should handle double backtick', () => {
const input = ['CREATE TABLE `a``b` (`c"d` INT)', 'CREATE TABLE `a````b` (`c"d` INT)']; const input = ['CREATE TABLE `a``b` (`c"d` INT)', 'CREATE TABLE `a````b` (`c"d` INT)'];
const output = splitQuery(input.join(';\n') + ';'); const output = splitQuery(input.join(';\n') + ';', mysqlSplitterOptions);
expect(output).toEqual(input); expect(output).toEqual(input);
}); });
test('semicolon inside string', () => { test('semicolon inside string', () => {
const input = ['CREATE TABLE [a1]', "INSERT INTO [a1] (x) VALUES ('1;2;3;4')"]; const input = ['CREATE TABLE [a;1]', "INSERT INTO [a;1] (x) VALUES ('1;2;3;4')"];
const output = splitQuery(input.join(';\n') + ';'); const output = splitQuery(input.join(';\n') + ';', mysqlSplitterOptions);
expect(output).toEqual(input); expect(output).toEqual(input);
}); });