読譜活動 – ZetaSQL – ResolveQuery

子が日本語補習校なので土曜は謎の時間があります。土曜まで学校とか気の毒だが、それはさておき読んでくぞ。

  • resolver_stmt.cc: Zeta, Create XXX が異常にたくさんあり、エンタープライズ製品感強し。CREATE MODEL ってなんやねん・・・。(BQ の機能らしい)
  • そういうのは置いといて SELECT 見てくべし。というわけで resolver_query.cc ... ギャー 8000 行! きびしい。
  • なお ResolveQuery() の出力は ResolvedScan。こうした resolved AST は スクリプトで生成しているらしい。こいつがまた 8000 行。もうちょっとなんとかならんのかロジックとデータを分離するとかさ (GYP につながる道)。superclass は ResolvedNode. 直列化をサポートしている。データベースは分散システムだからねえ。AST をどっかに送りたいこともあるのでしょう。Catalog とかも分散しないといけないので AST だけ直列化できても不十分だが、そういう usecase は興味深い。AST という木構造をどうやって proto に書いているのかは興味があるが、あとで。
  • ところでドキュメンテーションが割と充実してるね。ただユーザ向けのリファレンスと開発者向けのガイドが混ざってるのが厳しい。分けてもらえんもんかのう・・・。
  • コードを見ていて気付いたが、SELECT はネストできるのか。つまり・・・

$ bazel-bin/zetasql/tools/execute_query/execute_query "select (select 1 + 1) + 1"
+---+
|   |
+---+
| 3 |
+---+

Resolved AST をダンプしてみよう。

$ bazel-bin/zetasql/tools/execute_query/execute_query --mode=resolve "select (select 1 + 1) + 1"
QueryStmt
+-output_column_list=
| +-$query.$col1#2 AS `$col1` [INT64]
+-query=
  +-ProjectScan
    +-column_list=[$query.$col1#2]
    +-expr_list=
    | +-$col1#2 :=
    |   +-FunctionCall(ZetaSQL:$add(INT64, INT64) -> INT64)
    |     +-SubqueryExpr
    |     | +-type=INT64
    |     | +-subquery_type=SCALAR
    |     | +-subquery=
    |     |   +-ProjectScan
    |     |     +-column_list=[$expr_subquery.$col1#1]
    |     |     +-expr_list=
    |     |     | +-$col1#1 :=
    |     |     |   +-FunctionCall(ZetaSQL:$add(INT64, INT64) -> INT64)
    |     |     |     +-Literal(type=INT64, value=1)
    |     |     |     +-Literal(type=INT64, value=1)
    |     |     +-input_scan=
    |     |       +-SingleRowScan
    |     +-Literal(type=INT64, value=1)
    +-input_scan=
      +-SingleRowScan

Subquery が "SCALAR" であるという事実は静的に判定されている。複数の row を返すようなケースではどう resolve されるのだろうか・・・。なんとかテーブルをつっこむ必要があるな。この BUILD ファイル に CSV を食わせる方法がひっそり書いてあるので試すと動いた(が、コピペでは動かなかった。あとで bug をファイルすべし。)

$ bazel-bin/zetasql/tools/execute_query/execute_query  --table_spec="data=csv:$(pwd)/sample.csv" "select * from data"
+---+-----+----+
| a | b   | c  |
+---+-----+----+
| 5 | 1.2 | r1 |
| 6 | 1.5 | r2 |
| 7 | 3.3 | r3 |
+---+-----+----+

さて query のネストをしてみましょう。

$ bazel-bin/zetasql/tools/execute_query/execute_query  --table_spec="data=csv:$(pwd)/sample.csv" "select (select CAST(a AS INT64) from data) + 1"
ERROR: OUT_OF_RANGE: More than one element [type.googleapis.com/zetasql.execute_query.ParserErrorContext='\n.select (select CAST(a AS INT64) from data) + 1']

EXPLAIN すると...

morrita@noisia:~/src/zetasql$ bazel-bin/zetasql/tools/execute_query/execute_query  --table_spec="data=csv:$(pwd)/sample.csv" --mode=explain "select (select CAST(a AS INT64) from data) + 1"
RootOp(
+-input: ComputeOp(
  +-map: {
  | +-$col1.2 := Add(SingleValueExpr(
  |   +-value: $col1,
  |   +-input: ComputeOp(
  |     +-map: {
  |     | +-$col1 := Cast($a, ConstExpr(false))},
  |     +-input: EvaluatorTableScanOp(
  |       +-a#0
  |       +-b#1
  |       +-c#2
  |       +-table: data))), ConstExpr(1))},
  +-input: EnumerateOp(ConstExpr(1))))

"ParserErrorContext" といっていたが、EXPLAIN はできている。つまり実行時エラーである。実際 subquery を LIMIT 1 すると成功する。というわけで実行結果が vector か scalar かというのは型の一部ではない、と言えそう。

一方 string と int を + しようとすると・・・

$ bazel-bin/zetasql/tools/execute_query/execute_query  --mode=resolve --table_spec="data=csv:$(pwd)/sample.csv" "select (select a from data LIMIT 1) + 1"
ERROR: INVALID_ARGUMENT: No matching signature for operator + for argument types: STRING, INT64. Supported signatures: INT64 + INT64; UINT64 + UINT64; DOUBLE + DOUBLE; NUMERIC + NUMERIC; BIGNUMERIC + BIGNUMERIC; DATE + INT64; INT64 + DATE; TIMESTAMP + INTERVAL; INTERVAL + TIMESTAMP; DATE + INTERVAL; INTERVAL + DATE; DATETIME + INTERVAL; INTERVAL + DATETIME; INTERVAL + INTERVAL [at 1:8] [type.googleapis.com/zetasql.execute_query.ParserErrorContext='\n\'select (select a from data LIMIT 1) + 1']

Resolve に失敗する。というわけで + の評価には型が存在する as expected. なお parse には成功する。

$ bazel-bin/zetasql/tools/execute_query/execute_query  --mode=parse --table_spec="data=csv:$(pwd)/sample.csv" "select (select a from data LIMIT 1) + 1"
QueryStatement [0-39]
  Query [0-39]
    Select [0-39]
      SelectList [7-39]
        SelectColumn [7-39]
          BinaryExpression(+) [7-39]
            ExpressionSubquery [7-35]
              Query [8-34]
                Select [8-26]
                  SelectList [15-16]
                    SelectColumn [15-16]
                      PathExpression [15-16]
                        Identifier(a) [15-16]
                  FromClause [17-26]
                    TablePathExpression [22-26]
                      PathExpression [22-26]
                        Identifier(data) [22-26]
                LimitOffset [27-34]
                  IntLiteral(1) [33-34]
            IntLiteral(1) [38-39]

  • さて本題に戻り、末端の SELECT を評価するのは ResolveSelect(). これは・・・複雑。だがコメントが丁寧に書いてあって大企業えらいですね。こういうコメント見ると、コードで表現するのが常に正しいのか疑問になってしまうよな。理想的に正しいのはわかるが、現実として。
  • なお Duck も select の resolve はなかなかカオスだったので、そういうものなのでしょう。