query splitter can return positions and line and column numbers

This commit is contained in:
Jan Prochazka
2021-12-12 08:30:41 +01:00
parent f1e35689bb
commit f59ac66e78
4 changed files with 207 additions and 18 deletions

View File

@@ -11,6 +11,8 @@ export interface SplitterOptions {
doubleDashComments: boolean; doubleDashComments: boolean;
multilineComments: boolean; multilineComments: boolean;
javaScriptComments: boolean; javaScriptComments: boolean;
returnRichInfo: boolean;
} }
export const defaultSplitterOptions: SplitterOptions = { export const defaultSplitterOptions: SplitterOptions = {
@@ -27,6 +29,8 @@ export const defaultSplitterOptions: SplitterOptions = {
doubleDashComments: true, doubleDashComments: true,
multilineComments: true, multilineComments: true,
javaScriptComments: false, javaScriptComments: false,
returnRichInfo: false,
}; };
export const mysqlSplitterOptions: SplitterOptions = { export const mysqlSplitterOptions: SplitterOptions = {

View File

@@ -5,8 +5,16 @@ const SEMICOLON = ';';
export interface SplitStreamContext { export interface SplitStreamContext {
options: SplitterOptions; options: SplitterOptions;
currentDelimiter: string; currentDelimiter: string;
pushOutput: (sql: string) => void; pushOutput: (item: SplitResultItem) => void;
commandPart: string; commandPart: string;
line: number;
column: number;
streamPosition: number;
commandStartPosition: number;
commandStartLine: number;
commandStartColumn: number;
} }
export interface SplitLineContext extends SplitStreamContext { export interface SplitLineContext extends SplitStreamContext {
@@ -22,6 +30,41 @@ export interface SplitLineContext extends SplitStreamContext {
// semicolonKeyTokenRegex: RegExp; // semicolonKeyTokenRegex: RegExp;
} }
export type SplitResultItem =
| string
| {
text: string;
startPosition: number;
endPosition: number;
startLine: number;
startColumn: number;
endLine: number;
endColumn: number;
};
function movePosition(context: SplitLineContext, count: number) {
if (context.options.returnRichInfo) {
let { source, position, line, column, streamPosition } = context;
while (count > 0) {
if (source[position] == '\n') {
line += 1;
column = 0;
} else {
column += 1;
}
position += 1;
streamPosition += 1;
count -= 1;
}
context.position = position;
context.streamPosition = streamPosition;
context.line = line;
context.column = column;
} else {
context.position += count;
}
}
function isStringEnd(s: string, pos: number, endch: string, escapech: string) { function isStringEnd(s: string, pos: number, endch: string, escapech: string) {
if (!escapech) { if (!escapech) {
return s[pos] == endch; return s[pos] == endch;
@@ -163,7 +206,31 @@ function scanToken(context: SplitLineContext): Token {
function pushQuery(context: SplitLineContext) { function pushQuery(context: SplitLineContext) {
const sql = (context.commandPart || '') + context.source.slice(context.currentCommandStart, context.position); const sql = (context.commandPart || '') + context.source.slice(context.currentCommandStart, context.position);
const trimmed = sql.trim(); const trimmed = sql.trim();
if (trimmed) context.pushOutput(trimmed); if (trimmed) {
if (context.options.returnRichInfo) {
context.pushOutput({
text: trimmed,
startPosition: context.commandStartPosition,
startLine: context.commandStartLine,
startColumn: context.commandStartColumn,
endPosition: context.streamPosition,
endLine: context.line,
endColumn: context.column,
});
} else {
context.pushOutput(trimmed);
}
}
}
function markStartCommand(context: SplitLineContext) {
if (context.options.returnRichInfo) {
context.commandStartPosition = context.streamPosition;
context.commandStartLine = context.line;
context.commandStartColumn = context.column;
}
} }
export function splitQueryLine(context: SplitLineContext) { export function splitQueryLine(context: SplitLineContext) {
@@ -171,47 +238,50 @@ export function splitQueryLine(context: SplitLineContext) {
const token = scanToken(context); const token = scanToken(context);
if (!token) { if (!token) {
// nothing special, move forward // nothing special, move forward
context.position += 1; movePosition(context, 1);
continue; continue;
} }
switch (token.type) { switch (token.type) {
case 'string': case 'string':
context.position += token.length; movePosition(context, token.length);
context.wasDataOnLine = true; context.wasDataOnLine = true;
break; break;
case 'comment': case 'comment':
context.position += token.length; movePosition(context, token.length);
context.wasDataOnLine = true; context.wasDataOnLine = true;
break; break;
case 'eoln': case 'eoln':
context.position += token.length; movePosition(context, token.length);
context.wasDataOnLine = false; context.wasDataOnLine = false;
break; break;
case 'data': case 'data':
context.position += token.length; movePosition(context, token.length);
context.wasDataOnLine = true; context.wasDataOnLine = true;
break; break;
case 'whitespace': case 'whitespace':
context.position += token.length; movePosition(context, token.length);
break; break;
case 'set_delimiter': case 'set_delimiter':
pushQuery(context); pushQuery(context);
context.commandPart = ''; context.commandPart = '';
context.currentDelimiter = token.value; context.currentDelimiter = token.value;
context.position += token.length; movePosition(context, token.length);
context.currentCommandStart = context.position; context.currentCommandStart = context.position;
markStartCommand(context);
break; break;
case 'go_delimiter': case 'go_delimiter':
pushQuery(context); pushQuery(context);
context.commandPart = ''; context.commandPart = '';
context.position += token.length; movePosition(context, token.length);
context.currentCommandStart = context.position; context.currentCommandStart = context.position;
markStartCommand(context);
break; break;
case 'delimiter': case 'delimiter':
pushQuery(context); pushQuery(context);
context.commandPart = ''; context.commandPart = '';
context.position += token.length; movePosition(context, token.length);
context.currentCommandStart = context.position; context.currentCommandStart = context.position;
markStartCommand(context);
break; break;
} }
} }
@@ -224,13 +294,47 @@ export function splitQueryLine(context: SplitLineContext) {
export function getInitialDelimiter(options: SplitterOptions) { export function getInitialDelimiter(options: SplitterOptions) {
return options?.allowSemicolon === false ? null : SEMICOLON; return options?.allowSemicolon === false ? null : SEMICOLON;
} }
export function splitQuery(sql: string, options: SplitterOptions = null): string[] {
export function finishSplitStream(context: SplitStreamContext) {
const trimmed = context.commandPart.trim();
if (trimmed) {
if (context.options.returnRichInfo) {
context.pushOutput({
text: trimmed,
startPosition: context.commandStartPosition,
startLine: context.commandStartLine,
startColumn: context.commandStartColumn,
endPosition: context.streamPosition,
endLine: context.line,
endColumn: context.column,
});
} else {
context.pushOutput(trimmed);
}
}
}
export function splitQuery(sql: string, options: SplitterOptions = null): SplitResultItem[] {
const usedOptions = { const usedOptions = {
...defaultSplitterOptions, ...defaultSplitterOptions,
...options, ...options,
}; };
if (usedOptions.noSplit) { if (usedOptions.noSplit) {
if (usedOptions.returnRichInfo) {
const lines = sql.split('\n');
return [
{
text: sql,
startLine: 0,
startPosition: 0,
startColumn: 0,
endLine: lines.length,
endColumn: lines[lines.length - 1]?.length || 0,
endPosition: sql.length,
},
];
}
return [sql]; return [sql];
} }
@@ -240,7 +344,13 @@ export function splitQuery(sql: string, options: SplitterOptions = null): string
end: sql.length, end: sql.length,
currentDelimiter: getInitialDelimiter(options), currentDelimiter: getInitialDelimiter(options),
position: 0, position: 0,
column: 0,
line: 0,
currentCommandStart: 0, currentCommandStart: 0,
commandStartLine: 0,
commandStartColumn: 0,
commandStartPosition: 0,
streamPosition: 0,
pushOutput: cmd => output.push(cmd), pushOutput: cmd => output.push(cmd),
wasDataOnLine: false, wasDataOnLine: false,
options: usedOptions, options: usedOptions,
@@ -248,9 +358,7 @@ export function splitQuery(sql: string, options: SplitterOptions = null): string
}; };
splitQueryLine(context); splitQueryLine(context);
finishSplitStream(context);
const trimmed = context.commandPart.trim();
if (trimmed) context.pushOutput(trimmed);
return output; return output;
} }

View File

@@ -1,5 +1,11 @@
import stream from 'stream'; import stream from 'stream';
import { SplitStreamContext, getInitialDelimiter, SplitLineContext, splitQueryLine } from './splitQuery'; import {
SplitStreamContext,
getInitialDelimiter,
SplitLineContext,
splitQueryLine,
finishSplitStream,
} from './splitQuery';
import { SplitterOptions } from './options'; import { SplitterOptions } from './options';
export class SplitQueryStream extends stream.Transform { export class SplitQueryStream extends stream.Transform {
@@ -9,6 +15,12 @@ export class SplitQueryStream extends stream.Transform {
super({ objectMode: true }); super({ objectMode: true });
this.context = { this.context = {
commandPart: '', commandPart: '',
commandStartLine: 0,
commandStartColumn: 0,
commandStartPosition: 0,
streamPosition: 0,
line: 0,
column: 0,
options, options,
currentDelimiter: getInitialDelimiter(options), currentDelimiter: getInitialDelimiter(options),
pushOutput: cmd => this.push(cmd), pushOutput: cmd => this.push(cmd),
@@ -28,8 +40,7 @@ export class SplitQueryStream extends stream.Transform {
done(); done();
} }
_flush(done) { _flush(done) {
const trimmed = this.context.commandPart; finishSplitStream(this.context);
if (trimmed) this.push(trimmed);
done(); done();
} }
} }

View File

@@ -89,3 +89,69 @@ test('split mongo', () => {
const output = splitQuery(input, mongoSplitterOptions); const output = splitQuery(input, mongoSplitterOptions);
expect(output).toEqual(['db.collection.insert({x:1})', 'db.collection.insert({y:2})']); expect(output).toEqual(['db.collection.insert({x:1})', 'db.collection.insert({y:2})']);
}); });
test('count lines', () => {
const output = splitQuery('SELECT * FROM `table1`;\nSELECT * FROM `table2`;', {
...mysqlSplitterOptions,
returnRichInfo: true,
});
expect(output).toEqual(
expect.arrayContaining([
expect.objectContaining({
text: 'SELECT * FROM `table1`',
startPosition: 0,
startLine: 0,
startColumn: 0,
endPosition: 22,
endLine: 0,
endColumn: 22,
}),
expect.objectContaining({
text: 'SELECT * FROM `table2`',
startPosition: 23,
startLine: 0,
startColumn: 23,
endPosition: 46,
endLine: 1,
endColumn: 22,
}),
])
);
});
test('count lines with flush', () => {
const output = splitQuery('SELECT * FROM `table1`;\nSELECT * FROM `table2`', {
...mysqlSplitterOptions,
returnRichInfo: true,
});
expect(output).toEqual(
expect.arrayContaining([
expect.objectContaining({
text: 'SELECT * FROM `table1`',
startPosition: 0,
startLine: 0,
startColumn: 0,
endPosition: 22,
endLine: 0,
endColumn: 22,
}),
expect.objectContaining({
text: 'SELECT * FROM `table2`',
startPosition: 23,
startLine: 0,
startColumn: 23,
endPosition: 46,
endLine: 1,
endColumn: 22,
}),
])
);
});