Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
58e46e5b59 |
@@ -454,7 +454,7 @@ func (r *Reader) parseDBML(content string) (*models.Database, error) {
|
||||
// for a column declaration.
|
||||
if inTableNote {
|
||||
if line == "'''" {
|
||||
currentTable.Description = strings.TrimSpace(strings.Join(tableNoteLines, "\n"))
|
||||
setTableNote(currentTable, strings.TrimSpace(strings.Join(tableNoteLines, "\n")))
|
||||
inTableNote = false
|
||||
tableNoteLines = nil
|
||||
continue
|
||||
@@ -557,9 +557,10 @@ func (r *Reader) parseDBML(content string) (*models.Database, error) {
|
||||
continue
|
||||
}
|
||||
|
||||
// Parse table note
|
||||
if inTable && currentTable != nil && strings.HasPrefix(line, "Note:") {
|
||||
note := strings.TrimPrefix(line, "Note:")
|
||||
// Parse table note. DBML files in the wild use both `Note:` and
|
||||
// `note:`, so accept either spelling.
|
||||
if inTable && currentTable != nil && strings.HasPrefix(strings.ToLower(line), "note:") {
|
||||
note := strings.TrimSpace(line[len("note:"):])
|
||||
if strings.TrimSpace(note) == "'''" {
|
||||
inTableNote = true
|
||||
tableNoteLines = nil
|
||||
@@ -567,7 +568,7 @@ func (r *Reader) parseDBML(content string) (*models.Database, error) {
|
||||
continue
|
||||
}
|
||||
note = strings.Trim(note, " '\"")
|
||||
currentTable.Description = note
|
||||
setTableNote(currentTable, note)
|
||||
continue
|
||||
}
|
||||
|
||||
@@ -654,6 +655,20 @@ func (r *Reader) parseDBML(content string) (*models.Database, error) {
|
||||
return db, nil
|
||||
}
|
||||
|
||||
// setTableNote preserves multiple table notes. The first maps to Description
|
||||
// and the second to Comment, matching the model fields used by code writers.
|
||||
func setTableNote(table *models.Table, note string) {
|
||||
if table.Description == "" {
|
||||
table.Description = note
|
||||
return
|
||||
}
|
||||
if table.Comment == "" {
|
||||
table.Comment = note
|
||||
return
|
||||
}
|
||||
table.Comment += "\n" + note
|
||||
}
|
||||
|
||||
// parseColumn parses a DBML column definition
|
||||
func (r *Reader) parseColumn(line, tableName, schemaName string) (*models.Column, *models.Constraint) {
|
||||
// Format: column_name type [attributes] // comment
|
||||
@@ -671,7 +686,7 @@ func (r *Reader) parseColumn(line, tableName, schemaName string) (*models.Column
|
||||
|
||||
// Parse attributes in brackets
|
||||
if attrs != "" {
|
||||
attrList := strings.Split(attrs, ",")
|
||||
attrList := splitColumnAttrs(attrs)
|
||||
|
||||
for _, attr := range attrList {
|
||||
attr = strings.TrimSpace(attr)
|
||||
@@ -754,6 +769,44 @@ func (r *Reader) parseColumn(line, tableName, schemaName string) (*models.Column
|
||||
return column, constraint
|
||||
}
|
||||
|
||||
// splitColumnAttrs splits a DBML attribute list on top-level commas. Notes and
|
||||
// quoted defaults may contain commas of their own, which are part of the value
|
||||
// rather than attribute separators.
|
||||
func splitColumnAttrs(attrs string) []string {
|
||||
var result []string
|
||||
start := 0
|
||||
var quote byte
|
||||
escaped := false
|
||||
|
||||
for i := 0; i < len(attrs); i++ {
|
||||
ch := attrs[i]
|
||||
if quote != 0 {
|
||||
if escaped {
|
||||
escaped = false
|
||||
continue
|
||||
}
|
||||
if ch == '\\' {
|
||||
escaped = true
|
||||
continue
|
||||
}
|
||||
if ch == quote {
|
||||
quote = 0
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
switch ch {
|
||||
case '\'', '"', '`':
|
||||
quote = ch
|
||||
case ',':
|
||||
result = append(result, attrs[start:i])
|
||||
start = i + 1
|
||||
}
|
||||
}
|
||||
|
||||
return append(result, attrs[start:])
|
||||
}
|
||||
|
||||
func splitInlineComment(line string) (content, inlineComment string) {
|
||||
commentStart := strings.Index(line, "//")
|
||||
if commentStart == -1 {
|
||||
|
||||
@@ -1037,12 +1037,13 @@ func TestReader_ColumnPKOrderPreserved(t *testing.T) {
|
||||
func TestReader_MultilineTableNote(t *testing.T) {
|
||||
dbmlContent := "Table \"info\".\"city\" {\n" +
|
||||
" \"id_city\" serial [pk, not null, increment]\n" +
|
||||
" \"name\" text [not null]\n\n" +
|
||||
" Note: '''\n" +
|
||||
" \"name\" text [not null, note: 'first, second, third']\n\n" +
|
||||
" note: '''\n" +
|
||||
" Cities and municipalities worldwide.\n\n" +
|
||||
" SPATIAL:\n" +
|
||||
" Proximity queries use a GiST index.\n" +
|
||||
" '''\n" +
|
||||
" Note: 'Short summary'\n" +
|
||||
"}\n"
|
||||
path := filepath.Join(t.TempDir(), "city.dbml")
|
||||
if err := os.WriteFile(path, []byte(dbmlContent), 0o644); err != nil {
|
||||
@@ -1058,7 +1059,13 @@ func TestReader_MultilineTableNote(t *testing.T) {
|
||||
if got, want := table.Description, "Cities and municipalities worldwide.\n\nSPATIAL:\nProximity queries use a GiST index."; got != want {
|
||||
t.Errorf("table description = %q, want %q", got, want)
|
||||
}
|
||||
if got, want := table.Comment, "Short summary"; got != want {
|
||||
t.Errorf("table comment = %q, want %q", got, want)
|
||||
}
|
||||
if got, want := len(table.Columns), 2; got != want {
|
||||
t.Errorf("column count = %d, want %d; note body must not be parsed as columns", got, want)
|
||||
}
|
||||
if got, want := table.Columns["name"].Comment, "first, second, third"; got != want {
|
||||
t.Errorf("column note = %q, want %q", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user