Repository navigation
Expand file tree
/
Copy pathdb.go
More file actions
272 lines (243 loc) Β· 7.16 KB
/
Copy pathdb.go
File metadata and controls
272 lines (243 loc) Β· 7.16 KB
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
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
package oro
import (
"context"
"time"
)
// DB is an immutable query session over a shared runtime.
//
// Methods that change session state return a shallow clone, so it is safe to
// reuse a base DB across goroutines.
type DB struct {
runtime *Runtime
session sessionState
}
// RawQuery is a raw SQL query with optional cache and timeout settings.
type RawQuery struct {
db *DB
raw RawSpec
cache CacheSpec
timeout time.Duration
}
// Open creates a DB from Config, initializes runtime components, and installs
// configured extensions.
func Open(config Config) (*DB, error) {
factory := resolveFactory(config)
conns, err := factory.NewConnectionManager(config)
if err != nil {
return nil, err
}
rt := &Runtime{
Config: config,
Conns: conns,
Registry: factory.NewRegistry(config),
Events: factory.NewEventBus(config),
Cache: config.Cache,
Logger: config.Logger,
tableNames: newTableNameResolver(config),
SQLCache: newSQLCache(config.sqlCacheSize()),
ScanCache: newModelScanCache(config.scanCacheSize()),
}
rt.SchemaParser = factory.NewSchemaParser(rt)
rt.Planner = factory.NewQueryPlanner(rt)
rt.Executor = factory.NewExecutor(rt)
rt.Mapper = factory.NewMapper(rt)
rt.Syncer = factory.NewSyncer(rt)
rt.Serializer = factory.NewSerializer(rt)
db := &DB{
runtime: rt,
session: sessionState{connection: config.defaultConnectionName()},
}
if err := installExtensions(db, config.Extensions); err != nil {
_ = db.Close(context.Background())
return nil, err
}
return db, nil
}
// Close closes all owned database connections.
func (db *DB) Close(ctx context.Context) error {
if db == nil || db.runtime == nil || db.runtime.Conns == nil {
return nil
}
return db.runtime.Conns.Close()
}
// Connection returns a DB clone pinned to the named connection.
func (db *DB) Connection(name string) *DB {
clone := *db
clone.session.connection = name
clone.session.manualConnection = true
return &clone
}
// WithExtension returns a DB clone carrying extension-specific session state.
func (db *DB) WithExtension(name string, state any) *DB {
clone := *db
clone.session.extensions = cloneExtensionState(db.session.extensions)
clone.session.extensions[name] = state
return &clone
}
// ExtensionState returns extension-specific session state by name.
func (db *DB) ExtensionState(name string) (any, bool) {
if db == nil || db.session.extensions == nil {
return nil, false
}
value, ok := db.session.extensions[name]
return value, ok
}
// Use starts a model query for T. Model queries use Go field names in
// conditions, ordering, selection, and relation definitions.
func (db *DB) Use[T any]() *ModelQuery[T] {
return &ModelQuery[T]{
db: db,
spec: QuerySpec{
Connection: db.session.connection,
},
}
}
// Table starts a table query by database table name. Table queries use database
// column names.
func (db *DB) Table(name string) *TableQuery {
return &TableQuery{
db: db,
spec: QuerySpec{
Connection: db.session.connection,
Table: name,
},
}
}
// TableName returns the physical table name after applying the configured
// prefix resolver.
func (db *DB) TableName(name string) string {
return tableNames(db).Physical(name)
}
// SchemaOf returns the parsed schema for model type T, registering it lazily
// when necessary.
func SchemaOf[T any](db *DB) (*ModelSchema, error) {
return schemaForModel[T](db)
}
// SchemaOf returns the parsed schema for a model value, registering it lazily
// when necessary.
func (db *DB) SchemaOf(model Definer) (*ModelSchema, error) {
if db == nil || db.runtime == nil || db.runtime.SchemaParser == nil {
return nil, &Error{Op: "schema", Kind: ErrInvalidArgument}
}
if model == nil {
return nil, &Error{Op: "schema", Kind: ErrInvalidArgument}
}
if db.runtime.Registry != nil {
if schema, ok := db.runtime.Registry.Get(model); ok {
return schema, nil
}
}
schema, err := db.runtime.SchemaParser.Parse(model)
if err != nil {
return nil, err
}
if db.runtime.Registry != nil {
db.runtime.Registry.Register(schema, model)
}
return schema, nil
}
// From starts a table query from a structured source such as a subquery.
func (db *DB) From(source Source) *TableQuery {
return &TableQuery{
db: db,
spec: QuerySpec{
Connection: db.session.connection,
From: source.sourceAST(),
},
}
}
// Raw starts a raw SQL query. Raw SQL uses the target driver's native
// placeholders.
func (db *DB) Raw(sql string, args ...any) *RawQuery {
return &RawQuery{
db: db,
raw: RawSpec{
SQL: sql,
Args: args,
},
}
}
// Register parses and registers model schemas and their relation methods.
func (db *DB) Register(models ...Definer) error {
parsed := make([]*ModelSchema, 0, len(models))
for _, model := range models {
schema, err := db.runtime.SchemaParser.Parse(model)
if err != nil {
return err
}
if schema.Connection != "" {
if _, err := db.runtime.Conns.Get(schema.Connection); err != nil {
return err
}
}
if schema.ShardGroup != "" {
if _, err := shardConfigForSchema(db, schema); err != nil {
return err
}
}
db.runtime.Registry.Register(schema, model)
parsed = append(parsed, schema)
}
for index, model := range models {
if err := registerModelRelations(db.runtime.Registry, model, parsed[index]); err != nil {
return err
}
}
return nil
}
// Sync synchronizes registered model schemas to database tables.
func (db *DB) Sync(ctx context.Context) error {
if db == nil || db.runtime == nil || db.runtime.Syncer == nil {
return &Error{Op: "sync", Kind: ErrInvalidArgument}
}
return db.runtime.Syncer.Sync(ctx, db)
}
// Cache enables result caching for the raw query for ttl.
func (query *RawQuery) Cache(ttl time.Duration) *RawQuery {
clone := *query
clone.cache.Enabled = true
clone.cache.TTL = int64(ttl)
return &clone
}
// CacheKey sets an explicit cache key for the raw query.
func (query *RawQuery) CacheKey(key string) *RawQuery {
clone := *query
clone.cache.Key = key
return &clone
}
// CacheTags adds cache invalidation tags to the raw query.
func (query *RawQuery) CacheTags(tags ...string) *RawQuery {
clone := *query
clone.cache.Tags = append(clone.cache.Tags, tags...)
return &clone
}
// Timeout sets a per-query timeout for the raw query.
func (query *RawQuery) Timeout(timeout time.Duration) *RawQuery {
clone := *query
clone.timeout = timeout
return &clone
}
// First returns the first raw result row or nil when no row is found.
func (query *RawQuery) First(ctx context.Context) (Map, error) {
rows, err := query.Get(ctx)
if err != nil || len(rows) == 0 {
return nil, err
}
return rows[0], nil
}
// Get returns all raw result rows as Map values.
func (query *RawQuery) Get(ctx context.Context) ([]Map, error) {
return execRawRows(ctx, query.db, query.raw, query.cache, query.timeout)
}
// Stream opens a streaming raw row iterator.
func (query *RawQuery) Stream(ctx context.Context) (Stream[Map], error) {
rows, err := streamRaw(ctx, query.db, query.raw, query.timeout)
if err != nil {
return nil, err
}
return &mapStream{rows: rows}, nil
}
// Exec executes raw SQL and returns rows affected.
func (query *RawQuery) Exec(ctx context.Context) (int64, error) {
return execRaw(ctx, query.db, query.raw, query.timeout)
}