0.0.1-alpha.6:

1. 修改 `package.json` 中的项目名称和版本号。
2. 更新 `node_modules` 中的依赖包版本。
3. 修改 `src/index.ts` 中的 SQL 语句和 `caretColumn` 的计算方式。
4. 修改 `src/parse/default-rules.ts` 中的规则和导入语句。
5. 修改 `src/parse/visitor.ts` 中的访问者和规则。
6. 修改 `src/preprocess/default-preprocessor.ts` 中的预处理逻辑。
7. 修改 `src/types.ts` 中的导入语句。
This commit is contained in:
Kijin-Seija 2024-04-15 17:48:24 +08:00
parent d7f6568157
commit 645a73d6af
9 changed files with 109 additions and 122 deletions

View File

@ -12,7 +12,7 @@
<script type="module"> <script type="module">
const { DtSqlParserSemAnalysePlugin } = await import(/* @vite-ignore */import.meta.env.VITE_ENTRY_PATH) const { DtSqlParserSemAnalysePlugin } = await import(/* @vite-ignore */import.meta.env.VITE_ENTRY_PATH)
const myPlugin = new DtSqlParserSemAnalysePlugin() const myPlugin = new DtSqlParserSemAnalysePlugin()
const sql = 'SELECT a.b| FROM t' const sql = 'SELECT a.| AS c'
const caretColumn = sql.indexOf('|') + 1 const caretColumn = sql.indexOf('|') + 1
const result = myPlugin.parse(sql.replace('|', ''), { lineNumber: 1, columnNumber: caretColumn }) const result = myPlugin.parse(sql.replace('|', ''), { lineNumber: 1, columnNumber: caretColumn })
console.log(result) console.log(result)

45
package-lock.json generated
View File

@ -1,12 +1,14 @@
{ {
"name": "dt-sql-parser-analyse-demo", "name": "dt-sql-parser-semantic-analyse-plugin",
"version": "0.0.1-alpha.7",
"lockfileVersion": 3, "lockfileVersion": 3,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "dt-sql-parser-analyse-demo", "name": "dt-sql-parser-semantic-analyse-plugin",
"version": "0.0.1-alpha.6",
"dependencies": { "dependencies": {
"dt-sql-parser": "^4.0.0-beta.4.11" "dt-sql-parser": "^4.0.0-beta.4.12"
}, },
"devDependencies": { "devDependencies": {
"@eslint/eslintrc": "^3.0.2", "@eslint/eslintrc": "^3.0.2",
@ -1099,17 +1101,29 @@
} }
}, },
"node_modules/antlr4-c3": { "node_modules/antlr4-c3": {
"version": "3.1.1", "version": "3.3.7",
"resolved": "https://registry.npmjs.org/antlr4-c3/-/antlr4-c3-3.1.1.tgz", "resolved": "http://npm.oushu.com:14837/antlr4-c3/-/antlr4-c3-3.3.7.tgz",
"integrity": "sha512-S7DixV12kxWexTkQYGvooCgHYU5AjF74oYio+ZNgm0XN3EzxDY3J6Si9GprQ4KksvgWwK//EgZnL/26WB+bOpw==", "integrity": "sha512-F3ndE38wwA6z6AjUbL3heSdEGl4TxulGDPf9xB0/IY4dbRHWBh6XNaqFwur8vHKQk9FS5yNABHeg2wqlqIYO0w==",
"dependencies": { "dependencies": {
"antlr4ts": "0.5.0-alpha.4" "antlr4ng": "2.0.11"
} }
}, },
"node_modules/antlr4ts": { "node_modules/antlr4ng": {
"version": "0.5.0-alpha.4", "version": "2.0.11",
"resolved": "https://registry.npmjs.org/antlr4ts/-/antlr4ts-0.5.0-alpha.4.tgz", "resolved": "http://npm.oushu.com:14837/antlr4ng/-/antlr4ng-2.0.11.tgz",
"integrity": "sha512-WPQDt1B74OfPv/IMS2ekXAKkTZIHl88uMetg6q3OTqgFxZ/dxDXI0EWLyZid/1Pe6hTftyg5N7gel5wNAGxXyQ==" "integrity": "sha512-9jM91VVtHSqHkAHQsXHaoaiewFETMvUTI1/tXvwTiFw4f7zke3IGlwEyoKN9NS0FqIwDKFvUNW2e1cKPniTkVQ==",
"peerDependencies": {
"antlr4ng-cli": "1.0.7"
}
},
"node_modules/antlr4ng-cli": {
"version": "1.0.7",
"resolved": "http://npm.oushu.com:14837/antlr4ng-cli/-/antlr4ng-cli-1.0.7.tgz",
"integrity": "sha512-qN2FsDBmLvsQcA5CWTrPz8I8gNXeS1fgXBBhI78VyxBSBV/EJgqy8ks6IDTC9jyugpl40csCQ4sL5K4i2YZ/2w==",
"peer": true,
"bin": {
"antlr4ng": "index.js"
}
}, },
"node_modules/argparse": { "node_modules/argparse": {
"version": "2.0.1", "version": "2.0.1",
@ -1890,11 +1904,12 @@
} }
}, },
"node_modules/dt-sql-parser": { "node_modules/dt-sql-parser": {
"version": "4.0.0-beta.4.11", "version": "4.0.0-beta.4.12",
"integrity": "sha512-bgMJAMImikNwE0OPZApI+R+PjpI8xoJAksQIUJdtS4+piZII1LHzbmlp8T7iRJ4Fo56EwY5ILmv9VcZ/PwKV1A==", "resolved": "http://npm.oushu.com:14837/dt-sql-parser/-/dt-sql-parser-4.0.0-beta.4.12.tgz",
"integrity": "sha512-kfLRecn+dfdZjrKt3Ovm52ryoh9UGF+dCjNzV2pk8XI5kgF7lgQJhPZdRi+2yn1AU/BWQ1/0E6LnvFYbZ7Wr9Q==",
"dependencies": { "dependencies": {
"antlr4-c3": "3.1.1", "antlr4-c3": "3.3.7",
"antlr4ts": "0.5.0-alpha.4" "antlr4ng": "2.0.11"
} }
}, },
"node_modules/elliptic": { "node_modules/elliptic": {

View File

@ -37,6 +37,6 @@
"vite-plugin-node-polyfills": "^0.21.0" "vite-plugin-node-polyfills": "^0.21.0"
}, },
"dependencies": { "dependencies": {
"dt-sql-parser": "^4.0.0-beta.4.11" "dt-sql-parser": "^4.0.0-beta.4.12"
} }
} }

View File

@ -1,10 +1,10 @@
import { defaultEntities, defaultRules, defaultStmts } from './parse/default-rules' import { defaultAlias, defaultEntities, defaultRules, defaultStmts } from './parse/default-rules'
import { parse } from './parse' import { parse } from './parse'
import { preprocess } from './preprocess' import { preprocess } from './preprocess'
import { type PluginSettings, type InsertCaretPlaceholderConfig } from './types' import { type PluginSettings, type InsertCaretPlaceholderConfig } from './types'
import { defaultPreprocessorList } from './preprocess/default-preprocessor' import { defaultPreprocessorList } from './preprocess/default-preprocessor'
import { insertCaret } from './caret' import { insertCaret } from './caret'
import { PostgresSQL } from 'dt-sql-parser' import { PostgreSQL } from 'dt-sql-parser'
export class DtSqlParserSemAnalysePlugin { export class DtSqlParserSemAnalysePlugin {
private readonly settings: PluginSettings = {} private readonly settings: PluginSettings = {}
@ -18,10 +18,11 @@ export class DtSqlParserSemAnalysePlugin {
const sqlAfterPreprocess = preprocess(sqlAfterInsertCaret, this.settings.preprocessor || defaultPreprocessorList) const sqlAfterPreprocess = preprocess(sqlAfterInsertCaret, this.settings.preprocessor || defaultPreprocessorList)
const sqlParseResult = parse( const sqlParseResult = parse(
sqlAfterPreprocess, sqlAfterPreprocess,
this.settings.parse?.parser || new PostgresSQL(), this.settings.parse?.parser || new PostgreSQL(),
this.settings.parse?.stmts || defaultStmts, this.settings.parse?.stmts || defaultStmts,
this.settings.parse?.entities || defaultEntities, this.settings.parse?.entities || defaultEntities,
this.settings.parse?.rules || defaultRules this.settings.parse?.rules || defaultRules,
this.settings.parse?.alias || defaultAlias
) )
return sqlParseResult return sqlParseResult
} }

View File

@ -1,89 +1,39 @@
import { PostgreSQLParser } from 'dt-sql-parser/dist/lib/pgsql/PostgreSQLParser' import { PostgreSqlParser } from 'dt-sql-parser/dist/lib/postgresql/PostgreSqlParser'
export const defaultAlias = {
selectstmt: 'selectStatement',
target_el: 'target_label'
}
export const defaultStmts = [ export const defaultStmts = [
'stmt', 'simple_select',
// select statement
'selectstmt'
] ]
export const defaultEntities = [ export const defaultEntities = [
// column_name directly. ex: select column1 'target_el',
'column_name',
// column_name indirectly. ex: select schema.column1
'columnref',
'table_name',
'view_name',
'function_name',
'schema_name',
'colid', 'colid',
'attr_name', 'attr_name',
'collabel', 'collabel'
'func_arg_expr'
] ]
export const defaultRules: Record<string, number[]> = { export const defaultRules: Record<string, number[]> = {
// 通用的简单column规则不带.运算符的column) select_target: [
common_column_simple: [ PostgreSqlParser.RULE_simple_select,
PostgreSQLParser.RULE_stmt, PostgreSqlParser.RULE_target_el,
PostgreSQLParser.RULE_column_name
], ],
// 通用的复合column规则带.运算符的column) select_target_colid: [
common_column_ref: [ PostgreSqlParser.RULE_target_el,
PostgreSQLParser.RULE_stmt, PostgreSqlParser.RULE_function_name,
PostgreSQLParser.RULE_columnref PostgreSqlParser.RULE_colid
], ],
select_target_column_simple: [ select_target_attr: [
PostgreSQLParser.RULE_selectstmt, PostgreSqlParser.RULE_target_el,
PostgreSQLParser.RULE_select_clause, PostgreSqlParser.RULE_function_name,
PostgreSQLParser.RULE_target_list, PostgreSqlParser.RULE_attr_name
PostgreSQLParser.RULE_column_name
], ],
select_target_column_ref: [ select_target_alias: [
PostgreSQLParser.RULE_selectstmt, PostgreSqlParser.RULE_target_el,
PostgreSQLParser.RULE_select_clause, -PostgreSqlParser.RULE_attr_name,
PostgreSQLParser.RULE_target_list, PostgreSqlParser.RULE_collabel
PostgreSQLParser.RULE_columnref
],
select_target_function: [
PostgreSQLParser.RULE_selectstmt,
PostgreSQLParser.RULE_select_clause,
PostgreSQLParser.RULE_target_list,
PostgreSQLParser.RULE_function_name
],
select_column_alias: [
PostgreSQLParser.RULE_selectstmt,
PostgreSQLParser.RULE_select_clause,
PostgreSQLParser.RULE_target_list,
PostgreSQLParser.RULE_collabel
],
select_from_table: [
PostgreSQLParser.RULE_selectstmt,
PostgreSQLParser.RULE_select_clause,
PostgreSQLParser.RULE_from_clause,
PostgreSQLParser.RULE_table_name
],
select_from_view: [
PostgreSQLParser.RULE_selectstmt,
PostgreSQLParser.RULE_select_clause,
PostgreSQLParser.RULE_from_clause,
PostgreSQLParser.RULE_view_name
],
select_from_function: [
PostgreSQLParser.RULE_selectstmt,
PostgreSQLParser.RULE_select_clause,
PostgreSQLParser.RULE_from_clause,
PostgreSQLParser.RULE_function_name
],
column_ref_colid: [
PostgreSQLParser.RULE_columnref,
PostgreSQLParser.RULE_colid
],
column_ref_attr: [
PostgreSQLParser.RULE_columnref,
PostgreSQLParser.RULE_attr_name
],
function_arg_expr: [
PostgreSQLParser.RULE_function_name,
PostgreSQLParser.RULE_func_arg_expr
] ]
} }

View File

@ -1,18 +1,20 @@
import { PostgresSQL } from 'dt-sql-parser' import { PostgreSQL } from 'dt-sql-parser'
import { SQLVisitor } from './visitor' import { SQLVisitor } from './visitor'
import { type SQLParseResult } from '../types' import { type SQLParseResult } from '../types'
import type BasicParser from 'dt-sql-parser/dist/parser/common/basicParser' import { type BasicSQL } from 'dt-sql-parser/dist/parser/common/basicSQL'
export function parse ( export function parse (
sql: string, sql: string,
parser: BasicParser = new PostgresSQL(), parser: BasicSQL = new PostgreSQL(),
stmts: string[] = [], stmts: string[] = [],
entities: string[] = [], entities: string[] = [],
rules: Record<string, number[]> = {} rules: Record<string, number[]> = {},
alias: Record<string, string> = {},
): SQLParseResult { ): SQLParseResult {
const tree = parser.parse(sql) const tree = parser.parse(sql)
console.log('tree', tree) console.log('tree', tree)
const visitor = new SQLVisitor() const visitor = new SQLVisitor()
visitor.visitorAlias = alias
stmts.forEach(stmt => { visitor.addStmt(stmt) }) stmts.forEach(stmt => { visitor.addStmt(stmt) })
entities.forEach(entity => { visitor.addEntity(entity) }) entities.forEach(entity => { visitor.addEntity(entity) })
Object.keys(rules).forEach(name => { visitor.addRules(name, rules[name]) }) Object.keys(rules).forEach(name => { visitor.addRules(name, rules[name]) })

View File

@ -1,21 +1,24 @@
import { type ParserRuleContext } from 'antlr4ts' import { AbstractParseTreeVisitor, type RuleContext } from 'antlr4ng'
import { AbstractParseTreeVisitor, type PostgreSQLParserVisitor } from 'dt-sql-parser' import { type PostgreSqlParserVisitor } from 'dt-sql-parser'
import { type ProgramContext, PostgreSQLParser } from 'dt-sql-parser/dist/lib/pgsql/PostgreSQLParser' import { type ProgramContext, PostgreSqlParser } from 'dt-sql-parser/dist/lib/postgresql/PostgreSqlParser'
import { type Entity, type SQLParseResult, type Stmt } from '../types' import { type Entity, type SQLParseResult, type Stmt } from '../types'
import { caretPlaceholder } from '../caret' import { caretPlaceholder } from '../caret'
function withCaret (ctx: ParserRuleContext) { function toVisitorAlias (node: string, alias: Record<string, string>) {
return ctx.text.includes(caretPlaceholder) const result = alias[node] || node
return `visit${result.slice(0, 1).toUpperCase()}${result.slice(1)}`
} }
export class SQLVisitor extends AbstractParseTreeVisitor<void> implements PostgreSQLParserVisitor<void> { function withCaret (ctx: RuleContext) {
return ctx.getText().includes(caretPlaceholder)
}
export class SQLVisitor extends AbstractParseTreeVisitor<void> implements PostgreSqlParserVisitor<void> {
private result: SQLParseResult = { private result: SQLParseResult = {
stmtList: [], stmtList: [],
nerestCaretEntityList: [] nerestCaretEntityList: []
} }
protected defaultResult = () => ({ list: [], nerestCaret: null })
public clear () { public clear () {
this.result = { stmtList: [], nerestCaretEntityList: [] } this.result = { stmtList: [], nerestCaretEntityList: [] }
} }
@ -24,6 +27,8 @@ export class SQLVisitor extends AbstractParseTreeVisitor<void> implements Postgr
return this.result return this.result
} }
public visitorAlias = {}
private readonly stmtStack: Stmt[] = [] private readonly stmtStack: Stmt[] = []
private readonly entityStack: Entity[] = [] private readonly entityStack: Entity[] = []
@ -50,12 +55,12 @@ export class SQLVisitor extends AbstractParseTreeVisitor<void> implements Postgr
} }
public addEntity (name: string) { public addEntity (name: string) {
this.entityRules.set((PostgreSQLParser as any)[`RULE_${name}`], []) this.entityRules.set((PostgreSqlParser as any)[`RULE_${name}`], [])
let isHitRule = false let isHitRule = false
const visitorName = `visit${name.slice(0, 1).toUpperCase()}${name.slice(1)}` const visitorName = toVisitorAlias(name, this.visitorAlias)
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
const rules = this.entityRules.get((PostgreSQLParser as any)[`RULE_${name}`])!; const rules = this.entityRules.get((PostgreSqlParser as any)[`RULE_${name}`])!;
(this as any)[visitorName] = (ctx: ParserRuleContext) => { (this as any)[visitorName] = (ctx: RuleContext) => {
const chain = this.getNodeChain(ctx) const chain = this.getNodeChain(ctx)
for (const rule of rules) { for (const rule of rules) {
const ruleChain = this.rules.get(rule) const ruleChain = this.rules.get(rule)
@ -66,7 +71,7 @@ export class SQLVisitor extends AbstractParseTreeVisitor<void> implements Postgr
const beginEntity = this.entityStack.find(entity => entity.type === ruleChainBegin) const beginEntity = this.entityStack.find(entity => entity.type === ruleChainBegin)
const result: Entity = { const result: Entity = {
rule, rule,
text: ctx.text, text: ctx.getText(),
type: ctx.ruleIndex, type: ctx.ruleIndex,
caret: withCaret(ctx), caret: withCaret(ctx),
belongsToStmt: beginStmt || null, belongsToStmt: beginStmt || null,
@ -91,11 +96,11 @@ export class SQLVisitor extends AbstractParseTreeVisitor<void> implements Postgr
} }
public addStmt (name: string) { public addStmt (name: string) {
this.stmtRules.set((PostgreSQLParser as any)[`RULE_${name}`], []) this.stmtRules.set((PostgreSqlParser as any)[`RULE_${name}`], [])
const visitorName = `visit${name.slice(0, 1).toUpperCase()}${name.slice(1)}`; const visitorName = toVisitorAlias(name, this.visitorAlias);
(this as any)[visitorName] = (ctx: ParserRuleContext) => { (this as any)[visitorName] = (ctx: RuleContext) => {
this.stmtStack.push({ this.stmtStack.push({
text: ctx.text, text: ctx.getText(),
type: ctx.ruleIndex, type: ctx.ruleIndex,
caret: withCaret(ctx), caret: withCaret(ctx),
relatedEntities: {} relatedEntities: {}
@ -106,8 +111,8 @@ export class SQLVisitor extends AbstractParseTreeVisitor<void> implements Postgr
} }
} }
private getNodeChain (ctx: ParserRuleContext) { private getNodeChain (ctx: RuleContext) {
let _ctx: ParserRuleContext | undefined = ctx let _ctx: RuleContext | null = ctx
const result = [] const result = []
while (_ctx) { while (_ctx) {
result.unshift(_ctx.ruleIndex) result.unshift(_ctx.ruleIndex)
@ -118,10 +123,13 @@ export class SQLVisitor extends AbstractParseTreeVisitor<void> implements Postgr
private matchRules (chain: number[], ruleChain: number[] | undefined) { private matchRules (chain: number[], ruleChain: number[] | undefined) {
// 只要ruleChain里面每个元素都出现在chain里面且顺序一致则返回true。否则返回false // 只要ruleChain里面每个元素都出现在chain里面且顺序一致则返回true。否则返回false
// 当元素value为负数时表示NOT即不出现id为-value的规则。
if (!ruleChain) return false if (!ruleChain) return false
let index = 0 let index = 0
for (let i = 0; i < ruleChain.length; i++) { for (let i = 0; i < ruleChain.length; i++) {
if (chain.indexOf(ruleChain[i]) < index) return false if (ruleChain[i] < 0) {
if (chain.indexOf(-ruleChain[i]) >= index) return false
} else if (chain.indexOf(ruleChain[i]) < index) return false
else index = chain.indexOf(ruleChain[i]) else index = chain.indexOf(ruleChain[i])
} }
return true return true

View File

@ -1,5 +1,14 @@
import { type Preprocessor } from '../types' import { type Preprocessor } from '../types'
export const addSuffixForParseSelectProcessor: Preprocessor = (sql: string) => {
const suffix = ' '
const maxWords = 3
if (/select( )+/.test(sql.toLowerCase()) && sql.split(' ').filter(item => item).length < maxWords) {
return sql + '' + suffix
}
return sql
}
export const addSuffixForParseAlterFunctionProcessor: Preprocessor = (sql: string) => { export const addSuffixForParseAlterFunctionProcessor: Preprocessor = (sql: string) => {
const suffix = 'RESET ALL' const suffix = 'RESET ALL'
const maxWords = 4 const maxWords = 4
@ -20,5 +29,6 @@ export const addSuffixForParseAlterTableProcessor: Preprocessor = (sql: string)
export const defaultPreprocessorList = [ export const defaultPreprocessorList = [
addSuffixForParseAlterFunctionProcessor, addSuffixForParseAlterFunctionProcessor,
addSuffixForParseAlterTableProcessor addSuffixForParseAlterTableProcessor,
addSuffixForParseSelectProcessor
] ]

View File

@ -1,4 +1,4 @@
import type BasicParser from 'dt-sql-parser/dist/parser/common/basicParser' import { type BasicSQL } from 'dt-sql-parser/dist/parser/common/basicSQL'
export interface InsertCaretPlaceholderConfig { export interface InsertCaretPlaceholderConfig {
lineNumber: number lineNumber: number
@ -24,10 +24,11 @@ export interface PluginSettings {
* *
*/ */
parse?: { parse?: {
parser?: BasicParser parser?: BasicSQL
stmts?: string[] stmts?: string[]
entities?: string[] entities?: string[]
rules?: Record<string, number[]> rules?: Record<string, number[]>
alias?: Record<string, string>
} }
} }