package dbml import ( "strings" "testing" "git.warky.dev/wdevs/relspecgo/pkg/models" "git.warky.dev/wdevs/relspecgo/pkg/readers" ) func parse(t *testing.T, strict bool, src string) (*models.Database, error) { t.Helper() r := NewReader(&readers.ReaderOptions{StrictDirectives: strict}) return r.parseDBML(src) } func firstTable(t *testing.T, db *models.Database) *models.Table { t.Helper() if len(db.Schemas) == 0 || len(db.Schemas[0].Tables) == 0 { t.Fatal("no table parsed") } return db.Schemas[0].Tables[0] } func TestDirectives_AttachAtEachLocation(t *testing.T) { src := `@postgres: search_path myapp Table myapp.events { id bigint [pk] created_at timestamp [not null] @postgres(id): identity always @postgres: partition by RANGE (created_at) indexes { (created_at) [name: 'idx_events_created'] @postgres: with (fillfactor=90) } } ` db, err := parse(t, false, src) if err != nil { t.Fatalf("parse: %v", err) } if !models.HasDirective(db.Metadata, "postgres", "search_path") { t.Errorf("database-level directive missing: %+v", db.Metadata) } tbl := firstTable(t, db) if !models.HasDirective(tbl.Metadata, "postgres", "partition") { t.Errorf("table-level directive missing: %+v", tbl.Metadata) } col := tbl.Columns["id"] if col == nil || !models.HasDirective(col.Metadata, "postgres", "identity") { t.Errorf("column-level directive missing") } // Verbatim args preserved. if d := models.DirectivesForNamespace(col.Metadata, "postgres"); len(d) != 1 || d[0].Args != "identity always" { t.Errorf("column directive args = %+v", d) } var idx *models.Index for _, i := range tbl.Indexes { idx = i } if idx == nil || !models.HasDirective(idx.Metadata, "postgres", "with") { t.Errorf("index-level directive missing: %+v", idx) } } func TestDirectives_RepeatablePreservedAndOrdered(t *testing.T) { src := `Table s.t { id int [pk] @postgres: with (fillfactor=90) @postgres: with (autovacuum_enabled=off) } ` db, err := parse(t, false, src) if err != nil { t.Fatalf("parse: %v", err) } tbl := firstTable(t, db) got := models.DirectivesForNamespace(tbl.Metadata, "postgres") if len(got) != 2 { t.Fatalf("got %d directives, want 2", len(got)) } if got[0].Args != "with (fillfactor=90)" || got[1].Args != "with (autovacuum_enabled=off)" { t.Errorf("repeatable directives out of order: %+v", got) } } func TestDirectives_SingletonDuplicateErrors(t *testing.T) { src := `Table s.t { id int [pk] @postgres: partition by RANGE (a) @postgres: partition by LIST (b) } ` _, err := parse(t, false, src) if err == nil || !strings.Contains(err.Error(), "duplicate") { t.Fatalf("want duplicate error, got %v", err) } if !strings.Contains(err.Error(), "line 4") { t.Errorf("error not line-numbered: %v", err) } } func TestDirectives_MalformedErrors(t *testing.T) { cases := map[string]string{ "no colon": "@postgres partition by x", "empty args": "@postgres:", "bad namespace": "@Postgres: partition by x", "numeric prefix": "@1x: foo", } for name, line := range cases { t.Run(name, func(t *testing.T) { src := "Table s.t {\n id int [pk]\n " + line + "\n}\n" _, err := parse(t, false, src) if err == nil { t.Fatalf("want error for %q", line) } if !strings.Contains(err.Error(), "line 3") { t.Errorf("error not line-numbered: %v", err) } }) } } func TestDirectives_UnknownPreservedNonStrict(t *testing.T) { src := `Table s.t { id int [pk] @postgres: frobnicate all the things @clickhouse: engine MergeTree } ` db, err := parse(t, false, src) if err != nil { t.Fatalf("parse: %v", err) } tbl := firstTable(t, db) if !models.HasDirective(tbl.Metadata, "postgres", "frobnicate") { t.Error("unknown postgres key not preserved") } if !models.HasDirective(tbl.Metadata, "clickhouse", "engine") { t.Error("unknown namespace not preserved") } } func TestDirectives_StrictErrors(t *testing.T) { src := `Table s.t { id int [pk] @postgres: frobnicate x } ` _, err := parse(t, true, src) if err == nil || !strings.Contains(err.Error(), "strict mode") { t.Fatalf("want strict-mode error, got %v", err) } } func TestDirectives_UnknownColumnTargetErrors(t *testing.T) { src := `Table s.t { id int [pk] @postgres(missing): identity always } ` _, err := parse(t, false, src) if err == nil || !strings.Contains(err.Error(), "not found") { t.Fatalf("want unknown-column error, got %v", err) } } func TestDirectives_WrongLocationErrors(t *testing.T) { // partition is table-only. src := "@postgres: partition by RANGE (x)\n\nTable s.t {\n id int [pk]\n}\n" _, err := parse(t, false, src) if err == nil || !strings.Contains(err.Error(), "not valid at database level") { t.Fatalf("want location error, got %v", err) } }