package sqlite import ( "fmt" "strings" "git.warky.dev/wdevs/relspecgo/pkg/models" ) // directiveNamespace is the dialect namespace this writer consumes. Directives // for other namespaces (e.g. "postgres") are ignored and never emitted as SQL. const directiveNamespace = "sqlite" // sqliteHandledDirectives maps a directive location to the set of sqlite keys // this writer knows how to translate. In strict mode an unknown key for this // namespace at a supported location is a hard error. var sqliteHandledDirectives = map[string]map[string]bool{ models.DirectiveLocationTable: {"without": true, "strict": true}, models.DirectiveLocationColumn: {"collate": true}, } // checkDirectives validates sqlite directives across a schema when strict mode // is enabled. With strict mode off it is a no-op. func (w *Writer) checkDirectives(schema *models.Schema) error { if w.options == nil || !w.options.StrictDirectives { return nil } for _, table := range schema.Tables { if err := checkObjectDirectives(table.Metadata, models.DirectiveLocationTable, table.Name); err != nil { return err } for _, col := range table.Columns { if err := checkObjectDirectives(col.Metadata, models.DirectiveLocationColumn, table.Name+"."+col.Name); err != nil { return err } } for _, idx := range table.Indexes { if err := checkObjectDirectives(idx.Metadata, models.DirectiveLocationIndex, idx.Name); err != nil { return err } } } return nil } func checkObjectDirectives(meta map[string]any, location, owner string) error { for _, d := range models.DirectivesForNamespace(meta, directiveNamespace) { if !sqliteHandledDirectives[location][d.Key] { return fmt.Errorf("sqlite: %s: unsupported @sqlite directive %q at %s level (strict mode)", owner, d.Key, location) } } return nil } // sqliteTableOptions returns the trailing table-option clause for a CREATE TABLE // statement, e.g. "WITHOUT ROWID, STRICT". WITHOUT ROWID is emitted before // STRICT, matching SQLite's own grammar ordering. func sqliteTableOptions(table *models.Table) string { var opts []string if models.HasDirective(table.Metadata, directiveNamespace, "without") { opts = append(opts, "WITHOUT ROWID") } if models.HasDirective(table.Metadata, directiveNamespace, "strict") { opts = append(opts, "STRICT") } return strings.Join(opts, ", ") } // sqliteColumnCollate returns a " COLLATE " clause for a column carrying an // @sqlite(col): collate directive, or "". func sqliteColumnCollate(col *models.Column) string { for _, d := range models.DirectivesForNamespace(col.Metadata, directiveNamespace) { if d.Key != "collate" { continue } name := strings.TrimSpace(strings.TrimPrefix(strings.TrimSpace(d.Args), "collate")) name = strings.TrimSpace(name) if name == "" { return "" } return " COLLATE " + name } return "" }