0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

【Go】ハードコードしているSQLをバイナリに埋め込んですっきりさせよう

Last updated at Posted at 2023-02-12

はじめに

Goを書いていて、実行するSQLをハードコードしていることはないでしょうか

例えば、短いSQLだとこんなかんじ

	age := 27
	rows, err := db.QueryContext(ctx, "SELECT name FROM users WHERE age=?", age)
	if err != nil {
		log.Fatal(err)
	}

この例であれば、構文ミスがあっても実行前に気づくこともあるでしょう

ただ、次のように複数行にわたってSQLを書いてしまうこともあるのではないでしょうか

 	sqls := []string{
		`UPDATE employees
		SET salary = 5000;
		`,
		`UPDATE employees
		SET salary = salary * 1.1
		WHERE salary <= 10000;
		`,
		`UPDATE employees
		SET salary = 5000
		WHERE department = 'Sales';
		`,
		`UPDATE employees
		SET salary = 5000
		WHERE first_name = 'John' AND last_name = 'Doe';
		`,
		`UPDATE employees
		SET salary = salary * 1.1
		WHERE job_title = 'Manager' AND department = 'Sales';
		`,
	}

	for _, sql :=range sqls {
		_, err := tx.ExecContext(ctx, sql)
		if err != nil {
			log.Fatal(err)
		}
	}

O/Rマッパーをつかわない分、サッとSQLを用意して実行できるのは魅力的です
しかし、複数行にまたがるのでコードの可読性や、SQLの構文チェックがしにくいなどの懸念があると思います

SQLをファイルに分けよう

1つの解決策として、SQLをファイルに分けるという方法があるかと思います
そうすることで、コードの可読性は上がるでしょう

	input, _ := os.ReadFile("input.sql")

	// ファイルから読み込んだ複数のクエリを 1クエリずつ実行できるように処理
	sqls := Something(input)

	for _, sql := range sqls {
		_, err := tx.ExecContext(ctx, sql)
		if err != nil {
			log.Fatal(err)
		}
	}

ただし、読み込んだファイルを有効なSQLにする処理は自分で実装する必要があります

SQLファイルをバイナリに埋め込もう

そこで、今回つくったものがこちらです

このライブラリは、SQLファイルを読み込み1クエリずつ実行できる形に変換します

以下サンプルです

package main

import (
	"bytes"
	"embed"
	"fmt"

	"github.com/uh-zz/sqload"
	"github.com/uh-zz/sqload/driver/mysql"
)

//go:embed sql/*
var content embed.FS

func main() {
	var (
		buf  bytes.Buffer // sql which read from file
		sqls []string // sql after parse
	)

	loader := sqload.New(mysql.Dialector{}) // for PostgreSQL: postgresql.Dialector{}

	if err := loader.Load(&content, &buf); err != nil {
		fmt.Printf("Load error: %s", err.Error())
	}

	if err := loader.Parse(buf.String(), &sqls); err != nil {
		fmt.Printf("Parse error: %s", err.Error())
	}

	fmt.Printf("%+v", sqls)
    // [INSERT INTO table001 (name,age) VALUES ('alice', 10);]
}

go:embedディレクティブをつかってSQLファイルを実行バイナリに埋め込みます
こうすることで、プログラムからファイルを読み込むより効率的です(実行ファイルを配布するだけでよいです)

加えて、アピールポイントとしては、以下2つです

1. SQLファイルから読み込んだSQLが有効であるかをParseするときに検証します

Parserは以下を使用しています

MySQL

MySQL互換の分散DBであるTiDBのParserです

PostgreSQL

分散DBであるCockroachDBから分離されたPostgreSQL Parserです

2. 任意のSQLクライアントを使用できる

単に、読み込んだSQLを[]stringにするだけなので、SQLを実行するクライアントを任意に選べます

さいごに

現状、構文のサポートしているのは、MySQL, PostgreSQLのみになります

今後は、Parserを自前で実装したり、サポートするシステムを拡張できればと思います

IssueやPull Requestも歓迎ですので、どしどし送ってください!

もし気に入っていただけたらGitHubのスターとTwitterのフォローをよろしくおねがいします笑

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?