manage.go
69 lines1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
package database
import (
"fmt"
"strings"
)
// This file owns Collection setup: deriving a table from a struct, auto-creating
// and migrating it, and declaring indexes. It is the boot-time, declarative half
// of a Collection; collection.go owns the per-request CRUD half. Schema failures
// here panic — they are programmer/configuration errors surfaced at startup.
// ManageOption configures a Collection during creation.
type ManageOption[E any] func(*Collection[E])
// WithIndex creates a non-unique index on the given columns.
func WithIndex[E any](columns ...string) ManageOption[E] {
return func(c *Collection[E]) {
indexName := fmt.Sprintf("idx_%s_%s", strings.ToLower(c.table), strings.ToLower(strings.Join(columns, "_")))
query := fmt.Sprintf(`CREATE INDEX IF NOT EXISTS "%s" ON "%s"(%s)`, indexName, c.table, strings.Join(columns, ", "))
if _, err := c.db.Exec(query); err != nil {
panic(fmt.Sprintf("database: create index %s: %v", indexName, err))
}
}
}
// WithUniqueIndex creates a unique index on the given column(s). Pass multiple
// columns for a composite constraint, e.g. WithUniqueIndex("UserID", "TileID").
func WithUniqueIndex[E any](columns ...string) ManageOption[E] {
return func(c *Collection[E]) {
indexName := fmt.Sprintf("idx_%s_%s_unique", strings.ToLower(c.table), strings.ToLower(strings.Join(columns, "_")))
query := fmt.Sprintf(`CREATE UNIQUE INDEX IF NOT EXISTS "%s" ON "%s"(%s)`, indexName, c.table, strings.Join(columns, ", "))
if _, err := c.db.Exec(query); err != nil {
panic(fmt.Sprintf("database: create unique index %s: %v", indexName, err))
}
}
}
// Manage returns a Collection for the entity type, deriving the table name from
// the struct name. The table is created if absent and migrated (missing columns
// added) if present, then the options run. Schema is declarative and runs at
// startup, so failures panic.
func Manage[E any](db *Database, model *E, opts ...ManageOption[E]) *Collection[E] {
m := reflectType[E]()
c := &Collection[E]{db: db, table: m.Name(), mirror: m}
if !c.db.TableExists(c.table) {
if err := c.db.CreateTable(c.table, c.mirror.Columns()); err != nil {
panic(fmt.Sprintf("database: create table %s: %v", c.table, err))
}
} else {
existing := make(map[string]bool)
for _, name := range c.db.GetColumns(c.table) {
existing[name] = true
}
for _, col := range c.mirror.Columns() {
if !existing[col.Name] {
if err := c.db.AddColumn(c.table, col); err != nil {
panic(fmt.Sprintf("database: add column %s.%s: %v", c.table, col.Name, err))
}
}
}
}
for _, opt := range opts {
opt(c)
}
return c
}