reform
путь к лучшему ORM
Алексей Палажченко
mc² software
Цели database/sql
src/database/sql/doc.txt
• generic database API for SQL/SQL-like, feel like Go
• common cases, portable, no quirks
• consistent but flexible type conversions
• concurrency, thread safety, built-in pool
• push complexity to drivers via optional interfaces
Интерфейс
database/sql
• DB: Open, Close, Begin, Prepare, Driver
• DB, Stmt, Tx: Query, QueryRow, Exec
• Rows: Next, Scan, Err, Close
• Result: LastInsertId, RowsAffected
• NullBool, NullInt64, NullFloat64, NullString
• Scanner: Scan(src interface{}) error
INSERT
result, err := db.Exec(
"INSERT INTO users (name) "+
"VALUES ($1)",
"gopher"
)
SELECT
defer rows.Close()
for rows.Next() {
var name string
if e := rows.Scan(&name); e != nil {
log.Fatal(e)
}
fmt.Println(name)
}
if e := rows.Err(); e != nil {
log.Fatal(e)
}
Интерфейс
database/sql/driver
• Value: пустой интерфейс
• ValueConverter: ConvertValue(v interface{}) (Value,
error)
• Valuer: Value() (Value, error)
database/sql/driver.Value
• nil
• int64
• float64
• bool
• []byte (non-nil)
• string everywhere except from Rows.Next. #6497
• time.Time (боль с часовыми зонами)
Свои типы
func (j JSONText) Value() (driver.Value, error) {
if j == nil {
return nil, nil
}
var m json.RawMessage
err := json.Unmarshal(j, &m)
if err != nil {
return []byte{}, err
}
return []byte(j), nil
}
Свои типы
func (j *JSONText) Scan(value interface{}) error {
if value == nil {
*j = nil
return nil
}
v, ok := value.([]byte)
if !ok {
return fmt.Errorf("error")
}
*j = JSONText(append((*j)[0:0], v...))
return nil
}
Драйвера
• github.com/golang/go/wiki/SQLDrivers
• github.com/bradfitz/go-sql-test
Зачем ORM?
INSERT
result, err := db.Exec(
"INSERT INTO users (name) "+
"VALUES ($1)",
"gopher"
)
SELECT
defer rows.Close()
for rows.Next() {
var name string
if e := rows.Scan(&name); e != nil {
log.Fatal(e)
}
fmt.Println(name)
}
if e := rows.Err(); e != nil {
log.Fatal(e)
}
ORM
• Не-ORM / малые ORM (например, отображение
Scan строк в структуры)
• Большие ORM
ORM
func Save(m interface{}) error
ORM
Save(User{Name: "gopher"})
Save(&User{Name: "gopher"})
Save(nil)
Save(42)
Save("Batman!!")
Идея:
struct для данных
type Person struct {
ID int64 `sql:"id,omitempty"`
Name string `sql:"name,omitempty"`
}
Идея:
непустые интерфейсы
type Record interface {
Values() []interface{}
Pointers() []interface{}
PrimaryKeyPointer() interface{}
SetPrimaryKey(id interface{})
Table() Table
}
funс Save(record Record) error
Идея:
генерация кода
• struct и код из XML
• XML из information_schema
• struct пишется, код генерируется из него
Проблемы: Go vs SQL
• SQL: значения по-умолчанию
• SQL: отсутствие в запросе
• Go: zero value
person := &Person{
Name: "gopher",
}
if err := DB.Save(person); err != nil {
log.Fatal(err)
}
Что почитать
• Документацию database/sql/…
• Код database/sql/…
• github.com/mc2soft/pq-types
• github.com/AlekSi/reform
• https://groups.google.com/forum/#!forum/golang-ru
• http://www.meetup.com/Golang-Moscow/
• http://4gophers.ru
• http://4gophers.ru/slack
• https://golangshow.com

Reform: путь к лучшему ORM