diff --git a/driver/driver_test.go b/driver/driver_test.go index 91990c7..418ade1 100644 --- a/driver/driver_test.go +++ b/driver/driver_test.go @@ -2705,3 +2705,67 @@ func TestLimitEmptyTable(t *testing.T) { t.Fatalf("cannot select: %s", err) } } + +func TestInformationSchema(t *testing.T) { + + db, err := sql.Open("ramsql", "TestInformationSchema") + if err != nil { + t.Fatalf("sql.Open: %s", err) + } + defer db.Close() + + // Create a couple of tables + _, err = db.Exec("CREATE TABLE users (id INT, name TEXT)") + if err != nil { + t.Fatalf("cannot create users table: %s", err) + } + + _, err = db.Exec("CREATE TABLE posts (id INT, title TEXT, user_id INT)") + if err != nil { + t.Fatalf("cannot create posts table: %s", err) + } + + // Query information_schema.tables to verify the tables are registered + rows, err := db.Query(`SELECT table_schema, table_name, table_type FROM information_schema.tables WHERE table_schema = 'public' ORDER BY table_name`) + if err != nil { + t.Fatalf("cannot query information_schema.tables: %s", err) + } + defer rows.Close() + + // Expected tables: posts, users (alphabetically ordered) + expected := []struct { + schema string + name string + tableType string + }{ + {"public", "posts", "BASE TABLE"}, + {"public", "users", "BASE TABLE"}, + } + + i := 0 + for rows.Next() { + var schema, name, tableType string + if err := rows.Scan(&schema, &name, &tableType); err != nil { + t.Fatalf("cannot scan row: %s", err) + } + + if i >= len(expected) { + t.Fatalf("unexpected extra row: %s.%s", schema, name) + } + + if schema != expected[i].schema || name != expected[i].name || tableType != expected[i].tableType { + t.Errorf("row %d: got (%s, %s, %s), want (%s, %s, %s)", + i, schema, name, tableType, + expected[i].schema, expected[i].name, expected[i].tableType) + } + i++ + } + + if i != len(expected) { + t.Errorf("expected %d rows, got %d", len(expected), i) + } + + if err := rows.Err(); err != nil { + t.Fatalf("rows error: %s", err) + } +} diff --git a/engine/agnostic/engine.go b/engine/agnostic/engine.go index 48d0661..4df5835 100644 --- a/engine/agnostic/engine.go +++ b/engine/agnostic/engine.go @@ -22,6 +22,20 @@ func NewEngine() *Engine { e.schemas = make(map[string]*Schema) e.schemas[DefaultSchema] = NewSchema(DefaultSchema) + // create information_schema with a 'tables' relation used by clients (e.g. GORM) + info := NewSchema("information_schema") + // minimal columns used by GORM queries: table_schema, table_name, table_type + attrs := []Attribute{ + NewAttribute("table_schema", "varchar"), + NewAttribute("table_name", "varchar"), + NewAttribute("table_type", "varchar"), + } + // create relation (no primary key) + if r, err := NewRelation("information_schema", "tables", attrs, nil); err == nil { + info.Add("tables", r) + } + e.schemas["information_schema"] = info + return e } diff --git a/engine/agnostic/transaction.go b/engine/agnostic/transaction.go index 997e9a4..cf46104 100644 --- a/engine/agnostic/transaction.go +++ b/engine/agnostic/transaction.go @@ -150,6 +150,27 @@ func (t *Transaction) CreateRelation(schemaName, relName string, attributes []At log.Debug("CreateRelation(%s,%s,%s,%s)", schemaName, relName, attributes, pk) t.lock(r) + + // maintain information_schema.tables so external tools (eg. GORM) can query table existence + // insert a row into information_schema.tables: table_schema, table_name, table_type + // Use default schema if empty + sch := schemaName + if sch == "" { + sch = DefaultSchema + } + // best-effort: if information_schema exists, insert a metadata row via Transaction.Insert + if t.CheckSchema("information_schema") { + vals := map[string]any{ + "table_schema": sch, + "table_name": relName, + "table_type": "BASE TABLE", + } + _, err := t.Insert("information_schema", "tables", vals) + if err != nil { + // do not fail relation creation because of metadata insertion; just log + log.Warn("could not update information_schema.tables: %s", err) + } + } return nil } @@ -170,6 +191,23 @@ func (t *Transaction) DropRelation(schemaName, relName string) error { } t.changes.PushBack(c) + // remove metadata from information_schema.tables if present + sch := schemaName + if sch == "" { + sch = DefaultSchema + } + if t.CheckSchema("information_schema") { + // build predicate: table_schema = sch AND table_name = relName + left := NewEqPredicate(NewAttributeValueFunctor("tables", "table_schema"), NewConstValueFunctor(sch)) + right := NewEqPredicate(NewAttributeValueFunctor("tables", "table_name"), NewConstValueFunctor(relName)) + p := NewAndPredicate(left, right) + // selectors can be nil for Delete + _, _, err := t.Delete("information_schema", "tables", nil, p) + if err != nil { + log.Warn("could not remove information_schema.tables entry for %s.%s: %s", sch, relName, err) + } + } + return nil }