/
object.go
151 lines (125 loc) · 3.39 KB
/
object.go
1
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
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
package db
import (
"database/sql"
"errors"
"reflect"
"regexp"
"strings"
"time"
)
type Trail[idT, shardKeyT ~string] struct {
ID idT
ShardKey shardKeyT
CreatedAt time.Time
CreatedBy string
UpdatedAt time.Time
UpdatedBy string
}
type Object[idT, shardKeyT ~string] interface {
Trail() Trail[idT, shardKeyT]
}
type dbTable struct {
ObjectType reflect.Type
ObjectTypeName string
RuntimeTableName string
HistoryTableName string
LockTableName string
Sharding bool
}
const (
historySuffix = "_history"
lockSuffix = "_lock"
)
var (
matchFirstCap = regexp.MustCompile("(.)([A-Z][a-z]+)")
matchAllCap = regexp.MustCompile("([a-z0-9])([A-Z])")
typeToTable = map[reflect.Type]dbTable{}
ErrObjectTypeNotRegistered = errors.New("object type not registered - use RegisterObject before using specific type")
ErrObjectNotUsingShards = errors.New("object not using shards while shards are supplied to query")
)
func toSnakeCase(str string) string {
snake := matchFirstCap.ReplaceAllString(str, "${1}_${2}")
snake = matchAllCap.ReplaceAllString(snake, "${1}_${2}")
return strings.ToLower(snake)
}
func dbsForShardKeys[shardKeyT ~string](sks ...shardKeyT) []*sql.DB {
switch len(sks) {
case 1:
return []*sql.DB{dbByShardKey(string(sks[0]))}
case 0:
return Shards()
default:
sksStr := make([]string, len(sks))
for i, sk := range sks {
sksStr[i] = string(sk)
}
return dbsByShardKeys(sksStr...)
}
}
func RegisterObject[objT Object[idT, shardKeyT], idT ~string, shardKeyT ~string](sharding bool, indexes ...string) (err error) {
obj := new(objT)
objType := reflect.TypeOf(*obj)
objTypeName := objType.Name()
runtimeTableName := toSnakeCase(objType.Name())
historyTableName := runtimeTableName + historySuffix
lockTableName := runtimeTableName + lockSuffix
createScript := `CREATE TABLE IF NOT EXISTS "` + runtimeTableName + `" (
"id" text PRIMARY KEY,
"created_at" timestamp DEFAULT now(),
"created_by" text NOT NULL,
"updated_at" timestamp DEFAULT now(),
"updated_by" text NOT NULL,
"object" JSONB NULL
);
CREATE TABLE IF NOT EXISTS "` + historyTableName + `" (
"id" text NOT NULL,
"created_at" timestamp DEFAULT now(),
"created_by" text NOT NULL,
"updated_at" timestamp DEFAULT now(),
"updated_by" text NOT NULL,
"object" JSONB NULL
);
CREATE TABLE IF NOT EXISTS "` + lockTableName + `" (
"id" text PRIMARY KEY,
"created_at" timestamp DEFAULT now(),
"description" text NOT NULL
);
`
for _, index := range indexes {
createScript += `CREATE INDEX IF NOT EXISTS "` + runtimeTableName + `_` + index + `"
ON "` + runtimeTableName + `" USING gin (("object"->'` + index + `'));
`
}
if sharding {
for _, shard := range Shards() {
_, err = shard.Exec(createScript)
if err != nil {
return
}
}
} else {
_, err = Default().Exec(createScript)
if err != nil {
return
}
}
typeToTable[objType] = dbTable{
ObjectType: objType,
ObjectTypeName: objTypeName,
RuntimeTableName: runtimeTableName,
HistoryTableName: historyTableName,
LockTableName: lockTableName,
Sharding: sharding,
}
return
}
func NewObjectSet[objT Object[idT, shardKeyT], idT ~string, shardKeyT ~string]() ObjectSet[objT, idT, shardKeyT] {
obj := new(objT)
objType := reflect.TypeOf(*obj)
return ObjectSet[objT, idT, shardKeyT]{
objType: objType,
}
}
type ObjectSet[objT Object[idT, shardKeyT], idT, shardKeyT ~string] struct {
objType reflect.Type
}