Upgrade to Pro — share decks privately, control downloads, hide ads and more …

SpannerのSQL解析ライブラリ "memefish" の紹介/gcpug-memefish-introduction

SpannerのSQL解析ライブラリ "memefish" の紹介/gcpug-memefish-introduction

GCPUG Tokyo Handy Spanner Day February 2020 (2020/02/06)での発表資料

TSUYUSATO Kitsune

February 06, 2020
Tweet

More Decks by TSUYUSATO Kitsune

Other Decks in Programming

Transcript

  1. SpannerのSQL解析ライブラリ "memefish" の紹介 @MakeNowJust at GCPUG Tokyo Handy Spanner Day

    February 2020 (2020/02/06) SpannerのSQL解析ライブラリ "memefish" の紹介 by @MakeNowJust 1
  2. パッケージ構成 github.com/MakeNowJust/memefish/pkg 以下の重要なパッケージ token : パースに使うトークンやファイルの構造体を定義したパッケージ ast : パース結果のAST (抽象構⽂⽊)を定義したパッケージ

    parser : パーサーの実装があるパッケージ analyzer : 型チェッカーのあるパッケージ go/token, go/ast, go/parser などのパッケージを参考にしている SpannerのSQL解析ライブラリ "memefish" の紹介 by @MakeNowJust 8
  3. 使い⽅: import ⽂ まずは import 。 ast, token, parser を使う。

    import ( "fmt" "log" "github.com/MakenowJust/memefish/pkg/ast" "github.com/MakenowJust/memefish/pkg/parser" "github.com/MakenowJust/memefish/pkg/token" ) SpannerのSQL解析ライブラリ "memefish" の紹介 by @MakeNowJust 15
  4. 使い⽅: Parser の組み⽴て Parser は構造体なので必要なフィールドを埋めていく。 必要なフィールドは Lexer だけ。 ( Parser

    が Lexer を使うので) また、パースしたい⽂字列は File という構造体に包む。 file := &token.File{ Buffer: "SELECT * FROM Singers WHERE FirstName = \"Foo\"", } p := &parser.Parser{ Lexer: &parser.Lexer{File: file}, } SpannerのSQL解析ライブラリ "memefish" の紹介 by @MakeNowJust 16
  5. 使い⽅: ParseQuery の呼び出し 実際のパースは ParseQuery メソッドの呼び出しで⾏なわれる。 ParseQuery の返り値は抽象構⽂⽊とパース時のエラー。 stmt, err

    := p.ParseQuery() if err != nil { log.Fatal(err) } あとは、この stmt を使ってやりたいことをする。 stmt.Query.(*ast.Select).From.Source.(*ast.TableName).Table.Name == "Singers" // => true SpannerのSQL解析ライブラリ "memefish" の紹介 by @MakeNowJust 17
  6. 使い⽅: まとめ パースしたい⽂字列を構造体 File に包む ↑を使って Lexer 、 Parser を作る

    ParseQuery を呼び出してパースを実⾏する (DMLのパースなら ParseDML 、DDLのパースなら ParseDDL が使える) 結果の抽象構⽂⽊を使ってごにょごにょする SpannerのSQL解析ライブラリ "memefish" の紹介 by @MakeNowJust 18
  7. SQLの⽣成 抽象構⽂⽊の型 ast.Node の定義 type Node interface { Pos() token.Pos

    End() token.Pos // Convert AST node into SQL source string (a.k.a. Unparse). SQL() string } この SQL() メソッドで、抽象構⽂⽊からSQLの⽂字列を⽣成できる。 SpannerのSQL解析ライブラリ "memefish" の紹介 by @MakeNowJust 19
  8. 例: SQLの⽣成 さきほどまでのパースの例の続き // Singers の部分を Albums に差し替える stmt.Query.(*ast.Select).From.Source.(*ast.TableName).Table.Name =

    "Albums" fmt.Println(stmt.SQL()) // Output: // SELECT * FROM Albums WHERE FirstName = "Foo" SpannerのSQL解析ライブラリ "memefish" の紹介 by @MakeNowJust 20
  9. 意味解析・型チェック: 使⽤イメージ 例: SELECT CONCAT(FirstName, " ", LastName) AS Name,

    BirthDate FROM Singers から type Result struct { Name string BirthDate time.Time } を⾃動⽣成 SpannerのSQL解析ライブラリ "memefish" の紹介 by @MakeNowJust 26
  10. 使い⽅: import ⽂ 意味解析は抽象構⽂⽊に対して⾏うので、 parser, token のインポートも必要 import ( "fmt"

    "log" "github.com/MakeNowJust/memefish/pkg/analyzer" "github.com/MakeNowJust/memefish/pkg/parser" "github.com/MakeNowJust/memefish/pkg/token" ) SpannerのSQL解析ライブラリ "memefish" の紹介 by @MakeNowJust 28
  11. 使い⽅: パース まずはパースをする。(さっきと同じ) file := &token.File{ Buffer: "SELECT CONCAT(FirstName, \"

    \", LastName) AS Name, BirthDate FROM Singers", } p := &parser.Parser{ Lexer: &parser.Lexer{File: file}, } stmt, err := p.ParseQuery() if err != nil { log.Fatal(err) } SpannerのSQL解析ライブラリ "memefish" の紹介 by @MakeNowJust 29
  12. 使い⽅: Catalog の組み⽴て テーブルのスキーマ定義をまとめたものを Catalog と呼ぶ。 catalog := &analyzer.Catalog{ Tables:

    map[string]*analyzer.TableSchema{ "SINGERS": { Name: "Singers", Columns: []*analyzer.ColumnSchema{ {Name: "FirstName", Type: analyzer.StringType}, {Name: "LastName", Type: analyzer.StringType}, {Name: "BirthDate", Type: analyzer.DateType}, }, }, }, } SpannerのSQL解析ライブラリ "memefish" の紹介 by @MakeNowJust 30
  13. 使い⽅: Analyzer の組み⽴て Catalog と File を渡す必要がある。 File はエラー位置を実際のソースコードに対応させるために必要。 a

    := &analyzer.Analyzer{ File: file, Catalog: catalog, } SpannerのSQL解析ライブラリ "memefish" の紹介 by @MakeNowJust 31
  14. 使い⽅: 意味解析の結果を使う 例えば、 a.NameList には結果の各カラムの情報が保存されている。 これを使って、SQLの結果に対応する構造体の定義を出⼒する。 columns := a.NameLists[stmt.Query] fmt.Printf("type

    Result struct {\n"); for _, column := range columns { fmt.Printf("\t%-9s %s\n", column.Text, mapSQLType(column.Type)) } fmt.Printf("}\n"); ( mapSQLType はSQLの型をGo⾔語の型に対応させる関数。省略) SpannerのSQL解析ライブラリ "memefish" の紹介 by @MakeNowJust 33
  15. 使い⽅: まとめ 解析したいSQLをパースする スキーマをまとめた Catalog を組み⽴てる Catalog と File から

    Analyzer を組み⽴てる AnalyzeQueryStatement に解析したい抽象構⽂⽊を渡す 意味解析の結果は Analyzer ⾃⾝に保存されている 他には Types フィールドに各式の型が保存されていたりする。 今回の例の全体は次のGo Playgroundから確認できます。 https://play.golang.org/p/PN5JuJKB-Mo SpannerのSQL解析ライブラリ "memefish" の紹介 by @MakeNowJust 34