Skip to content
New issue

Have a question about this project? # for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “#”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? # to your account

如何根据SQL语句上下文得知别名与实体的关系 #151

Closed
resetsix opened this issue Oct 17, 2024 · 11 comments
Closed

如何根据SQL语句上下文得知别名与实体的关系 #151

resetsix opened this issue Oct 17, 2024 · 11 comments
Labels
question Further information is requested

Comments

@resetsix
Copy link
Contributor

Issue 区分

自动补全方式

实现自动补全的方式有两种:

  1. 前端静态补全:如使用 monaco-sql-languages
  2. 后端动态补全:如使用 LSPLanguage Server Protocol,语言服务器协议)。

核心原理

通过预测用户意图并提供可能的输入选项,基于已输入的内容和上下文信息,推断用户可能想要输入的内容。

实现步骤

  1. 词法分析(Lexical Analysis)

    • 操作:将输入的文本分解为标记(tokens)。
    • 目标:识别出关键字、标识符、运算符等。
  2. 语法分析(Syntactic Analysis)

    • 操作:基于词法分析结果,理解代码结构,构建语法树。
    • 目标:确定当前输入在语法树中的位置。
  3. 语义分析(Semantic Analysis)

    • 操作:理解代码的语义,分析变量作用域、类型信息等。
    • 目标:推断出当前光标位置应该提示什么类型的数据。
  4. 上下文感知(Context Awareness)

    • 操作:结合当前光标位置,分析上下文代码结构及之前的输入。
    • 目标:理解当前环境,确保提示项符合上下文。
  5. 候选项生成(Candidate Generation)

    • 操作:根据词法、语法、语义分析生成可能的补全选项。
    • 目标:生成如关键字、变量名、函数名等补全内容。
  6. 排序和过滤(Sorting & Filtering)

    • 操作:根据上下文的相关性和历史使用频率,对候选项进行排序。
    • 目标:过滤掉不符合当前上下文的无关项,并优化排序。

实现方法

  • 静态分析:基于预定义的语言规则和规范生成补全项。
  • 动态分析:考虑运行时信息和项目特定的上下文(如自定义函数、表别名等)。

数据来源

  • 语言规范:关键字、内置函数等基础补全内容。
  • 项目代码:如自定义别名、函数、变量等。
  • 上下文:用户已输入的代码内容,如库、表等。

AST 与动态编辑的局限

AST(抽象语法树)虽然可以帮助解析 SQL 语句的结构,但它只适合静态分析的场景。

在动态编辑环境中,SQL 语句往往不完整或存在语法错误,此时生成 AST 可能会影响性能。因此AST并不适用于实时编辑器的输入场景。

LSP 的优势

LSP更加适合动态编辑环境,它能够持续分析不完整或有语法错误的代码,并提供自动补全、跳转到定义等功能。通过增量更新可以提高性能,适合实时代码编辑和补全需求。

前端实现表别名与表名映射(重点)

虽然使用LSP有颇多好处,但十分依赖后端服务。

如果站在前端角度来看待自动补全别名处理(这也是monaco-sql-languages正在实现的愿景),可能需要如下步骤:

  1. 使用正则表达式识别 AS 语句或类似别名定义的语句。(无法精准匹配,因为 AS 关键字可以省略)
  2. 维护一个表别名与表名的映射表,记录所有别名和其对应的表名。
  3. 实时更新这个映射表,在用户输入 SQL 语句时,结合光标位置和上下文分析表别名。
  4. Select u. from user AS u语句中假设用户正在输入u.,那么通过查询映射表找到对应的表名(如 user),并提供列的自动补全。

别名处理会更加相对复杂,因为除了表可以作为别名还有字段、计算函数、子查询等等。

目前在前端处理别名映射我并没有想到一个完美解决方案和思路,例如AS关键字省略怎么办?

@openai0229
Copy link

lsp不是一个交互协议而已吗?为什么lsp能持续分析不完整或有语法错误的代码?

@resetsix
Copy link
Contributor Author

lsp不是一个交互协议而已吗?为什么lsp能持续分析不完整或有语法错误的代码?

LSP 本身确实只是一个协议,但它实际是作为连接编辑器和语言服务器的桥梁。真正进行代码分析的是语言服务器(Language Server) ,而不是协议本身。

举个具体例子:

前端 TypeScript        LSP协议        后端 Java(例如)
(Monaco Editor) <----------------> (SQL Language Server)
  1. 用户输入:

|代表光标位置。

SELECT * FROM u|
  1. 编辑器通过LSP发送请求:
textDocument/didChange: {
    "content": "SELECT * FROM u"
}
  1. Java实现的语言服务器接收并分析:
  • 识别这是一个不完整的 SQL 查询
  • 通过语法解析器(如 ANTLR)分析
  • 检查数据库表结构,发现可能的表名(user, users 等)
  1. 通过LSP返回结果:
  • 诊断信息(SQL语句未完成)
  • 可能的表名补全建议:
    • user
    • users
    • user_roles
    • ...

@openai0229
Copy link

要识别这种不完整的情况也很棘手,再加上在编辑器中那么多sql,又要准确获取到当前正在写的这段sql的上下文也麻烦

@JackWang032
Copy link
Collaborator

dt-sql-parser 现在已经支持了对别名、Comment等额外实体的收集,这些别名实体会作为额外信息标注在普通实体内,
select t1. from table1 as t1 可以拿到实体信息为
image
可以通过isContainCaret 判断这个实体是不是在当前光标输入所处的statement中,当然如果 SQL 未输入完全导致语法解析树存在错误,可能会有收集准确度的问题。

@resetsix
Copy link
Contributor Author

要识别这种不完整的情况也很棘手

常规步骤都是:获取所有文本,转换为抽象语法树,随后进行词法分析(Lexer)、语法分析(Parser)、遍历 AST 节点。接着才是实现高级功能如: SQL 校验、 自动补全、收集表名字段名。

再加上在编辑器中那么多sql,又要准确获取到当前正在写的这段sql的上下文也麻烦

当前位置的上下文 就需要在前面语法树的基础上获取用户光标位置,然后根据;等关键信息来标识。

目前我是这么实现,通过;来判断当前光标所在语句是否是独立语句。当然也可以根据Select等关键字来继续精准判断。

@liuxy0551
Copy link
Collaborator

要识别这种不完整的情况也很棘手

常规步骤都是:获取所有文本,转换为抽象语法树,随后进行词法分析(Lexer)、语法分析(Parser)、遍历 AST 节点。接着才是实现高级功能如: SQL 校验、 自动补全、收集表名字段名。

再加上在编辑器中那么多sql,又要准确获取到当前正在写的这段sql的上下文也麻烦

当前位置的上下文 就需要在前面语法树的基础上获取用户光标位置,然后根据;等关键信息来标识。

目前我是这么实现,通过;来判断当前光标所在语句是否是独立语句。当然也可以根据Select等关键字来继续精准判断。

; 会更准确一点,以 SELECT 等可以作为语句开头关键词判断容易出现子语句切分异常的问题,DTStack/dt-sql-parser#334 (comment) ,我们在开发中遇到过,所以建议以 ; 判断,不过这要求用户在语句结尾书写 ;

@resetsix
Copy link
Contributor Author

dt-sql-parser 现在已经支持了对别名、Comment等额外实体的收集,这些别名实体会作为额外信息标注在普通实体内

太赞了👍🏻!别名这个特性真的太常用了。

另外我有几个疑惑:

  1. 目前monaco-sql-languages也集成了收集别名新特性吗。
  2. as关键字省略的时候也可以收集到别名吗。
  3. 列别名,表别名和派生表别名都能够收集到吗。

@resetsix
Copy link
Contributor Author

; 会更准确一点,以 SELECT 等可以作为语句开头关键词判断容易出现子语句切分异常的问题,DTStack/dt-sql-parser#334 (comment) ,我们在开发中遇到过,所以建议以 ; 判断,不过这要求用户在语句结尾书写 ;

没毛病。

@JackWang032
Copy link
Collaborator

dt-sql-parser 现在已经支持了对别名、Comment等额外实体的收集,这些别名实体会作为额外信息标注在普通实体内

太赞了👍🏻!别名这个特性真的太常用了。

另外我有几个疑惑:

  1. 目前monaco-sql-languages也集成了收集别名新特性吗。
  2. as关键字省略的时候也可以收集到别名吗。
  3. 列别名,表别名和派生表别名都能够收集到吗。
  1. 4.x已经集成,通过completionService中第5个参数获取实体列表
  2. 可以收集到, sql-parser中并不是以 as 作为标识来识别别名的
  3. 目前只收集到了表别名和定义表、列的Comment, 派生别名目前还未收集

@apankun
Copy link

apankun commented Dec 19, 2024

你好,请问我要怎样收集到这个别名?
sql demo如下
SELECT od.id
FROM fakedb.faketb AS od
WHERE 1=1
LIMIT 1000;

@JackWang032
Copy link
Collaborator

@apankun 提供自定义completionService,第五个参数为entities,打印数据如下,然后可以自行处理补全
image

# for free to join this conversation on GitHub. Already have an account? # to comment
Labels
question Further information is requested
Projects
None yet
Development

No branches or pull requests

6 participants