DTStack / dt-sql-parser

SQL Parsers for BigData, built with antlr4.
https://dtstack.github.io/monaco-sql-languages/
MIT License
293 stars 93 forks source link

基于语法上下文补全? #189

Closed HaydenOrz closed 9 months ago

HaydenOrz commented 12 months ago

分析出当前sql内容中新创建的 catalog名/schema名/表名/视图名/函数名/字段名,用于自动补全

kissycn commented 11 months ago

Are there any support plans in the near future?

resetsix commented 11 months ago

I just came across this context completion requirement and would like to know if there is any intention to implement this feature in the near future or the approximate time to do so.

HaydenOrz commented 11 months ago

Are there any support plans in the near future?

We will implement this feature in December. We haven't started doing this yet because we are still rechecking the sql grammar files, which takes a lot of time.

HaydenOrz commented 11 months ago

I just came across this context completion requirement and would like to know if there is any intention to implement this feature in the near future or the approximate time to do so.

Which sql language do you need to implement this feature in? If you need implement this feature in one of the languages in flink,hive,spark or trino, we can support it in the near future

kissycn commented 11 months ago

Are there any support plans in the near future?

We will implement this feature in December. We haven't started doing this yet because we are still rechecking the sql grammar files, which takes a lot of time.

This is good news. Looking forward to it!

resetsix commented 11 months ago

I just came across this context completion requirement and would like to know if there is any intention to implement this feature in the near future or the approximate time to do so.

Which sql language do you need to implement this feature in? If you need implement this feature in one of the languages in flink,hive,spark or trino, we can support it in the near future

It's great to hear from you. All of those you mentioned above are in demand, even mysql, and it's really exciting to hear that new features are coming

zhi-zhi-zhi commented 10 months ago

上下文 之前有看到一个结合 Monaco editor 的一个思路:把 cursor(当前光标) 当做一个特殊的 token,用于通过语法匹配并提示当前光标处的补全

ex: select | from user; // '|' means cursor position 把 cursor 当成一个特殊的 token 通过 AST,此时 AST 正确,cursor token 充当了 select list 的作用 此时就可以提供提示了,提示这里应该输入 field,可通过 from 后面的上下文,来提示这里应该输入 user 这张表的 field

zhi-zhi-zhi commented 10 months ago

请问这一块需要社区支持吗? 我很感兴趣并且最近很有需要

resetsix commented 10 months ago

上下文 之前有看到一个结合 Monaco editor 的一个思路:把 cursor(当前光标) 当做一个特殊的 token,用于通过语法匹配并提示当前光标处的补全

ex: select | from user; // '|' means cursor position 把 cursor 当成一个特殊的 token 通过 AST,此时 AST 正确,cursor token 充当了 select list 的作用 此时就可以提供提示了,提示这里应该输入 field,可通过 from 后面的上下文,来提示这里应该输入 user 这张表的 field

How should monaco get the position of the cursor, please? I didn't find the api for it, but it does have this function

HaydenOrz commented 10 months ago

How should monaco get the position of the cursor, please? I didn't find the api for it, but it does have this function


// 获取 monaco-editor 实例
var editor = monaco.editor.create(document.getElementById('container'), {
value: 'function hello() {\n\talert("Hello, world!");\n}',
language: 'javascript'
});

// 获取光标位置 var position = editor.getPosition(); console.log(position);

HaydenOrz commented 10 months ago

把 cursor 当成一个特殊的 token 通过 AST,此时 AST 正确,cursor token 充当了 select list 的作用 此时就可以提供提示了,提示这里应该输入 field

@zhi-zhi-zhi 事实上这个功能已经支持了,目前 dt-sql-parser 的自动补全功能通过引入 antlr4-c3 实现。 你可以通过调用 parser. getSuggestionAtCaretPosition 来体验该功能,详情见自动补全功能文档。或者你可以通过自动补全功能的单元测试来查看如何使用, 自动补全功能的单元测试位于 test/parser/*/suggestion/*.test.ts.


请问这一块需要社区支持吗? 我很感兴趣并且最近很有需要

@zhi-zhi-zhi 非常欢迎来自社区的 PR。


但是在此之前,我们需要先讨论一下该功能,以保证该功能实现后符合我们的期望。

需要实现什么?

  1. 提示某个指定位置应该填写什么类型(类型是指如表,字段,函数等,下文中同),例:

    SELECT * FROM |

    | 代表光标位置,那么在此处进行自动补全时,dt-sql-parser 的自动补全方法应该提示用户,这里应该填一个表名。再次强调,目前此功能已经支持。

  2. 收集sql 上下文中出现的表名,字段名,函数名等,比如:

    
    CREATE TABLE tb1 (id int);

CREATE TABLE tb2 (id int);

CREATE TABLE tb3 (id int);

SELECT * FROM |


事实上,对于使用自动补全功能的用户来说,除了想要知道指定位置应该填什么类型,还需要知道该类型对应有哪些实体?比如此例中的光标位置,在此位置做自动补全功能时,除了需要知道该位置要填表名,还需要知道当前上下文中有哪些表名。 那么此时我们需要一个功能去收集sql文本上下文中出现的所有表名,在本例中应该收集到 `tableNames: [ 'tb1', 'tb2', 'tb3' ]`

一般情况下,在实际业务场景中,这些信息应该由数据库/计算引擎的元数据提供,但即使有元数据支持,此功能仍然具有意义,因为当前上下文中的 DDL 语句可能并未执行,也就是说当前上下文中可能包含元数据中不包含的信息。比如建表语句未执行前,元数据中没有该表的信息,但是我们可以在sql上下文中获取。

### 有哪些类型需要收集?
参照 `SyntaxContextType`,  其中包含的所有类型都需要收集。

### 注意事项
需要考虑多种情况,例如:
1. 先创建表,再通过 Alter Table 修改表:
```sql
CREATE TABLE tb1 (id int);

ALTER TABLE tb1 RENAME TO tb2;
  1. 字段别名/表别名
    CREATE TABLE tb (
    sum(age) as col  int
    )

    这里无法列举出全部情况,因为不同的sql类型的语法具有差异。

如何实现?

  1. 考虑通过 Anltr4 Visitor 或者 Antlr4 Listener 实现。
  2. 应该通过在抽象类 BasicParser 中新增一个独立成员方法,以对外暴露该功能。
Shu-Ji commented 10 months ago

一般情况下,在实际业务场景中,这些信息应该由数据库/计算引擎的元数据提供

我想通过后端接口获取到db.tb, tb.column补全列表;

如用户输入:

use a_db;
SELECT *
FROM a_table
INNER JOIN b_db.b_table AS  b ON a_table.<cursor_1> = b.<cursor_2>
WHERE <cursor_3>

在光标处,目前我获取到的补全上下文信息,只有以下信息:

{
    "syntaxContextType": "column",
    "wordRanges": [
        {
            "text": " ",
            "startIndex": 28,
            "stopIndex": 28,
            "line": 3,
            "startColumn": 6,
            "stopColumn": 6
        }
    ]
}

但是我无法请求后端得到当前应该输入的字段列表,因为后端无法得知需要什么表下面字段列表,也就是上下文信息中缺少更多信息,如果是以下这样,那就可以完成后端获取数据。

上述SQL中包含了,a_db.a_table以及 b_db.b_table,且b_table 有一个别名b.

对于 ,仅需要补全a_table下的字段:

{
    "syntaxContextType": "column",
    "wordRanges": [
        {
             // 其他属性省略
            "tables": [{"db": "a_db", "tb": "a_table"}]
        }
    ]
}

对于 ,仅需要补全b_table(别名b)下的字段:

{
    "syntaxContextType": "column",
    "wordRanges": [
        {
             // 其他属性省略
            "tables": [{"db": "b_db", "tb": "b_table"}]
        }
    ]
}

对于 ,需要补全a_table和b_table下的字段:

{
    "syntaxContextType": "column",
    "wordRanges": [
        {
             // 其他属性省略
            "tables": [{"db": "b_db", "tb": "b_table"}, {"db": "b_db", "tb": "b_table"}]
        }
    ]
}

还有一些更为复杂的场景:

如包含子查询和别名(b_table -> bbb -> b),其中最初的b_table是后端需要的真实的表名,且其来自于b_db。 bbb和b对后端无意义:

use a_db;
SELECT *
FROM a_table
INNER JOIN (
    SELECT * FROM b_db.b_table bbb LIMIT 100
)  b ON a_table.<cursor_1> = b.<cursor_2>
WHERE <cursor_3>

当然,除此之外,自动感知上下文中的库、表和字段也很重要。

HaydenOrz commented 10 months ago

@Shu-Ji emmm,你讲到了自动补全功能的重点也是目前自动补全功能的一大缺陷,我暂且将这些信息统称为上下文信息。

目前自动补全功能提供的上下文信息,是一个wordRange 数组,而在 wordRange 中,并没有提到该 word 是什么类型,这实际上是因为 dt-sql-parser 也无法确认对应的类型。

很多数据库/大数据引擎都具有三层结构,比如 Trino 有 catalog -> database -> table/view

表名的自动补全

当输入为:

SELECT * FROM a<cursor pos>

此时光标位置可能的输入类型有:catalogdatabasetableview,那么对于 dt-sq-parser 来说,a 就可能是catalogdatabasetable 或者view 的一部分 。

当输入为

SELECT * FROM a.b<cursor pos>

此时 a 就可能是 catalogdatabase, b 就可能是 databasetable 或者 view 的一部分。

这在理论上也似乎无法确认(最起码对我来说,没想到什么好办法),如果你有什么更好的点子可以告诉我们。

字段的自动补全

事实上,dt-sql-parser 能够获取到 wordRanges 数组是依靠位置,即对于表名的补全来说,所需要的信息在位置上一定与当前补全位置相邻。另外,wordRanges 数组中的 token 与补全位置的 token 一定在同一个 Antlr4.parserRuleContext 下。

字段的自动补全在此基础上变得复杂了起来,由于位置的关系,dt-sql-parser 无法直接感知到关于对应表名的上下文信息。

另外如你所说,这需要考虑很多复杂的情况,比如子查询,表别名,当然也包括 tb1 JOIN tb2 ON tb1.id = tb2.id 的情况。

这个功能相当重要,但是这都需要额外的开发,基于上下文的自动补全功能实际上需要语义分析的支持,Antlr4 本身则只支持词法分析和语法分析。又因为每一种 SQL 的语法各不相同,所以在做语义分析时,需要每一种 SQL 单独支持,即使实现起来并不困难(理论上),但这仍然需要大量的时间。

这个功能目前在我们的计划中,但是并不是最高优先级,最近的开发计划还是集中在支持更多类型的 SQL 上,比如 OracleStarRocksInceptor 。同时开发基于上下文的自动补全功能对目前的维护团队来说是难以承受的负担。因为这个工作量需要与SQL类型数量相乘。

另外,我们非常欢迎来自社区的 PR 或者说是需要社区的帮助。

HaydenOrz commented 10 months ago

@Shu-Ji
目前,我们计划先支持获取上下文中的库、表和字段,这相比于完整的语义分析来说简单许多,只需要通过 listener 监听特定节点即可。

此功能支持后,可能对字段的补全有所帮助。即你可以一次性获取上下文中出现的所有的表名,从后端接口中获取所有的表的元数据,当当前的 SyntaxContextType 为 column 时,在补全项中插入所有的字段名,这些字段名的自动补全项根据所属的表名出现的位置与当前光标位置的远近来排序。

这显然不是最终的解决方案,但是可能值得一试。

kissycn commented 10 months ago

这是一个好办法期待,期待!

Shu-Ji commented 10 months ago

@Shu-Ji 目前,我们计划先支持获取上下文中的库、表和字段,这相比于完整的语义分析来说简单许多,只需要通过 listener 监听特定节点即可。

此功能支持后,可能对字段的补全有所帮助。即你可以一次性获取上下文中出现的所有的表名,从后端接口中获取所有的表的元数据,当当前的 SyntaxContextType 为 column 时,在补全项中插入所有的字段名,这些字段名的自动补全项根据所属的表名出现的位置与当前光标位置的远近来排序。

这显然不是最终的解决方案,但是可能值得一试。

如果能取到当前文档中所有的库名和表名,那也很好了,因为至少可以补全了,只是补全列表中会出现相对无关的条目,不过问题不大,基本解决问题了。(尝试了一下MySqlParserVisitor模式,在语法不正确的时候,无法visit table等信息,会报语法错误,如何把光标处当成正确的字符,让解析器通过呢?)

有一些阿里同学的参考文章:

https://github.com/ascoders/weekly/blob/master/%E7%BC%96%E8%AF%91%E5%8E%9F%E7%90%86/85.%E7%B2%BE%E8%AF%BB%E3%80%8A%E6%89%8B%E5%86%99%20SQL%20%E7%BC%96%E8%AF%91%E5%99%A8%20-%20%E6%99%BA%E8%83%BD%E6%8F%90%E7%A4%BA%E3%80%8B.md

还有一些后端的lsp项目中,基本都提供光标处的自动补全功能;如: https://github.com/lighttiger2505/sqls

@kissycn 这位同学都激动的彪中文了🐮,这个功能真的对于编辑器来说是很刚需

HaydenOrz commented 10 months ago

如果能取到当前文档中所有的库名和表名,那也很好了,因为至少可以补全了,只是补全列表中会出现相对无关的条目,不过问题不大,基本解决问题了。(尝试了一下MySqlParserVisitor模式,在语法不正确的时候,无法visit table等信息,会报语法错误,如何把光标处当成正确的字符,让解析器通过呢?)

@Shu-Ji 这个问题确实值得思考,你所提到的参考文章是很好的思路。


这位同学都激动的彪中文了🐮,这个功能真的对于编辑器来说是很刚需

@kissycn 关于这个功能的实现时间,我可能需要说声抱歉,之前暂定是 12 月份实现此功能,但是目前看来是没办法做到了,主要是因为年底了,emmm 琐事有点多。

kissycn commented 9 months ago

理解,理解,年底大家事情都多!

Shu-Ji commented 9 months ago

尝试了一下MySqlParserVisitor模式,在语法不正确的时候,无法visit table等信息,会报语法错误,如何把光标处当成正确的字符,让解析器通过呢?

HaydenOrz commented 9 months ago

尝试了一下MySqlParserVisitor模式,在语法不正确的时候,无法visit table等信息,会报语法错误,如何把光标处当成正确的字符,让解析器通过呢?

或许我们可以通过Antlr4 ErrorStrategy (即 Antlr4 提供的自定义错误处理策略接口)来做到这件事,此前我在这个 pr https://github.com/DTStack/dt-sql-parser/pull/230 中已经修改过ErrorStrategy ,但是并不涉及错误恢复策略的改动。

对于 Antlr4 ErrorStrategy 我只是略微了解不是太熟悉,目前没有更多进展,这只是一个可能可行的方案。

另外想要在错误处进行恢复也要分多种情况,对于有结构性错误的语句在理论上很难恢复,因为很难预料语句原本是什么样子的。