feat(dbml): @postgres/@sqlite dialect directives (#19)
Add parseable `@<namespace>[(<target>)]: <args>` directives embedded in DBML.
They are stored losslessly on each object's Metadata, round-trip unchanged
through the DBML writer, and are translated to SQL only by the writer for the
matching dialect.
- models: Directive type + catalog; Metadata map added to Column and Index
- dbml reader: parse and attach directives at database/table/column/index
level; line-numbered errors; repeatable by default with singleton duplicate
detection. Fixes a preexisting bug where an `indexes {}` closing brace ended
the table early, dropping trailing Note: and directive lines.
- dbml writer: re-emit directives at their location; idempotent output
- pgsql writer: PARTITION BY / INHERITS / WITH / TABLESPACE (table),
STORAGE / COMPRESSION / identity (column), WITH / TABLESPACE (index)
- sqlite writer: WITHOUT ROWID / STRICT (table), COLLATE (column)
- --strict-directives flag on ReaderOptions and WriterOptions
- docs/DBML_DIRECTIVES.md + reader/writer READMEs
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Ss2MY5J11cRGwEz86ZXk7d
This commit is contained in:
@@ -435,11 +435,14 @@ func (r *Reader) parseDBML(content string) (*models.Database, error) {
|
||||
var inIndexes bool
|
||||
var inTable bool
|
||||
var columnSeq uint
|
||||
var lastIndex *models.Index // most recent index in the current Indexes block
|
||||
lineNo := 0
|
||||
|
||||
tableRegex := regexp.MustCompile(`^Table\s+(.+?)\s*{`)
|
||||
refRegex := regexp.MustCompile(`^Ref:\s+(.+)`)
|
||||
|
||||
for scanner.Scan() {
|
||||
lineNo++
|
||||
line := strings.TrimSpace(scanner.Text())
|
||||
|
||||
// Skip empty lines and comments
|
||||
@@ -447,6 +450,20 @@ func (r *Reader) parseDBML(content string) (*models.Database, error) {
|
||||
continue
|
||||
}
|
||||
|
||||
// Parse a dialect directive (@postgres:, @sqlite:, …). Handled before
|
||||
// table/column/index parsing so directive lines are never mistaken for
|
||||
// columns.
|
||||
if strings.HasPrefix(line, "@") {
|
||||
pd, err := parseDirectiveLine(line, lineNo)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := r.attachDirective(pd, db, currentTable, inTable, inIndexes, lastIndex); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
// Parse Table definition
|
||||
if matches := tableRegex.FindStringSubmatch(line); matches != nil {
|
||||
tableName := matches[1]
|
||||
@@ -474,8 +491,10 @@ func (r *Reader) parseDBML(content string) (*models.Database, error) {
|
||||
continue
|
||||
}
|
||||
|
||||
// End of table definition
|
||||
if inTable && line == "}" {
|
||||
// End of table definition. Guarded by !inIndexes so the closing brace
|
||||
// of an `indexes { }` block is not mistaken for the end of the table
|
||||
// (which would drop any table-level content that follows it).
|
||||
if inTable && !inIndexes && line == "}" {
|
||||
if currentTable != nil && currentSchema != "" {
|
||||
schemaMap[currentSchema].Tables = append(schemaMap[currentSchema].Tables, currentTable)
|
||||
currentTable = nil
|
||||
@@ -488,12 +507,14 @@ func (r *Reader) parseDBML(content string) (*models.Database, error) {
|
||||
// Parse indexes section
|
||||
if inTable && (strings.HasPrefix(line, "Indexes {") || strings.HasPrefix(line, "indexes {")) {
|
||||
inIndexes = true
|
||||
lastIndex = nil
|
||||
continue
|
||||
}
|
||||
|
||||
// End of indexes section
|
||||
if inIndexes && line == "}" {
|
||||
inIndexes = false
|
||||
lastIndex = nil
|
||||
continue
|
||||
}
|
||||
|
||||
@@ -513,6 +534,7 @@ func (r *Reader) parseDBML(content string) (*models.Database, error) {
|
||||
index := r.parseIndex(line, currentTable.Name, currentSchema)
|
||||
if index != nil {
|
||||
currentTable.Indexes[index.Name] = index
|
||||
lastIndex = index
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user