Skip to content

Commit ce0750d

Browse files
committed
refactor: migrate Catalog, Builder, and LintContext to CatalogDB interface
1 parent 804d64c commit ce0750d

9 files changed

Lines changed: 49 additions & 44 deletions

File tree

mdl/catalog/builder.go

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@
33
package catalog
44

55
import (
6-
"database/sql"
76
"fmt"
87
"strings"
98
"time"
@@ -79,7 +78,7 @@ type Builder struct {
7978
snapshot *Snapshot
8079
progress ProgressFunc
8180
hierarchy *hierarchy
82-
tx *sql.Tx // Transaction for batched inserts
81+
tx CatalogTx // Transaction for batched inserts
8382
fullMode bool // If true, do full parsing (activities/widgets)
8483
sourceMode bool // If true, build source FTS table (implies full)
8584
describeFunc DescribeFunc

mdl/catalog/catalog.go

Lines changed: 23 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -8,13 +8,11 @@ import (
88
"fmt"
99
"strings"
1010
"time"
11-
12-
_ "modernc.org/sqlite"
1311
)
1412

1513
// Catalog provides SQL querying over Mendix project metadata.
1614
type Catalog struct {
17-
db *sql.DB
15+
db CatalogDB
1816
projectID string
1917
projectName string
2018
mendixVersion string
@@ -58,17 +56,15 @@ type ProgressFunc func(table string, count int)
5856

5957
// New creates a new catalog with an in-memory SQLite database.
6058
func New() (*Catalog, error) {
61-
db, err := sql.Open("sqlite", ":memory:")
59+
db, err := NewSqliteCatalogDB()
6260
if err != nil {
6361
return nil, fmt.Errorf("failed to open in-memory database: %w", err)
6462
}
63+
return NewFromDB(db)
64+
}
6565

66-
// Enable foreign keys
67-
if _, err := db.Exec("PRAGMA foreign_keys = ON"); err != nil {
68-
db.Close()
69-
return nil, fmt.Errorf("failed to enable foreign keys: %w", err)
70-
}
71-
66+
// NewFromDB creates a catalog from an existing CatalogDB.
67+
func NewFromDB(db CatalogDB) (*Catalog, error) {
7268
c := &Catalog{
7369
db: db,
7470
projectID: "default",
@@ -193,8 +189,8 @@ func (c *Catalog) Query(sqlQuery string) (*QueryResult, error) {
193189
return result, rows.Err()
194190
}
195191

196-
// DB returns the underlying database connection for direct access.
197-
func (c *Catalog) DB() *sql.DB {
192+
// CatalogDB returns the underlying database abstraction.
193+
func (c *Catalog) CatalogDB() CatalogDB {
198194
return c.db
199195
}
200196

@@ -263,17 +259,11 @@ type CacheInfo struct {
263259

264260
// NewFromFile opens a catalog from a persisted SQLite file.
265261
func NewFromFile(path string) (*Catalog, error) {
266-
db, err := sql.Open("sqlite", path)
262+
db, err := NewSqliteCatalogDBFromFile(path)
267263
if err != nil {
268264
return nil, fmt.Errorf("failed to open catalog file: %w", err)
269265
}
270266

271-
// Enable foreign keys
272-
if _, err := db.Exec("PRAGMA foreign_keys = ON"); err != nil {
273-
db.Close()
274-
return nil, fmt.Errorf("failed to enable foreign keys: %w", err)
275-
}
276-
277267
c := &Catalog{
278268
db: db,
279269
projectID: "default",
@@ -286,7 +276,14 @@ func NewFromFile(path string) (*Catalog, error) {
286276

287277
// SaveToFile saves the catalog to a SQLite file.
288278
// This copies the in-memory database to a file for persistence.
279+
// Requires the underlying CatalogDB to be a *SqliteCatalogDB.
289280
func (c *Catalog) SaveToFile(path string) error {
281+
sdb, ok := c.db.(*SqliteCatalogDB)
282+
if !ok {
283+
return fmt.Errorf("SaveToFile requires a SQLite-backed catalog")
284+
}
285+
rawDB := sdb.RawDB()
286+
290287
// Open destination file database
291288
destDB, err := sql.Open("sqlite", path)
292289
if err != nil {
@@ -296,17 +293,17 @@ func (c *Catalog) SaveToFile(path string) error {
296293

297294
// Use SQLite backup API via VACUUM INTO (SQLite 3.27+)
298295
// Fall back to manual copy if not available
299-
_, err = c.db.Exec(fmt.Sprintf("VACUUM INTO '%s'", path))
296+
_, err = rawDB.Exec(fmt.Sprintf("VACUUM INTO '%s'", path))
300297
if err != nil {
301298
// Fall back: export and import
302-
return c.saveToFileManual(path)
299+
return c.saveToFileManual(path, rawDB)
303300
}
304301

305302
return nil
306303
}
307304

308305
// saveToFileManual saves by dumping and restoring (fallback method).
309-
func (c *Catalog) saveToFileManual(path string) error {
306+
func (c *Catalog) saveToFileManual(path string, rawDB *sql.DB) error {
310307
// Open destination
311308
destDB, err := sql.Open("sqlite", path)
312309
if err != nil {
@@ -315,7 +312,7 @@ func (c *Catalog) saveToFileManual(path string) error {
315312
defer destDB.Close()
316313

317314
// Get list of tables
318-
rows, err := c.db.Query("SELECT name, sql FROM sqlite_master WHERE type='table' AND name NOT LIKE 'sqlite_%'")
315+
rows, err := rawDB.Query("SELECT name, sql FROM sqlite_master WHERE type='table' AND name NOT LIKE 'sqlite_%'")
319316
if err != nil {
320317
return err
321318
}
@@ -339,7 +336,7 @@ func (c *Catalog) saveToFileManual(path string) error {
339336
}
340337

341338
// Copy data
342-
srcRows, err := c.db.Query(fmt.Sprintf("SELECT * FROM %s", t.name))
339+
srcRows, err := rawDB.Query(fmt.Sprintf("SELECT * FROM %s", t.name))
343340
if err != nil {
344341
return err
345342
}
@@ -377,7 +374,7 @@ func (c *Catalog) saveToFileManual(path string) error {
377374
}
378375

379376
// Copy views
380-
rows, err = c.db.Query("SELECT sql FROM sqlite_master WHERE type='view'")
377+
rows, err = rawDB.Query("SELECT sql FROM sqlite_master WHERE type='view'")
381378
if err != nil {
382379
return err
383380
}
@@ -394,7 +391,7 @@ func (c *Catalog) saveToFileManual(path string) error {
394391
rows.Close()
395392

396393
// Copy indexes
397-
rows, err = c.db.Query("SELECT sql FROM sqlite_master WHERE type='index' AND sql IS NOT NULL")
394+
rows, err = rawDB.Query("SELECT sql FROM sqlite_master WHERE type='index' AND sql IS NOT NULL")
398395
if err != nil {
399396
return err
400397
}

mdl/catalog/catalog_test.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -124,7 +124,7 @@ func TestQueryWithData(t *testing.T) {
124124
defer cat.Close()
125125

126126
// Insert test data
127-
_, err = cat.DB().Exec(
127+
_, err = cat.CatalogDB().Exec(
128128
"INSERT INTO modules (Id, Name, QualifiedName, ModuleName) VALUES (?, ?, ?, ?)",
129129
"mod-1", "TestModule", "TestModule", "TestModule",
130130
)
@@ -280,7 +280,7 @@ func TestSaveAndLoadFromFile(t *testing.T) {
280280
}
281281

282282
cat.SetProject("proj-1", "TestApp", "10.0.0")
283-
_, err = cat.DB().Exec(
283+
_, err = cat.CatalogDB().Exec(
284284
"INSERT INTO modules (Id, Name, QualifiedName, ModuleName) VALUES (?, ?, ?, ?)",
285285
"mod-1", "MyModule", "MyModule", "MyModule",
286286
)
@@ -359,7 +359,7 @@ func TestRoleMappingsTable(t *testing.T) {
359359
}
360360

361361
for _, m := range mappings {
362-
_, err := cat.DB().Exec(
362+
_, err := cat.CatalogDB().Exec(
363363
"INSERT INTO role_mappings (UserRoleName, ModuleRoleName, ModuleName) VALUES (?, ?, ?)",
364364
m.userRole, m.moduleRole, m.module,
365365
)

mdl/catalog/catalogdb_sqlite.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,3 +66,9 @@ func (s *SqliteCatalogDB) Close() error {
6666
func (s *SqliteCatalogDB) RawDB() *sql.DB {
6767
return s.db
6868
}
69+
70+
// WrapSqlDB wraps an existing *sql.DB as a CatalogDB.
71+
// Used by tests that create their own in-memory databases.
72+
func WrapSqlDB(db *sql.DB) *SqliteCatalogDB {
73+
return &SqliteCatalogDB{db: db}
74+
}

mdl/linter/context.go

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ import (
1313
// LintContext wraps a catalog and provides rule-friendly APIs.
1414
type LintContext struct {
1515
catalog *catalog.Catalog
16-
db *sql.DB
16+
db catalog.CatalogDB
1717
excluded map[string]bool
1818
reader *mpr.Reader
1919
}
@@ -32,14 +32,14 @@ func (ctx *LintContext) Reader() *mpr.Reader {
3232
func NewLintContext(cat *catalog.Catalog) *LintContext {
3333
return &LintContext{
3434
catalog: cat,
35-
db: cat.DB(),
35+
db: cat.CatalogDB(),
3636
excluded: make(map[string]bool),
3737
}
3838
}
3939

40-
// NewLintContextFromDB creates a new LintContext from a raw database connection.
40+
// NewLintContextFromDB creates a new LintContext from a CatalogDB.
4141
// Used in tests to provide an in-memory database with test data.
42-
func NewLintContextFromDB(db *sql.DB) *LintContext {
42+
func NewLintContextFromDB(db catalog.CatalogDB) *LintContext {
4343
return &LintContext{
4444
db: db,
4545
excluded: make(map[string]bool),
@@ -64,8 +64,8 @@ func (ctx *LintContext) Catalog() *catalog.Catalog {
6464
return ctx.catalog
6565
}
6666

67-
// DB returns the underlying database connection for advanced queries.
68-
func (ctx *LintContext) DB() *sql.DB {
67+
// CatalogDB returns the underlying database abstraction for advanced queries.
68+
func (ctx *LintContext) CatalogDB() catalog.CatalogDB {
6969
return ctx.db
7070
}
7171

mdl/linter/rules/empty_test.go

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,14 +6,15 @@ import (
66
"database/sql"
77
"testing"
88

9+
"github.com/mendixlabs/mxcli/mdl/catalog"
910
"github.com/mendixlabs/mxcli/mdl/linter"
1011

1112
_ "modernc.org/sqlite"
1213
)
1314

1415
// setupMicroflowsDB creates an in-memory SQLite database with the microflows and modules tables.
1516
// Each row is [Id, Name, QualifiedName, ModuleName, Folder, MicroflowType, Description, ReturnType, ParameterCount, ActivityCount, Complexity].
16-
func setupMicroflowsDB(t *testing.T, rows [][]any) *sql.DB {
17+
func setupMicroflowsDB(t *testing.T, rows [][]any) catalog.CatalogDB {
1718
t.Helper()
1819
db, err := sql.Open("sqlite", ":memory:")
1920
if err != nil {
@@ -47,7 +48,7 @@ func setupMicroflowsDB(t *testing.T, rows [][]any) *sql.DB {
4748
}
4849
}
4950

50-
return db
51+
return catalog.WrapSqlDB(db)
5152
}
5253

5354
func TestEmptyMicroflowRule_NoViolations(t *testing.T) {

mdl/linter/rules/helpers_test.go

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import (
66
"database/sql"
77
"testing"
88

9+
"github.com/mendixlabs/mxcli/mdl/catalog"
910
"github.com/mendixlabs/mxcli/mdl/linter"
1011

1112
_ "modernc.org/sqlite"
@@ -22,7 +23,7 @@ func testMicroflow() linter.Microflow {
2223
}
2324

2425
// setupEntitiesDB creates an in-memory SQLite database with entities and modules tables.
25-
func setupEntitiesDB(t *testing.T, entities [][]any) *sql.DB {
26+
func setupEntitiesDB(t *testing.T, entities [][]any) catalog.CatalogDB {
2627
t.Helper()
2728
db, err := sql.Open("sqlite", ":memory:")
2829
if err != nil {
@@ -56,5 +57,5 @@ func setupEntitiesDB(t *testing.T, entities [][]any) *sql.DB {
5657
}
5758
}
5859

59-
return db
60+
return catalog.WrapSqlDB(db)
6061
}

mdl/linter/rules/missing_translations.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ func (r *MissingTranslationsRule) Description() string {
2929
// Check runs the missing translations check.
3030
// Requires REFRESH CATALOG FULL to populate the strings table.
3131
func (r *MissingTranslationsRule) Check(ctx *linter.LintContext) []linter.Violation {
32-
db := ctx.DB()
32+
db := ctx.CatalogDB()
3333
if db == nil {
3434
return nil
3535
}

mdl/linter/rules/missing_translations_test.go

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import (
66
"database/sql"
77
"testing"
88

9+
"github.com/mendixlabs/mxcli/mdl/catalog"
910
"github.com/mendixlabs/mxcli/mdl/linter"
1011

1112
_ "modernc.org/sqlite"
@@ -14,7 +15,7 @@ import (
1415
// setupTranslationsDB creates an in-memory SQLite database with the strings FTS5 table
1516
// and inserts test data. Rows are [QualifiedName, ObjectType, StringValue, StringContext, Language, ModuleName].
1617
// ElementId is auto-generated from QualifiedName+StringContext for simplicity.
17-
func setupTranslationsDB(t *testing.T, rows [][]string) *sql.DB {
18+
func setupTranslationsDB(t *testing.T, rows [][]string) catalog.CatalogDB {
1819
t.Helper()
1920
db, err := sql.Open("sqlite", ":memory:")
2021
if err != nil {
@@ -46,7 +47,7 @@ func setupTranslationsDB(t *testing.T, rows [][]string) *sql.DB {
4647
}
4748
}
4849

49-
return db
50+
return catalog.WrapSqlDB(db)
5051
}
5152

5253
func TestMissingTranslations_NoViolationsWhenComplete(t *testing.T) {

0 commit comments

Comments
 (0)