SQL クエリを使用するアプリケーションは、クエリ ビルダーを使用することでコードの可読性、保守性、セキュリティを向上させることができます。実際、Golang にはまさにそれを行うさまざまなライブラリが多数あります。 Vaunt では、最終的に自分たちで作成することに決定する前に、さまざまなオプションを試しました。最終的には、安全であり、読み取り可能で条件付きステートメントを含めることができる一方で、SQL インジェクションを防ぐための変数置換を提供するものを求めていました。そこで私たちは tqla という新しいライブラリを作成し、昨年末にリリースおよび発表しました。詳細については、この記事をご覧ください。
tqla を構築する前は、SQL クエリ構築ロジックに主に Squirrel を使用していました。これを強くお勧めします。一部の領域ではまだ Squirrel を使用していますが、徐々に新しいクエリ構築ロジックを tqla に置き換えて実装し始めています。私たちは、tqla によってコードを保守し、他のステートメントビルダーを使用したときに遭遇した問題を修正する能力が向上した例を数多く見つけてきました。
Vaunt では最近、CockroachDB から TiDB へのデータベース移行を行いました。 CockroachDB はパフォーマンスが高く信頼性が高かったものの、最終的には OLAP データベースをサポートするために技術スタックを追加するという決定に直面しました。これは、オープンソース コミュニティ インサイト製品での分析ワークロードをサポートするために必要でした。テクノロジーのフットプリントを小さく保つために、私たちは TiDB の使用を進め、データベースの HTAP アーキテクチャを活用することにしました。
CockroachDB は PostgreSQL とほぼ互換性があり、SQL クエリの多くに PostgreSQL 構文を使用しました。 TiDB に切り替えるには、いくつかのテーブルを変更し、MySQL 構文を使用するようにクエリを更新する必要がありました。移行中のいくつかの場所で、条件付きクエリ構築ステートメントが不適切に使用されており、ステートメントが正しく生成されていないことを検出するための適切なテストが不足していることが判明しました。
Squirrel の README には、条件付きクエリの構築を使用してオプションのフィルターを使用してステートメントを更新する方法の例があります。
if len(q) > 0 { users = users.Where("name LIKE ?", fmt.Sprint("%", q, "%")) }
これは、クエリの 1 つを更新して条件付きでテーブルを結合し、オプションのフィルターを追加する方法の実際の、簡略化された例です:
psql := squirrel.StatementBuilder.PlaceholderFormat(squirrel.Question) statementBuilder := psql.Select(`i.id`). From("vaunt.installations i"). Where(`entity_name = ?`, name) if len(provider) > 0 { statementBuilder.Where(`provider = ?`, provider) } if len(repo) > 0 { statementBuilder.Join(`repositories as r on JSON_CONTAINS(i.repositories, CONCAT('["', r.id, '"]'))`) statementBuilder.Where(`r.name = ?`, repo) }
コードの問題を見つけられますか?そうでない場合でも、心配しないでください。これは、テストを実行するまで、私たち自身のコードレビューでもすり抜けていたものです。
ここでの問題は、ビルダー関数の結果でステートメントビルダーを更新するのを忘れたことです。たとえば、プロバイダー条件フィルターは代わりに次のように読み取る必要があります:
if len(provider) > 0 { statementBuilder = statementBuilder.Where(`provider = ?`, provider) }
これは比較的単純な間違いであり、十分なテスト ケースを使用すれば簡単に発見できますが、技術的に無効なコードではないため、何が起こっているのかをすぐに理解するには少し時間がかかる場合があります。
この設定のもう 1 つの読みやすさの問題は、条件付き結合が最初の select ステートメントから分離されていることです。ビルダーを再編成して、各部分を配置すべき場所に配置することもできますが、複数の重複した条件文のチェックが必要になり、依然として読みやすさの問題がいくつか発生します。
Squirrel を使用した上記のデモはその後書き直されており、tqla での同等のものは次のようになります。
t, err := tqla.New(tqla.WithPlaceHolder(tqla.Question)) if err != nil { return nil, err } query, args, err := t.Compile(` SELECT i.id FROM vaunt.installations as i {{ if .Repo }} JOIN vaunt.repositories as r on JSON_CONTAINS(i.repositories, CONCAT('["', r.id, '"]'), '$') {{ end }} WHERE entity_name = {{ .Name}} {{ if .Provider }} AND i.provider = {{ .Provider }} {{ end }} {{ if .Repo }} AND r.name = {{ .Repo }} {{ end }} `, data) if err != nil { return nil, err }
ご覧のとおり、tqla のテンプレート構文を使用すると、条件句の組み込みが非常に簡単になります。 Tqla は、設定している変数を指定されたプレースホルダーに自動的に置き換え、ステートメントを実行するために SQL ドライバーで使用できる引数を提供します。
Squirrel と同様に、このステートメント構築アプローチは、テンプレート ビルダーに渡して出力を検証するさまざまなデータ オブジェクトのセットを作成できるため、テストが簡単です。
クエリの条件部分を最適な場所に簡単に追加できることがわかります。たとえば、ここでは FROM ステートメントの直後に条件付き JOIN があります。まだ複数の条件チェックがありますが、テンプレートが過度に複雑になることはありません。
SQL ビルダーの保守性の向上に役立つもう 1 つの優れた tqla 機能は、変換ロジックを抽象化するためにテンプレートで使用できるカスタム関数を定義できることです。
これは、関数を使用して Golang の time.Time 値を sql.NullTime に変換し、事前に変換せずにデータ オブジェクトの挿入を実行できるようにする方法の例です。
funcs := template.FuncMap{ "time": func(t time.Time) sql.NullTime { if t.IsZero() { return sql.NullTime{Valid: false} } return sql.NullTime{Time: t, Valid: true} }, } t, err := tqla.New(tqla.WithPlaceHolder(tqla.Question), tqla.WithFuncMap(funcs)) if err != nil { return err }
この関数を tqla funcs マップで定義すると、time.Time フィールドであるデータ オブジェクトのパラメーターを指定することで、クエリ テンプレートで自由に使用できるようになります。この関数は、同じテンプレート内で異なるフィールドを使用して複数回呼び出すこともできます。
簡略化した例を次に示します:
statement, args, err := t.Compile(` INSERT INTO events (name, created_at, merged_at, closed_at) VALUES ( {{ .Name }}, {{ time .CreatedAt }}, {{ time .MergedAt }}, {{ time .ClosedAt }} )`, eventData)
結論として、tqla を使用すると、動的クエリを作成するための強力なユーティリティを提供しながら、クエリ構築ロジックの保守性を向上できると考えられます。テンプレート構造が単純であるため、コードが読みやすくなり、潜在的なエラーをより迅速にデバッグできるようになります。
私たちは、さまざまな種類のアプリケーションで SQL クエリを構築するためのシンプルで保守可能で安全な方法を求める他のユーザーに良いオプションを提供できることを願って、このライブラリを共有するために tqla をオープン ソースにしました。
ご興味がございましたら、リポジトリをチェックして、何らかの形で役立つ場合はスターを付けてください。機能リクエストやバグレポートはお気軽にどうぞ!
フィードバックや貢献はいつでもお待ちしております。
将来の開発について最新情報を入手するには、X でフォローするか、Discord に参加してください。
免責事項: 提供されるすべてのリソースの一部はインターネットからのものです。お客様の著作権またはその他の権利および利益の侵害がある場合は、詳細な理由を説明し、著作権または権利および利益の証拠を提出して、電子メール [email protected] に送信してください。 できるだけ早く対応させていただきます。
Copyright© 2022 湘ICP备2022001581号-3