-
Notifications
You must be signed in to change notification settings - Fork 6
/
multitenancy.go
676 lines (517 loc) · 23.1 KB
/
multitenancy.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
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
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
/*
Package multitenancy provides a Go framework for building multi-tenant applications, streamlining
tenant management and model migrations. It abstracts multitenancy complexities through a unified,
database-agnostic API compatible with GORM.
The framework supports two primary multitenancy strategies:
- Shared Database, Separate Schemas: This approach allows for data isolation and schema
customization per tenant within a single database instance, simplifying maintenance and
resource utilization.
- Separate Databases: This strategy ensures complete data isolation by utilizing separate
databases for each tenant, making it suitable for applications with stringent data security
requirements.
# Usage
The following sections provide an overview of the package's features and usage instructions for
implementing multitenancy in Go applications.
# Opening a Database Connection
The package supports multitenancy for both PostgreSQL and MySQL databases, offering three methods
for establishing a new database connection with multitenancy support:
# Approach 1: OpenDB with URL (Recommended for Most Users)
[OpenDB] allows opening a database connection using a URL-like DSN string, providing a flexible
and easy way to switch between drivers. This method abstracts the underlying driver mechanics,
offering a straightforward connection process and a unified, database-agnostic API through the
returned [*DB] instance, which embeds the [gorm.DB] instance.
For constructing the DSN string, refer to the driver-specific documentation for the required
parameters and formats.
import (
_ "github.com/bartventer/gorm-multitenancy/<driver>/v8"
multitenancy "github.com/bartventer/gorm-multitenancy/v8"
)
func main() {
url := "<driver>://user:password@host:port/dbname"
db, err := multitenancy.OpenDB(context.Background(), url)
if err != nil {...}
db.RegisterModels(ctx, ...) // Access to a database-agnostic API with GORM features
}
This approach is useful for applications that need to dynamically switch between
different database drivers or configurations without changing the codebase.
Postgres:
import (
_ "github.com/bartventer/gorm-multitenancy/postgres/v8"
multitenancy "github.com/bartventer/gorm-multitenancy/v8"
)
func main() {
url := "postgres://user:password@localhost:5432/dbname?sslmode=disable"
db, err := multitenancy.OpenDB(context.Background(), url)
if err != nil {...}
}
MySQL:
import (
_ "github.com/bartventer/gorm-multitenancy/mysql/v8"
multitenancy "github.com/bartventer/gorm-multitenancy/v8"
)
func main() {
url := "mysql://user:password@tcp(localhost:3306)/dbname"
db, err := multitenancy.OpenDB(context.Background(), url)
if err != nil {...}
}
# Approach 2: Unified API
[Open] with a supported driver offers a unified, database-agnostic API for managing tenant-specific
and shared data, embedding the [gorm.DB] instance. This method allows developers to initialize and
configure the dialect themselves before opening the database connection, providing granular control
over the database connection and configuration. It facilitates seamless switching between database
drivers while maintaining access to GORM's full functionality.
import (
multitenancy "github.com/bartventer/gorm-multitenancy/v8"
"github.com/bartventer/gorm-multitenancy/<driver>/v8"
)
func main() {
dsn := "<driver-specific DSN>"
db, err := multitenancy.Open(<driver>.Open(dsn))
if err != nil {...}
db.RegisterModels(ctx, ...) // Access to a database-agnostic API with GORM features
}
Postgres:
import (
multitenancy "github.com/bartventer/gorm-multitenancy/v8"
"github.com/bartventer/gorm-multitenancy/postgres/v8"
)
func main() {
dsn := "postgres://user:password@localhost:5432/dbname?sslmode=disable"
db, err := multitenancy.Open(postgres.Open(dsn))
}
MySQL:
import (
multitenancy "github.com/bartventer/gorm-multitenancy/v8"
"github.com/bartventer/gorm-multitenancy/mysql/v8"
)
func main() {
dsn := "user:password@tcp(localhost:3306)/dbname"
db, err := multitenancy.Open(mysql.Open(dsn))
}
# Approach 3: Direct Driver API
For direct access to the [gorm.DB] API and multitenancy features for specific tasks, this approach
allows invoking driver-specific functions directly. It provides a lower level of abstraction,
requiring manual management of database-specific operations. Prior to version 8, this was the
only method available for using the package.
Postgres:
import (
"github.com/bartventer/gorm-multitenancy/postgres/v8"
"gorm.io/gorm"
)
func main() {
db, err := gorm.Open(postgres.Open(dsn), &gorm.Config{})
// Directly call driver-specific functions
postgres.RegisterModels(db, ...)
}
MySQL:
import (
"github.com/bartventer/gorm-multitenancy/mysql/v8"
"gorm.io/gorm"
)
func main() {
db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{})
// Directly call driver-specific functions
mysql.RegisterModels(db, ...)
}
# Declaring Models
All models must implement [driver.TenantTabler], which extends GORM's Tabler interface. This
extension allows models to define their table name and indicate whether they are shared across
tenants.
Public Models:
These are models which are shared across tenants.
[driver.TenantTabler.TableName] should return the table name prefixed with 'public.'.
[driver.TenantTabler.IsSharedModel] should return true.
type Tenant struct { multitenancy.TenantModel}
func (Tenant) TableName() string { return "public.tenants" } // note the 'public.' prefix
func (Tenant) IsSharedModel() bool { return true }
Tenant-Specific Models:
These models are specific to a single tenant and should not be shared across tenants.
[driver.TenantTabler.TableName] should return the table name without any prefix.
[driver.TenantTabler.IsSharedModel] should return false.
type Book struct {
gorm.Model
Title string
TenantSchema string
Tenant Tenant `gorm:"foreignKey:TenantSchema;references:SchemaName"`
}
func (Book) TableName() string { return "books" } // no 'public.' prefix
func (Book) IsSharedModel() bool { return false }
Tenant Model:
This package provides a [TenantModel] struct that can be embedded in any public model that requires
tenant scoping, enriching it with essential fields for managing tenant-specific information. This
structure incorporates fields for the tenant's domain URL and schema name, facilitating the linkage
of tenant-specific models to their respective schemas.
type Tenant struct { multitenancy.TenantModel }
func (Tenant) TableName() string { return "public.tenants" }
func (Tenant) IsSharedModel() bool { return true }
# Model Registration
Before performing any migrations or operations on tenant-specific models, the models
must be registered with the DB instance using [DB.RegisterModels].
import (
multitenancy "github.com/bartventer/gorm-multitenancy/v8"
_ "github.com/bartventer/gorm-multitenancy/postgres/v8"
)
func main() {
db, err := multitenancy.OpenDB(ctx, dsn)
if err != nil {...}
db.RegisterModels(ctx, &Tenant{}, &Book{})
}
Postgres Adapter:
Use [postgres.RegisterModels] to register models.
import "github.com/bartventer/gorm-multitenancy/postgres/v8"
postgres.RegisterModels(db, &Tenant{}, &Book{})
MySQL Adapter:
Use [mysql.RegisterModels] to register models.
import "github.com/bartventer/gorm-multitenancy/mysql/v8"
mysql.RegisterModels(db, &Tenant{}, &Book{})
# Migration Strategy
To ensure data integrity and schema isolation across tenants,[gorm.DB.AutoMigrate] has been
disabled. Instead, use the provided shared and tenant-specific migration methods.
[driver.ErrInvalidMigration] is returned if the `AutoMigrate` method is called directly.
# Concurrent Migrations
To ensure tenant isolation and facilitate concurrent migrations, driver-specific locking mechanisms
are used. These locks prevent concurrent migrations from interfering with each other, ensuring that
only one migration process can run at a time for a given tenant. Consult the driver-specific
documentation for more information.
# Shared Model Migrations
After registering models, shared models are migrated using [DB.MigrateSharedModels].
import (
"context"
multitenancy "github.com/bartventer/gorm-multitenancy/v8"
_ "github.com/bartventer/gorm-multitenancy/postgres/v8"
)
func main() {
db, err := multitenancy.OpenDB(ctx, dsn)
if err != nil {...}
db.RegisterModels(ctx, &Tenant{}, &Book{})
db.MigrateSharedModels(ctx)
}
Postgres Adapter:
Use [postgres.MigrateSharedModels] to migrate shared models.
import "github.com/bartventer/gorm-multitenancy/postgres/v8"
postgres.MigrateSharedModels(db)
MySQL Adapter:
Use [mysql.MigrateSharedModels] to migrate shared models.
import "github.com/bartventer/gorm-multitenancy/mysql/v8"
mysql.MigrateSharedModels(db)
# Tenant-Specific Model Migrations
After registering models, tenant-specific models are migrated using [DB.MigrateTenantModels].
import (
"context"
multitenancy "github.com/bartventer/gorm-multitenancy/v8"
_ "github.com/bartventer/gorm-multitenancy/postgres/v8"
)
func main() {
db, err := multitenancy.OpenDB(ctx, dsn)
if err != nil {...}
db.RegisterModels(ctx, &Tenant{}, &Book{})
db.MigrateSharedModels(ctx)
// Assuming we have a tenant with schema name 'tenant1'
db.MigrateTenantModels(ctx, "tenant1")
}
Postgres Adapter:
Use [postgres.MigrateTenantModels] to migrate tenant-specific models.
import "github.com/bartventer/gorm-multitenancy/postgres/v8"
postgres.MigrateTenantModels(db, "tenant1")
MySQL Adapter:
Use [mysql.MigrateTenantModels] to migrate tenant-specific models.
import "github.com/bartventer/gorm-multitenancy/mysql/v8"
mysql.MigrateTenantModels(db, "tenant1")
# Offboarding Tenants
When a tenant is removed from the system, the tenant-specific schema and associated tables
should be cleaned up using [DB.OffboardTenant].
import (
"context"
multitenancy "github.com/bartventer/gorm-multitenancy/v8"
_ "github.com/bartventer/gorm-multitenancy/postgres/v8"
)
func main() {
db, err := multitenancy.OpenDB(ctx, dsn)
if err != nil {...}
db.RegisterModels(ctx, &Tenant{}, &Book{})
db.MigrateSharedModels(ctx)
// Assuming we have a tenant with schema name 'tenant1'
db.MigrateTenantModels(ctx, "tenant1")
db.OffboardTenant(ctx, "tenant1") // Drop the tenant schema and associated tables
}
Postgres Adapter:
Use [postgres.DropSchemaForTenant] to offboard a tenant.
import "github.com/bartventer/gorm-multitenancy/postgres/v8"
postgres.DropSchemaForTenant(db, "tenant1")
MySQL Adapter:
Use [mysql.DropDatabaseForTenant] to offboard a tenant.
import "github.com/bartventer/gorm-multitenancy/mysql/v8"
mysql.DropDatabaseForTenant(db, "tenant1")
# Tenant Context Configuration
[DB.UseTenant] configures the database for operations specific to a tenant,
abstracting database-specific operations for tenant context configuration. This method
returns a reset function to revert the database context and an error if the operation fails.
import (
"context"
multitenancy "github.com/bartventer/gorm-multitenancy/v8"
_ "github.com/bartventer/gorm-multitenancy/postgres/v8"
)
func main() {
db, err := multitenancy.OpenDB(ctx, dsn)
if err != nil {...}
db.RegisterModels(ctx, &Tenant{}, &Book{})
db.MigrateSharedModels(ctx)
// Assuming we have a tenant with schema name 'tenant1'
reset, err := db.UseTenant(ctx, "tenant1")
if err != nil {...}
defer reset() // reset to the default search path
// ... do operations with the search path set to 'tenant1'
db.Create(&Book{Title: "The Great Gatsby"})
db.Find(&Book{})
db.Delete(&Book{})
}
Postgres Adapter:
Use [postgres.SetSearchPath] to set the search path for a tenant.
import "github.com/bartventer/gorm-multitenancy/postgres/v8"
reset, err := postgres.SetSearchPath(ctx, db, "tenant1")
if err != nil {...}
defer reset() // reset to the default search path
db.Create(&Book{Title: "The Great Gatsby"})
MySQL Adapter:
Use [mysql.UseDatabase] function to set the database for a tenant.
import "github.com/bartventer/gorm-multitenancy/mysql/v8"
reset, err := mysql.UseDatabase(ctx, db, "tenant1")
if err != nil {...}
defer reset() // reset to the default database
db.Create(&Book{Title: "The Great Gatsby"})
# Foreign Key Constraints
For the most part, foreign key constraints work as expected, but there are some restrictions and
considerations to keep in mind when working with multitenancy. The following guidelines outline
the supported relationships between tables:
Between Tables in the Public Schema:
- No foreign key constraints restrictions
- E.g., `public.events` -> `public.locations`.
From Public Schema Tables to Tenant-Specific Tables:
- Avoid foreign key constraints to maintain isolation and integrity.
- E.g., `public.users` should not reference `orders` in a tenant-specific schema.
From Tenant-Specific Tables to Public Schema Tables:
- Allowed, enabling references to shared resources.
- E.g., `invoices` in a tenant schema -> `public.payment_methods`.
Between Tenant-Specific Tables:
- Constraints allowed within the same tenant schema for encapsulation.
- E.g., `projects` -> `employees` within a tenant's schema.
# Example
package main
import (
"context"
_ "github.com/bartventer/gorm-multitenancy/postgres/v8"
multitenancy "github.com/bartventer/gorm-multitenancy/v8"
)
type Tenant struct{ multitenancy.TenantModel }
func (Tenant) TableName() string { return "public.tenants" }
func (Tenant) IsSharedModel() bool { return true }
type Book struct {
gorm.Model
Title string
TenantSchema string
Tenant Tenant `gorm:"foreignKey:TenantSchema;references:SchemaName"`
}
func (Book) TableName() string { return "books" }
func (Book) IsSharedModel() bool { return false }
func main() {
ctx := context.Background()
dsn := "postgres://user:password@localhost:5432/dbname?sslmode=disable"
db, err := multitenancy.OpenDB(ctx, dsn)
if err != nil {
panic(err)
}
if err := db.RegisterModels(ctx, &Tenant{}, &Book{}); err != nil {
panic(err)
}
if err := db.MigrateSharedModels(ctx); err != nil {
panic(err)
}
// Create and manage tenants as needed
tenant := &Tenant{
TenantModel: multitenancy.TenantModel{
DomainURL: "tenant1.example.com",
SchemaName: "tenant1",
},
}
// Create a tenant in the default public/shared schema
if err := db.Create(tenant).Error; err != nil {
panic(err)
}
// Migrate models under the tenant schema
if err := db.MigrateTenantModels(ctx, tenant.SchemaName); err != nil {
panic(err)
}
// Create a book under the tenant schema
if err := createBookHandler(ctx, db, "The Great Gatsby", tenant.SchemaName); err != nil {
panic(err)
}
// Drop the tenant schema and associated tables
if err := db.OffboardTenant(ctx, tenant.SchemaName); err != nil {
panic(err)
}
}
func createBookHandler(ctx context.Context, tx *multitenancy.DB, title, tenantID string) error {
// Set the tenant context for the current operation(s)
reset, err := tx.UseTenant(ctx, tenantID)
if err != nil {
return err
}
defer reset()
// Create a book under the tenant schema
b := &Book{
Title: title,
TenantSchema: tenantID,
}
// ... do operations with the search path set to <tenantID>
return tx.Create(b).Error
}
See [the example application] for a more comprehensive demonstration of the framework's
capabilities.
# Security Considerations
Always sanitize input to prevent SQL injection vulnerabilities. This framework does not perform any
validation on the database name or schema name parameters. It is the responsibility of the caller to
ensure that these parameters are sanitized. To facilitate this, the framework provides the following
utilities:
- [pkg/namespace/Validate] to verify tenant names against all supported drivers (MySQL and
PostgreSQL), ensuring both scheme and database name adhere to expected formats.
- [middleware/nethttp/ExtractSubdomain] to extract subdomains from HTTP requests, which can be
used to derive tenant names.
# Design Strategy
For a detailed technical overview of SQL design strategies adopted by the framework, see the
[STRATEGY.md] file.
[postgres.RegisterModels]: https://pkg.go.dev/github.com/bartventer/gorm-multitenancy/postgres/v8#RegisterModels
[mysql.RegisterModels]: https://pkg.go.dev/github.com/bartventer/gorm-multitenancy/mysql/v8#RegisterModels
[postgres.MigrateSharedModels]: https://pkg.go.dev/github.com/bartventer/gorm-multitenancy/postgres/v8#MigrateSharedModels
[mysql.MigrateSharedModels]: https://pkg.go.dev/github.com/bartventer/gorm-multitenancy/mysql/v8#MigrateSharedModels
[postgres.MigrateTenantModels]: https://pkg.go.dev/github.com/bartventer/gorm-multitenancy/postgres/v8#MigrateTenantModels
[mysql.MigrateTenantModels]: https://pkg.go.dev/github.com/bartventer/gorm-multitenancy/mysql/v8#MigrateTenantModels
[postgres.DropSchemaForTenant]: https://pkg.go.dev/github.com/bartventer/gorm-multitenancy/postgres/v8#OffboardTenant
[mysql.DropDatabaseForTenant]: https://pkg.go.dev/github.com/bartventer/gorm-multitenancy/mysql/v8#OffboardTenant
[postgres.SetSearchPath]: https://pkg.go.dev/github.com/bartventer/gorm-multitenancy/postgres/v8#UseTenant
[mysql.UseDatabase]: https://pkg.go.dev/github.com/bartventer/gorm-multitenancy/mysql/v8#UseDatabase
[the example application]: https://github.com/bartventer/gorm-multitenancy/tree/master/examples/README.md
[pkg/namespace/Validate]: https://pkg.go.dev/github.com/bartventer/gorm-multitenancy/v8/pkg/namespace#Validate
[middleware/nethttp/ExtractSubdomain]: https://pkg.go.dev/github.com/bartventer/gorm-multitenancy/middleware/nethttp/v8#ExtractSubdomain
[STRATEGY.md]: https://github.com/bartventer/gorm-multitenancy/tree/master/docs/STRATEGY.md
*/
package multitenancy
import (
"context"
"database/sql"
"github.com/bartventer/gorm-multitenancy/v8/pkg/driver"
"gorm.io/gorm"
)
type (
// DB wraps a GORM DB connection, integrating support for multitenancy operations.
// It provides a unified interface for managing tenant-specific and shared data within
// a multi-tenant application, leveraging GORM's ORM capabilities for database operations.
DB struct {
*gorm.DB
driver driver.DBFactory
}
)
// CurrentTenant returns the identifier for the current tenant context or an empty string
// if no context is set.
func (db *DB) CurrentTenant(ctx context.Context) string {
return db.driver.CurrentTenant(ctx, db.DB)
}
// RegisterModels registers GORM model structs for multitenancy support, preparing models for
// tenant-specific operations.
//
// Not safe for concurrent use by multiple goroutines. Call this method from your main function
// or during application initialization.
func (db *DB) RegisterModels(ctx context.Context, models ...driver.TenantTabler) error {
return db.driver.RegisterModels(ctx, db.DB, models...)
}
// MigrateSharedModels migrates all registered shared/public models.
//
// Safe for concurrent use by multiple goroutines ito ensuring data integrity and schema isolation.
func (db *DB) MigrateSharedModels(ctx context.Context) error {
return db.driver.MigrateSharedModels(ctx, db.DB)
}
// MigrateTenantModels migrates all registered tenant-specific models for the specified tenant.
// This method is intended to be used when onboarding a new tenant or updating an existing tenant's
// schema to match the latest model definitions.
//
// Safe for concurrent use by multiple goroutines ito ensuring data integrity and schema isolation.
func (db *DB) MigrateTenantModels(ctx context.Context, tenantID string) error {
return db.driver.MigrateTenantModels(ctx, db.DB, tenantID)
}
// OffboardTenant cleans up the database by dropping the tenant-specific schema and associated tables.
// This method is intended to be used after a tenant has been removed.
//
// Safe for concurrent use by multiple goroutines ito ensuring data integrity and schema isolation.
func (db *DB) OffboardTenant(ctx context.Context, tenantID string) error {
return db.driver.OffboardTenant(ctx, db.DB, tenantID)
}
// UseTenant configures the database for operations specific to a tenant. A reset function is returned
// to revert the database context to its original state. This method is intended to be used when
// performing operations specific to a tenant, such as creating, updating, or deleting tenant-specific
// data.
//
// Technically safe for concurrent use by multiple goroutines, but should not be used concurrently
// ito ensuring data integrity and schema isolation. Either use [DB.WithTenant], or ensure that this method is called
// within a transaction or from its own database connection.
func (db *DB) UseTenant(ctx context.Context, tenantID string) (reset func() error, err error) {
return db.driver.UseTenant(ctx, db.DB, tenantID)
}
// WithTenant executes the provided function within the context of a specific tenant, ensuring that
// the database operations are scoped to the tenant's schema. This method is intended to be used when
// performing a series of operations within a tenant context, such as creating, updating, or deleting
// tenant-specific data.
//
// Safe for concurrent use by multiple goroutines ito ensuring data integrity and schema isolation.
func (db *DB) WithTenant(ctx context.Context, tenantID string, fc func(tx *DB) error, opts ...*sql.TxOptions) (err error) {
tx := db.WithContext(ctx).Begin(opts...)
defer func() {
if tx.Error == nil {
err = tx.Commit().Error
} else {
err = tx.Rollback().Error
}
}()
reset, err := tx.UseTenant(ctx, tenantID)
if err != nil {
return err
}
defer reset()
return fc(NewDB(db.driver, tx.DB))
}
// NewDB creates a new [DB] instance using the provided [driver.DBFactory] and [gorm.DB]
// instance. This function is intended for use by custom [Adapter] implementations to
// create new instances of DB with multitenancy support. Not intended for direct
// use in application code.
func NewDB(d driver.DBFactory, tx *gorm.DB) *DB {
return &DB{
DB: tx,
driver: d,
}
}
// ======================================================================================
// The below methods have been overridden to return a new DB instance with the updated
// configuration, allowing for method chaining and preserving the multitenancy context.
// ======================================================================================
// Session returns a new copy of the DB, which has a new session with the configuration.
func (db *DB) Session(config *gorm.Session) *DB {
return NewDB(db.driver, db.DB.Session(config))
}
// Debug starts debug mode.
func (db *DB) Debug() *DB {
return NewDB(db.driver, db.DB.Debug())
}
// WithContext sets the context for the DB.
func (db *DB) WithContext(ctx context.Context) *DB {
return NewDB(db.driver, db.DB.WithContext(ctx))
}
// Transaction starts a transaction as a block, returns an error if there's any error
// within the block. If the function passed to tx returns an error, the transaction will
// be rolled back automatically, otherwise, the transaction will be committed.
func (db *DB) Transaction(fc func(tx *DB) error, opts ...*sql.TxOptions) (err error) {
return db.DB.Transaction(func(tx *gorm.DB) error {
return fc(NewDB(db.driver, tx))
}, opts...)
}
// Begin begins a transaction.
func (db *DB) Begin(opts ...*sql.TxOptions) *DB {
return NewDB(db.driver, db.DB.Begin(opts...))
}