Files
db_in_45_steps_go/0402/table.go
T
2026-08-04 21:39:01 +02:00

268 lines
5.6 KiB
Go

package db0402
import (
"encoding/json"
"errors"
"slices"
)
type DB struct {
KV KV
tables map[string]Schema
}
func (db *DB) Open() error {
db.tables = map[string]Schema{}
return db.KV.Open()
}
func (db *DB) Close() error { return db.KV.Close() }
func (db *DB) Select(schema *Schema, row Row) (ok bool, err error) {
key := row.EncodeKey(schema)
val, ok, err := db.KV.Get(key)
if err != nil || !ok {
return ok, err
}
if err = row.DecodeVal(schema, val); err != nil {
return false, err
}
return true, nil
}
func (db *DB) Insert(schema *Schema, row Row) (updated bool, err error) {
key := row.EncodeKey(schema)
val := row.EncodeVal(schema)
return db.KV.SetEx(key, val, ModeInsert)
}
func (db *DB) Upsert(schema *Schema, row Row) (updated bool, err error) {
key := row.EncodeKey(schema)
val := row.EncodeVal(schema)
return db.KV.SetEx(key, val, ModeUpsert)
}
func (db *DB) Update(schema *Schema, row Row) (updated bool, err error) {
key := row.EncodeKey(schema)
val := row.EncodeVal(schema)
return db.KV.SetEx(key, val, ModeUpdate)
}
func (db *DB) Delete(schema *Schema, row Row) (deleted bool, err error) {
key := row.EncodeKey(schema)
return db.KV.Del(key)
}
type SQLResult struct {
Updated int
Header []string
Values []Row
}
func (db *DB) ExecStmt(stmt interface{}) (r SQLResult, err error) {
switch ptr := stmt.(type) {
case *StmtCreatTable:
err = db.execCreateTable(ptr)
case *StmtSelect:
r.Header = ptr.cols
r.Values, err = db.execSelect(ptr)
case *StmtInsert:
r.Updated, err = db.execInsert(ptr)
case *StmtUpdate:
r.Updated, err = db.execUpdate(ptr)
case *StmtDelete:
r.Updated, err = db.execDelete(ptr)
default:
panic("unreachable")
}
return
}
func (db *DB) execCreateTable(stmt *StmtCreatTable) (err error) {
if _, err := db.GetSchema(stmt.table); err == nil {
return errors.New("duplicate table name")
}
schema := Schema{
Table: stmt.table,
Cols: stmt.cols,
}
if schema.PKey, err = lookupColumns(stmt.cols, stmt.pkey); err != nil {
return err
}
val, err := json.Marshal(schema)
check(err == nil)
if _, err = db.KV.Set([]byte("@schema_"+stmt.table), val); err != nil {
return err
}
db.tables[schema.Table] = schema
return nil
}
func (db *DB) GetSchema(table string) (Schema, error) {
schema, ok := db.tables[table]
if !ok {
val, ok, err := db.KV.Get([]byte("@schema_" + table))
if err == nil && ok {
err = json.Unmarshal(val, &schema)
}
if err != nil {
return Schema{}, err
}
if !ok {
return Schema{}, errors.New("table is not found")
}
db.tables[table] = schema
}
return schema, nil
}
func lookupColumns(cols []Column, names []string) (indices []int, err error) {
for _, name := range names {
idx := slices.IndexFunc(cols, func(col Column) bool {
return col.Name == name
})
if idx < 0 {
return nil, errors.New("column is not found")
}
indices = append(indices, idx)
}
return
}
func makePKey(schema *Schema, pkey []NamedCell) (Row, error) {
if len(schema.PKey) != len(pkey) {
return nil, errors.New("not primary key")
}
row := schema.NewRow()
for _, idx1 := range schema.PKey {
col := schema.Cols[idx1]
idx2 := slices.IndexFunc(pkey, func(expr NamedCell) bool {
return expr.column == col.Name && expr.value.Type == col.Type
})
if idx2 < 0 {
return nil, errors.New("not primary key")
}
row[idx1] = pkey[idx2].value
}
return row, nil
}
func subsetRow(row Row, indices []int) (out Row) {
for _, idx := range indices {
out = append(out, row[idx])
}
return
}
func (db *DB) execSelect(stmt *StmtSelect) ([]Row, error) {
schema, err := db.GetSchema(stmt.table)
if err != nil {
return nil, err
}
indices, err := lookupColumns(schema.Cols, stmt.cols)
if err != nil {
return nil, err
}
row, err := makePKey(&schema, stmt.keys)
if err != nil {
return nil, err
}
if ok, err := db.Select(&schema, row); err != nil || !ok {
return nil, err
}
row = subsetRow(row, indices)
return []Row{row}, nil
}
func (db *DB) execInsert(stmt *StmtInsert) (count int, err error) {
schema, err := db.GetSchema(stmt.table)
if err != nil {
return 0, err
}
if len(schema.Cols) != len(stmt.value) {
return 0, errors.New("schema mismatch")
}
for i := range schema.Cols {
if schema.Cols[i].Type != stmt.value[i].Type {
return 0, errors.New("schema mismatch")
}
}
updated, err := db.Insert(&schema, stmt.value)
if err != nil {
return 0, err
}
if updated {
count++
}
return count, nil
}
func fillNonPKey(schema *Schema, updates []NamedCell, out Row) error {
for _, expr := range updates {
idx := slices.IndexFunc(schema.Cols, func(col Column) bool {
return col.Name == expr.column && col.Type == expr.value.Type
})
if idx < 0 || slices.Contains(schema.PKey, idx) {
return errors.New("cannot update column")
}
out[idx] = expr.value
}
return nil
}
func (db *DB) execUpdate(stmt *StmtUpdate) (count int, err error) {
schema, err := db.GetSchema(stmt.table)
if err != nil {
return 0, err
}
row, err := makePKey(&schema, stmt.keys)
if err != nil {
return 0, err
}
if ok, err := db.Select(&schema, row); err != nil || !ok {
return 0, err
}
if err = fillNonPKey(&schema, stmt.value, row); err != nil {
return 0, err
}
updated, err := db.Update(&schema, row)
if err != nil {
return 0, err
}
if updated {
count++
}
return count, nil
}
func (db *DB) execDelete(stmt *StmtDelete) (count int, err error) {
schema, err := db.GetSchema(stmt.table)
if err != nil {
return 0, err
}
row, err := makePKey(&schema, stmt.keys)
if err != nil {
return 0, err
}
updated, err := db.Delete(&schema, row)
if err != nil {
return 0, err
}
if updated {
count++
}
return count, nil
}
// QzBQWVJJOUhU https://trialofcode.org/