สร้างโค้ดฐานข้อมูล Go ด้วย sqlc
sqlc เป็นเครื่องมือใหม่ที่ทำให้การทำงานกับ SQL ใน Go เป็นเรื่องสนุก
ปรับปรุงประสบการณ์ของนักพัฒนาในการทำงานกับฐานข้อมูลเชิงสัมพันธ์ได้อย่างมากโดยไม่ลดทอนความปลอดภัยของประเภทหรือประสิทธิภาพรันไทม์ ไม่ใช้แท็กโครงสร้าง ฟังก์ชัน mapper ที่เขียนด้วยลายมือ การสะท้อนสิ่งที่ไม่จำเป็น หรือเการพิ่ม dependencies ใหม่ให้กับโค้ดของคุณ ในความเป็นจริง มันยังรับประกันความถูกต้องและความปลอดภัยอีกด้วย
sqlc ทำสิ่งนี้ให้สำเร็จโดยใช้วิธีการที่แตกต่างกันโดยพื้นฐาน: รวบรวม SQL ลงในโค้ด Go ที่ปลอดภัยสำหรับการสร้างอย่างสมบูรณ์
วิธีใช้ sqlc มี 3 ขั้นตอน
- คุณเขียนคำสั่ง SQL
- คุณเรียกใช้
sqlc
เพื่อสร้างโค้ด Go ที่แสดงอินเทอร์เฟซที่ปลอดภัยสำหรับข้อความค้นหาเหล่านั้น - คุณเขียนโค้ดแอปพลิเคชันที่เรียกวิธีการสร้าง
sqlc
มันเป็นเรื่องง่ายที่ คุณไม่จำเป็นต้องเขียนโค้ดการสืบค้น SQL แบบสำเร็จรูปอีกต่อไป sqlc สร้างโค้ด Go สำนวนที่ปลอดภัยแบบเต็มรูปแบบจากการสืบค้นของคุณ sqlc ยังป้องกันข้อผิดพลาดทั่วไปทั้งคลาสในโค้ด SQL
ในระหว่างการสร้างโค้ด sqlc จะแยกวิเคราะห์ข้อความค้นหาและคำสั่ง DDL ทั้งหมดของคุณ (เช่น CREATE TABLE
) เพื่อให้ทราบชื่อและประเภทของทุกคอลัมน์ในตารางและทุกนิพจน์ในการสืบค้นของคุณ หากมีสิ่งใดที่ไม่ตรงกัน sqlc จะไม่สามารถคอมไพล์เคียวรีของคุณจับข้อผิดพลาดรันไทม์ก่อนที่จะเกิดขึ้น
ในทำนองเดียวกัน วิธีการที่ sqlc สร้างขึ้นสำหรับคุณมี arity ที่เข้มงวดและแก้ไขคำจำกัดความประเภท Go ที่ตรงกับคอลัมน์ของคุณ ดังนั้น หากคุณเปลี่ยนอาร์กิวเมนต์ของคิวรีหรือประเภทของคอลัมน์แต่ไม่อัปเดตโค้ด จะไม่สามารถคอมไพล์ได้
ข้อกำหนดเบื้องต้น
ข้อกำหนดสำหรับบทความนี้คือ คุณควรได้ทำตามบทความ ติดตั้ง Go บน Mac และ ติดตั้งและใช้งาน Homebrew บน Mac มาก่อน
ติดตั้ง sqlc บน macOS
ตอนนี้จะแสดงวิธีติดตั้ง sqlc ด้วย Mac
สร้างโปรเจคใหม่ ในตัวอย่างชื่อ sqlc-example เปิดเทอร์มินัลแล้วใช้คำสั่ง brew install
brew install kyleconroy/sqlc/sqlc
เราสามารถเรียกใช้ sqlc version เพื่อดูว่ามันใช้รุ่นอะไรอยู่ ในตัวอย่างคือเวอร์ชัน 1.8.0
sqlc version
เขียนไฟล์การตั้งค่า (Configuration file)
และสร้างไฟล์ sqlc.yaml ด้วยคำสั่ง
sqlc init
sqlc ใช้ไฟล์การกำหนดค่าที่รูทของโปรเจ็กต์ของคุณเพื่อจัดเก็บการตั้งค่า สร้าง sqlc.json ด้วยเนื้อหาต่อไปนี้:
version: 1
packages:
- name: "postgres"
path: "postgres"
emit_json_tags: true
schema: "schema.sql"
queries: "queries.sql"
- name ตัวเลือกที่นี่คือบอก sqlc ว่าแพ็คเกจ Go ที่จะถูกสร้างขึ้นชื่อ postgres
- ต่อไป เราต้องระบุ path โฟลเดอร์สำหรับเก็บไฟล์โค้ด Go ที่จะถูก sqlc สร้างขึ้นชื่อ postgres
- emit_json_tags เป็น true เนื่องจาก เราต้องการให้ sqlc เพิ่มแท็ก JSON ให้กับโครงสร้างที่สร้างขึ้น
- ตัวเลือกสคีมานี้ควรชี้ไปที่มีสคีมาฐานข้อมูลในกรณีของเราก็คือ schema.sql
- จากนั้นเรามีตัวเลือก queries ที่จะบอก sqlc ว่าจะค้นหาไฟล์ SQL query ก็คือ queries.sql
เริ่มการทำงาน
สร้างไฟล์ Go Modules ด้วยคำสั่ง
go mod init sqlc-example
สร้างไฟล์ main.go และเขียนโค้ดเบื้องต้น ดังนี้
package main
func main() {
}
สร้างไฟล์ schema.sql และเขียนโค้ดดังนี้
CREATE TABLE users (
id SERIAL PRIMARY KEY,
firstname TEXT NOT NULL,
lastname TEXT NOT NULL
);
CREATE TABLE todos (
id SERIAL PRIMARY KEY,
user_id SERIAL NOT NULL REFERENCES users(id),
task TEXT NOT NULL,
done BOOLEAN NOT NULL
);
สร้างไฟล์ queries.sql และเขียนโค้ดดังนี้
-- name: GetUsers :many
SELECT * FROM users;
-- name: GetUser :one
SELECT * FROM users
WHERE id = $1 LIMIT 1;
-- name: DeleteUser :exec
DELETE FROM users
WHERE id = $1;
-- name: CreateUser :one
INSERT INTO users (firstname, lastname)
VALUES ($1, $2) RETURNING *;
-- name: CreateTodo :one
INSERT INTO todos (user_id, task, done)
VALUES ($1, $2, $3) RETURNING *;
เรียกใช้ sqlc สร้างโค้ดฐานข้อมูล
เรียกใช้คำสั่ง
sqlc generate
จะพบโฟลเดอร์ postgres ที่ sqlc สร้างขึ้น เพิ่มเข้ามา และเราจะเห็นไฟล์ที่สร้างขึ้นใหม่ 3 ไฟล์ คือ
ไฟล์ที่ 1 คือ models.go ไฟล์นี้มีคำจำกัดความโครงสร้างของ 2 โครงสร้าง คือ: Todo struct และ User struct
// Code generated by sqlc. DO NOT EDIT.
package postgres
import ()
type Todo struct {
ID int32 `json:"id"`
UserID int32 `json:"user_id"`
Task string `json:"task"`
Done bool `json:"done"`
}
type User struct {
ID int32 `json:"id"`
Firstname string `json:"firstname"`
Lastname string `json:"lastname"`
}
โค้ดทั้งหมดมีแท็ก JSON เนื่องจากเราตั้งค่า emit_json_tags เป็น true
ไฟล์ที่ 2 คือ db.go ไฟล์นี้มี DBTX อินเทอร์เฟซ มันกำหนด 4 วิธีทั่วไปที่ทั้งคู่ sql.DB และ sql.Tx มี object ซึ่งช่วยให้เราใช้ฐานข้อมูลหรือธุรกรรมเพื่อดำเนินการสืบค้นข้อมูลได้อย่างอิสระ
// Code generated by sqlc. DO NOT EDIT.
package postgres
import (
"context"
"database/sql"
)
type DBTX interface {
ExecContext(context.Context, string, ...interface{}) (sql.Result, error)
PrepareContext(context.Context, string) (*sql.Stmt, error)
QueryContext(context.Context, string, ...interface{}) (*sql.Rows, error)
QueryRowContext(context.Context, string, ...interface{}) *sql.Row
}
func New(db DBTX) *Queries {
return &Queries{db: db}
}
type Queries struct {
db DBTX
}
func (q *Queries) WithTx(tx *sql.Tx) *Queries {
return &Queries{
db: tx,
}
}
ดังที่คุณเห็นในที่นี้ฟังก์ชัน New() รับ DBTX เป็นอินพุตและส่งคืนอ็อบเจ็กต์ Queries ดังนั้นเราจึงสามารถส่งผ่านใน object sql.DB หรือ sql.Tx ขึ้นอยู่กับว่าเราต้องการดำเนินการค้นหาเพียง 1 แบบสอบถามเดียวหรือชุดของแบบสอบถามหลายรายการภายในธุรกรรม
ไฟล์ที่ 3 เป็นไฟล์ queries.sql.go ชื่อแพ็คเกจเป็น postgres เป็นไปตามที่เรากำหนดในไฟล์ sqlc.yaml และมีโค้ดวิธีการเข้าถึงข้อมูลที่เรากำหนดไว้ใน queries.sql มันค่อนข้างละเอียด ถ้าคุณไม่ได้ใช้ sqlc นี่คือโค้ดที่คุณต้องเขียนเอง!
// Code generated by sqlc. DO NOT EDIT.
// source: queries.sql
package postgres
import (
"context"
)
const createTodo = `-- name: CreateTodo :one
INSERT INTO todos (user_id, task, done)
VALUES ($1, $2, $3) RETURNING id, user_id, task, done
`
type CreateTodoParams struct {
UserID int32 `json:"user_id"`
Task string `json:"task"`
Done bool `json:"done"`
}
func (q *Queries) CreateTodo(ctx context.Context, arg CreateTodoParams) (Todo, error) {
row := q.db.QueryRowContext(ctx, createTodo, arg.UserID, arg.Task, arg.Done)
var i Todo
err := row.Scan(
&i.ID,
&i.UserID,
&i.Task,
&i.Done,
)
return i, err
}
const createUser = `-- name: CreateUser :one
INSERT INTO users (firstname, lastname)
VALUES ($1, $2) RETURNING id, firstname, lastname
`
type CreateUserParams struct {
Firstname string `json:"firstname"`
Lastname string `json:"lastname"`
}
func (q *Queries) CreateUser(ctx context.Context, arg CreateUserParams) (User, error) {
row := q.db.QueryRowContext(ctx, createUser, arg.Firstname, arg.Lastname)
var i User
err := row.Scan(&i.ID, &i.Firstname, &i.Lastname)
return i, err
}
const deleteUser = `-- name: DeleteUser :exec
DELETE FROM users
WHERE id = $1
`
func (q *Queries) DeleteUser(ctx context.Context, id int32) error {
_, err := q.db.ExecContext(ctx, deleteUser, id)
return err
}
const getUser = `-- name: GetUser :one
SELECT id, firstname, lastname FROM users
WHERE id = $1 LIMIT 1
`
func (q *Queries) GetUser(ctx context.Context, id int32) (User, error) {
row := q.db.QueryRowContext(ctx, getUser, id)
var i User
err := row.Scan(&i.ID, &i.Firstname, &i.Lastname)
return i, err
}
const getUsers = `-- name: GetUsers :many
SELECT id, firstname, lastname FROM users
`
func (q *Queries) GetUsers(ctx context.Context) ([]User, error) {
rows, err := q.db.QueryContext(ctx, getUsers)
if err != nil {
return nil, err
}
defer rows.Close()
var items []User
for rows.Next() {
var i User
if err := rows.Scan(&i.ID, &i.Firstname, &i.Lastname); err != nil {
return nil, err
}
items = append(items, i)
}
if err := rows.Close(); err != nil {
return nil, err
}
if err := rows.Err(); err != nil {
return nil, err
}
return items, nil
}
ติดตั้ง PostgreSQL บน Mac
https://www.enterprisedb.com/downloads/postgres-postgresql-downloads
สร้างฐานข้อมูล
pgAdmin 4 เป็นโปรแกรมที่ใช้ในการจัดการระบบฐานข้อมูลและการพัฒนา Open Source ที่หลากหลาย สำหรับ PostgreSQL
สร้าง database ชื่อ sqlc โดย คลิกขวาที่ Database -> Create -> Database…
Save
สร้างตารางใหม่ในฐานข้อมูล โดย คลิกขวาที่ Tables -> Query Tool
คัดลอกโค้ดจากไฟล์ schema.sql ไปวางไว้ที่ Query Editor
CREATE TABLE users (
id SERIAL PRIMARY KEY,
firstname TEXT NOT NULL,
lastname TEXT NOT NULL
);
CREATE TABLE todos (
id SERIAL PRIMARY KEY,
user_id SERIAL NOT NULL REFERENCES users(id),
task TEXT NOT NULL,
done BOOLEAN NOT NULL
);
คลิกที่ รูป 3 เหลี่ยม (Execute) -> แสดงข้อความว่า สร้างสำเร็จ -> ที่ไฟล์ database -> Refresh… -> จะพบ ตาราง todos และ users เพิ่มเข้ามา
ทดสอบการทำงาน
ที่ไฟล์ main.go เขียนโค้ดเพิ่มดังนี้ (แก้ไขโค้ด conn ส่วนติดต่อฐานข้อมูลตามค่าที่คุณกำหนด)
package main
import (
"context"
"database/sql"
"log"
"sqlc-example/postgres"
_ "github.com/lib/pq"
)
func main() {
conn, err := sql.Open("postgres", "host=localhost user=postgres password=123456 dbname=sqlc port=5432 sslmode=disable")
if err != nil {
log.Fatal(err)
}
db := postgres.New(conn)
user, err := db.CreateUser(context.Background(), postgres.CreateUserParams{
Firstname: "Go Developers",
Lastname: "Thailand",
})
if err != nil {
log.Fatal(err)
}
log.Println(user)
}
ติดตั้ง github.com/lib/pq
ใช้คำสั่ง
go get -u github.com/lib/pq
ทดสอบการทำงาน
go run main.go
ถ้าสำเร็จจะแสดงข้อมูลที่เพิ่มเข้าไป ในตัวอย่างคือ {1 Go Developers Thailand}
ตรวจสอบข้อมูลที่เพิ่มเข้าไป
ที่ pgAdmin 4 ดูข้อมูลที่เพิ่มเข้าไป movies -> View/Edit Data -> All Rows
pgAdmin 4 แสดงข้อมูลที่เพิ่มเข้าไป