apache / apache/datafusion-sqlparser-rs

Improve representation of `LIKE ALL` and variants

オープン
#1,770 コメント 1 件 リアクション 0 件 担当者 0 名 GitHub で見る
主要言語
Rust
スター
3.5k
フォーク
772
平均マージ
4日 9時間
マージ済み PR(30日)
17

説明

Currently, expressions such as `... LIKE ALL(...)` are parsed as an `Expr::Like` with the `pattern` being an `Expr::Function` with a `name` of `"ALL"`. It seems preferable to parse them as an `Expr::AllOp` with `compare_op: BinaryOperator::Like`.

However, with #569, the various string matching operators (`LIKE`, `ILIKE`) were turned from binary operators into special `Expr` variants, so that is not currently possible. I believe this change also indirectly led to #863 being raised, which was seemingly worked around using the bandaid of a catch-all "custom" binary operator. (`REGEXP` and `RLIKE` were later added in the same manner, as `Expr` variants instead of binary operators, in #1017.)

I cannot tell from the PRs or discussions why these were turned into special `Expr` variants instead of left as binary operators, but it seems that an improved representation of `LIKE ALL`, `NOT ILIKE ANY`, etc. would require either turning them back into binary operators, or somehow changing `AllOp` from using `compare_op: BinaryOperator` to... something else. I can't currently think of an alternative approach.

On the other hand, perhaps the representation of `ALL`/`ANY` with special `Expr` variants is actually unnecessary, and they should actually parse as something like `Expr::BinaryOp { left, op, right: Expr::All(_)}`. Either way, ideally `a = ANY(...)` and `a LIKE ANY(...)` would end up with similar ASTs.

### Appendix: current parsing behavior

```
$ echo "SELECT 'abc' ILIKE ALL('{aBC, _bc}');" | cargo run --example cli - --postgres
Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.06s
Running `target/debug/examples/cli - --postgres`
Parsing from stdin using PostgreSqlDialect
2025-03-18T22:58:41.683Z DEBUG [sqlparser::parser] Parsing sql 'SELECT 'abc' ILIKE ALL('{aBC, _bc}');
'...
2025-03-18T22:58:41.687Z DEBUG [sqlparser::parser] parsing expr
2025-03-18T22:58:41.687Z DEBUG [sqlparser::parser] prefix: Value(ValueWithSpan { value: SingleQuotedString("abc"), span: Span(Location(1,8)..Location(1,13)) })
2025-03-18T22:58:41.688Z DEBUG [sqlparser::dialect::postgresql] get_next_precedence() TokenWithSpan { token: Word(Word { value: "ILIKE", quote_style: None, keyword: ILIKE }), span: Span(Location(1,14)..Location(1,19)) }
2025-03-18T22:58:41.688Z DEBUG [sqlparser::dialect] get_next_precedence_full() TokenWithSpan { token: Word(Word { value: "ILIKE", quote_style: None, keyword: ILIKE }), span: Span(Location(1,14)..Location(1,19)) }
2025-03-18T22:58:41.688Z DEBUG [sqlparser::parser] next precedence: 60
2025-03-18T22:58:41.688Z DEBUG [sqlparser::parser] parsing expr
2025-03-18T22:58:41.689Z DEBUG [sqlparser::parser] parsing expr
2025-03-18T22:58:41.689Z DEBUG [sqlparser::parser] prefix: Value(ValueWithSpan { value: SingleQuotedString("{aBC, _bc}"), span: Span(Location(1,24)..Location(1,36)) })
2025-03-18T22:58:41.689Z DEBUG [sqlparser::dialect::postgresql] get_next_precedence() TokenWithSpan { token: RParen, span: Span(Location(1,36)..Location(1,37)) }
2025-03-18T22:58:41.689Z DEBUG [sqlparser::dialect] get_next_precedence_full() TokenWithSpan { token: RParen, span: Span(Location(1,36)..Location(1,37)) }
2025-03-18T22:58:41.689Z DEBUG [sqlparser::parser] next precedence: 0
2025-03-18T22:58:41.689Z DEBUG [sqlparser::parser] parsing expr
2025-03-18T22:58:41.689Z DEBUG [sqlparser::parser] prefix: Value(ValueWithSpan { value: SingleQuotedString("{aBC, _bc}"), span: Span(Location(1,24)..Location(1,36)) })
2025-03-18T22:58:41.689Z DEBUG [sqlparser::dialect::postgresql] get_next_precedence() TokenWithSpan { token: RParen, span: Span(Location(1,36)..Location(1,37)) }
2025-03-18T22:58:41.689Z DEBUG [sqlparser::dialect] get_next_precedence_full() TokenWithSpan { token: RParen, span: Span(Location(1,36)..Location(1,37)) }
2025-03-18T22:58:41.689Z DEBUG [sqlparser::parser] next precedence: 0
2025-03-18T22:58:41.689Z DEBUG [sqlparser::parser] prefix: Function(Function { name: ObjectName([Identifier(Ident { value: "ALL", quote_style: None, span: Span(Location(1,20)..Location(1,23)) })]), uses_odbc_syntax: false, parameters: None, args: List(FunctionArgumentList { duplicate_treatment: None, args: [Unnamed(Expr(Value(ValueWithSpan { value: SingleQuotedString("{aBC, _bc}"), span: Span(Location(1,24)..Location(1,36)) })))], clauses: [] }), filter: None, null_treatment: None, over: None, within_group: [] })
2025-03-18T22:58:41.689Z DEBUG [sqlparser::dialect::postgresql] get_next_precedence() TokenWithSpan { token: SemiColon, span: Span(Location(1,37)..Location(1,38)) }
2025-03-18T22:58:41.689Z DEBUG [sqlparser::dialect] get_next_precedence_full() TokenWithSpan { token: SemiColon, span: Span(Location(1,37)..Location(1,38)) }
2025-03-18T22:58:41.689Z DEBUG [sqlparser::parser] next precedence: 0
2025-03-18T22:58:41.689Z DEBUG [sqlparser::dialect::postgresql] get_next_precedence() TokenWithSpan { token: SemiColon, span: Span(Location(1,37)..Location(1,38)) }
2025-03-18T22:58:41.689Z DEBUG [sqlparser::dialect] get_next_precedence_full() TokenWithSpan { token: SemiColon, span: Span(Location(1,37)..Location(1,38)) }
2025-03-18T22:58:41.689Z DEBUG [sqlparser::parser] next precedence: 0
Round-trip:
'SELECT 'abc' ILIKE ALL('{aBC, _bc}')'
Parse results:
[
Query(
Query {
with: None,
body: Select(
Select {
select_token: TokenWithSpan {
token: Word(
Word {
value: "SELECT",
quote_style: None,
keyword: SELECT,
},
),
span: Span(Location(1,1)..Location(1,7)),
},
distinct: None,
top: None,
top_before_distinct: false,
projection: [
UnnamedExpr(
ILike {
negated: false,
any: false,
expr: Value(
ValueWithSpan {
value: SingleQuotedString(
"abc",
),
span: Span(Location(1,8)..Location(1,13)),
},
),
pattern: Function(
Function {
name: ObjectName(
[
Identifier(
Ident {
value: "ALL",
quote_style: None,
span: Span(Location(1,20)..Location(1,23)),
},
),
],
),
uses_odbc_syntax: false,
parameters: None,
args: List(
FunctionArgumentList {
duplicate_treatment: None,
args: [
Unnamed(
Expr(
Value(
ValueWithSpan {
value: SingleQuotedString(
"{aBC, _bc}",
),
span: Span(Location(1,24)..Location(1,36)),
},
),
),
),
],
clauses: [],
},
),
filter: None,
null_treatment: None,
over: None,
within_group: [],
},
),
escape_char: None,
},
),
],
into: None,
from: [],
lateral_views: [],
prewhere: None,
selection: None,
group_by: Expressions(
[],
[],
),
cluster_by: [],
distribute_by: [],
sort_by: [],
having: None,
named_window: [],
qualify: None,
window_before_qualify: false,
value_table_mode: None,
connect_by: None,
flavor: Standard,
},
),
order_by: None,
limit_clause: None,
fetch: None,
locks: [],
for_clause: None,
settings: None,
format_clause: None,
},
),
]
```

コントリビューションガイド

このリポジトリのコントリビューションガイドは索引されていません

調査の方向性

まず、既存の `Expr::Like`、`Expr::AllOp`、`BinaryOperator` の表現を比較し、次に issue に示されている PostgreSQL CLI の例を実行して現在の AST を確認します。issue では設計が未決定のままです。`LIKE ALL` と関連するバリアントが `= ANY(...)` と一貫した AST 表現を持ち、その動作がテストでカバーされれば完了です。

索引モデルが issue の本文から書いたものです。

評価

技術スタック
rust
領域
databases
issue の種類
機能追加
難易度
5/5
見積もり時間
1週間以上
活発さ
停滞
明瞭さ
説明が足りない
初心者へのやさしさ
25/100

新しい issue をメールで受け取る

初心者向けの GitHub issue を短くまとめたダイジェスト。