`vet` - クエリのリンティング

`vet` - クエリのリンティング

v1.19.0で追加

sqlc vetは、クエリを一連のリントルールにかけて実行します。

ルールはsqlc設定ファイルで定義されます。ルールは名前、メッセージ、およびCommon Expression Language(CEL)式で構成されます。式はcel-goを使用して評価されます。式がtrueと評価される場合、sqlc vetは与えられたメッセージを使用してエラーを報告します。

リントルールの定義

各リントルールのCEL式は、以下のprotoメッセージで定義された変数を介して、sqlc設定とクエリからの情報にアクセスできます。

message Config
{
  string version = 1;
  string engine = 2 ;
  repeated string schema = 3;
  repeated string queries = 4;
}

message Query
{
  // SQL body
  string sql = 1;
  // Name of the query
  string name = 2; 
  // One of "many", "one", "exec", etc.
  string cmd = 3;
  // Query parameters, if any
  repeated Parameter params = 4;
}

message Parameter
{
  int32 number = 1;
}

この基本情報に加えて、PostgreSQLまたはMySQLのデータベース接続が設定されている場合、各CEL式はpostgresql.explainおよびmysql.explain変数を介してクエリでEXPLAIN ...を実行した出力にアクセスできます。この出力は非常に複雑で、クエリの構造に依存しますが、sqlcは可能な限り多くの情報を解析して提供しようと試みます。詳細については、EXPLAIN ...出力を使用するルールを参照してください。

CEL式環境で利用可能な基本的な設定とクエリ情報のみを使用したいくつかのルール例を以下に示します。これらの例は単純ですが、書くことができるルールの種類の雰囲気を掴むことができます。

version: 2
sql:
  - schema: "query.sql"
    queries: "query.sql"
    engine: "postgresql"
    gen:
      go:
        package: "authors"
        out: "db"
    rules:
      - no-pg
      - no-delete
      - only-one-param
      - no-exec
rules:
  - name: no-pg
    message: "invalid engine: postgresql"
    rule: |
      config.engine == "postgresql"
  - name: no-delete
    message: "don't use delete statements"
    rule: |
      query.sql.contains("DELETE")
  - name: only-one-param
    message: "too many parameters"
    rule: |
      query.params.size() > 1
  - name: no-exec
    message: "don't use exec"
    rule: |
      query.cmd == "exec"

EXPLAIN ...出力を使用するルール

v1.20.0で追加

CEL式環境には、EXPLAIN ...出力を含む2つの変数、postgresql.explainmysql.explainがあります。sqlcは、設定されたデータベースエンジンに関連付けられた変数のみを入力し、データベース接続が設定されている場合にのみ入力します。

postgresqlエンジンの場合、sqlcは以下を実行します:

EXPLAIN (ANALYZE false, VERBOSE, COSTS, SETTINGS, BUFFERS, FORMAT JSON) ...

ここで"..."はクエリ文字列で、出力をPostgreSQLExplainprotoメッセージに解析します。

mysqlエンジンの場合、sqlcは以下を実行します:

EXPLAIN FORMAT=JSON ...

ここで"..."はクエリ文字列で、出力をMySQLExplainprotoメッセージに解析します。

これらのprotoメッセージ定義はここに含めるには長すぎますが、sqlcソースツリー内のprotosディレクトリで見つけることができます。

EXPLAIN ...からの出力はクエリの構造に依存するため、汎用的な例を提供するのは少し困難です。詳細については、PostgreSQLドキュメントMySQLドキュメントを参照してください。

...
rules:
- name: postgresql-query-too-costly
  message: "Query cost estimate is too high"
  rule: "postgresql.explain.plan.total_cost > 1.0"
- name: postgresql-no-seq-scan
  message: "Query plan results in a sequential scan"
  rule: "postgresql.explain.plan.node_type == 'Seq Scan'"
- name: mysql-query-too-costly
  message: "Query cost estimate is too high"
  rule: "has(mysql.explain.query_block.cost_info) && double(mysql.explain.query_block.cost_info.query_cost) > 2.0"
- name: mysql-must-use-primary-key
  message: "Query plan doesn't use primary key"
  rule: "has(mysql.explain.query_block.table.key) && mysql.explain.query_block.table.key != 'PRIMARY'"

EXPLAIN ...出力に依存するルールを構築する際、データベースから返される実際のJSONを見ることが役立つ場合があります。環境変数SQLCDEBUG=dumpexplain=1を設定すると、sqlcはそれを出力します。この環境変数をダミールールと一緒に使用して、すべてのクエリのEXPLAIN ...出力を見ることができます。

version: 2
sql:
  - schema: "query.sql"
    queries: "query.sql"
    engine: "postgresql"
    database:
      uri: "postgresql://postgres:postgres@localhost:5432/postgres"
    gen:
      go:
        package: "db"
        out: "db"
    rules:
      - debug
rules:
- name: debug
  rule: "!has(postgresql.explain)" # A dummy rule to trigger explain

uriで設定されたデータベースは、vetが正しく動作するために最新のスキーマを持つ必要があり、sqlcはデータベースにスキーママイグレーションを適用しないことに注意してください。EXPLAIN ...出力に依存するルールでsqlc vetを実行する前に、選択したマイグレーションツールを使用して必要なテーブルとオブジェクトを作成してください。

または、管理データベースを設定して、sqlcが正しいスキーマを持つホスト型エフェメラルデータベースを自動的に作成するようにすることもできます。

組み込みルール

sqlc/db-prepare

データベース接続が設定されている場合、組み込みのsqlc/db-prepareルールを実行できます。このルールは、接続されたデータベースに対して各クエリの準備を試み、失敗があれば報告します。

version: 2
sql:
  - schema: "schema.sql"
    queries: "query.sql"
    engine: "postgresql"
    gen:
      go:
        package: "authors"
        out: "db"
    database:
      uri: "postgresql://postgres:password@localhost:5432/postgres"
    rules:
      - sqlc/db-prepare

uriで設定されたデータベースは、vetが正しく動作するために最新のスキーマを持つ必要があり、sqlcはデータベースにスキーママイグレーションを適用しないことに注意してください。sqlc/db-prepareルールでsqlc vetを実行する前に、選択したマイグレーションツールを使用して必要なテーブルとオブジェクトを作成してください。

または、管理データベースを設定して、sqlcが正しいスキーマを持つホスト型エフェメラルデータベースを自動的に作成するようにすることもできます。

version: 2
cloud:
  project: "<PROJECT_ID>"
sql:
  - schema: "schema.sql"
    queries: "query.sql"
    engine: "postgresql"
    gen:
      go:
        package: "authors"
        out: "db"
    database:
      managed: true
    rules:
      - sqlc/db-prepare

実際の動作を見るには、authorsの例をチェックしてください。

リントルールの実行

定義されたルールの名前をsqlパッケージのrulesリストに追加すると、sqlc vetはそのルールをパッケージ内のすべてのクエリに対して評価します。

以下の例では、2つのルールが定義されていますが、1つだけが有効になっています。

version: 2
sql:
  - schema: "query.sql"
    queries: "query.sql"
    engine: "postgresql"
    gen:
      go:
        package: "authors"
        out: "db"
    rules:
      - no-delete
rules:
  - name: no-pg
    message: "invalid engine: postgresql"
    rule: |
      config.engine == "postgresql"
  - name: no-delete
    message: "don't use delete statements"
    rule: |
      query.sql.contains("DELETE")

リントルールのオプトアウト

任意のクエリについて、@sqlc-vet-disableクエリアノテーションを使用してsqlc vetにリントルールを評価しないよう指示できます。アノテーションは無視するルールのリストを受け取ります。

/* name: GetAuthor :one */
/* @sqlc-vet-disable sqlc/db-prepare no-pg */
SELECT * FROM authors
WHERE id = ? LIMIT 1;

ルールは複数の行に分割することもできます。

/* name: GetAuthor :one */
/* @sqlc-vet-disable sqlc/db-prepare */
/* @sqlc-vet-disable no-pg */
SELECT * FROM authors
WHERE id = ? LIMIT 1;

クエリのすべてのルールをスキップするには、パラメータなしで@sqlc-vet-disableアノテーションを提供できます。

/* name: GetAuthor :one */
/* @sqlc-vet-disable */
SELECT * FROM authors
WHERE id = ? LIMIT 1;

原文:https://docs.sqlc.dev/en/latest/howto/vet.html