mirror of
https://github.com/DeNNiiInc/dbgate.git
synced 2026-04-26 23:46:23 +00:00
spliter options
This commit is contained in:
35
packages/querysplitter/src/options.ts
Normal file
35
packages/querysplitter/src/options.ts
Normal 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,
|
||||||
|
};
|
||||||
@@ -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) {
|
||||||
|
|||||||
@@ -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);
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user