สร้างโค้ดฐานข้อมูล Go ด้วย sqlc


sqlc เป็นเครื่องมือใหม่ที่ทำให้การทำงานกับ SQL ใน Go เป็นเรื่องสนุก

ปรับปรุงประสบการณ์ของนักพัฒนาในการทำงานกับฐานข้อมูลเชิงสัมพันธ์ได้อย่างมากโดยไม่ลดทอนความปลอดภัยของประเภทหรือประสิทธิภาพรันไทม์ ไม่ใช้แท็กโครงสร้าง ฟังก์ชัน mapper ที่เขียนด้วยลายมือ การสะท้อนสิ่งที่ไม่จำเป็น หรือเการพิ่ม dependencies ใหม่ให้กับโค้ดของคุณ ในความเป็นจริง มันยังรับประกันความถูกต้องและความปลอดภัยอีกด้วย

sqlc ทำสิ่งนี้ให้สำเร็จโดยใช้วิธีการที่แตกต่างกันโดยพื้นฐาน: รวบรวม SQL ลงในโค้ด Go ที่ปลอดภัยสำหรับการสร้างอย่างสมบูรณ์ 

วิธีใช้ sqlc มี 3 ขั้นตอน

  1. คุณเขียนคำสั่ง SQL
  2. คุณเรียกใช้ sqlc เพื่อสร้างโค้ด Go ที่แสดงอินเทอร์เฟซที่ปลอดภัยสำหรับข้อความค้นหาเหล่านั้น
  3. คุณเขียนโค้ดแอปพลิเคชันที่เรียกวิธีการสร้าง 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 แสดงข้อมูลที่เพิ่มเข้าไป

Leave a Reply

Your email address will not be published. Required fields are marked *