Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
58e46e5b59 |
@@ -454,7 +454,7 @@ func (r *Reader) parseDBML(content string) (*models.Database, error) {
|
|||||||
// for a column declaration.
|
// for a column declaration.
|
||||||
if inTableNote {
|
if inTableNote {
|
||||||
if line == "'''" {
|
if line == "'''" {
|
||||||
currentTable.Description = strings.TrimSpace(strings.Join(tableNoteLines, "\n"))
|
setTableNote(currentTable, strings.TrimSpace(strings.Join(tableNoteLines, "\n")))
|
||||||
inTableNote = false
|
inTableNote = false
|
||||||
tableNoteLines = nil
|
tableNoteLines = nil
|
||||||
continue
|
continue
|
||||||
@@ -557,9 +557,10 @@ func (r *Reader) parseDBML(content string) (*models.Database, error) {
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
// Parse table note
|
// Parse table note. DBML files in the wild use both `Note:` and
|
||||||
if inTable && currentTable != nil && strings.HasPrefix(line, "Note:") {
|
// `note:`, so accept either spelling.
|
||||||
note := strings.TrimPrefix(line, "Note:")
|
if inTable && currentTable != nil && strings.HasPrefix(strings.ToLower(line), "note:") {
|
||||||
|
note := strings.TrimSpace(line[len("note:"):])
|
||||||
if strings.TrimSpace(note) == "'''" {
|
if strings.TrimSpace(note) == "'''" {
|
||||||
inTableNote = true
|
inTableNote = true
|
||||||
tableNoteLines = nil
|
tableNoteLines = nil
|
||||||
@@ -567,7 +568,7 @@ func (r *Reader) parseDBML(content string) (*models.Database, error) {
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
note = strings.Trim(note, " '\"")
|
note = strings.Trim(note, " '\"")
|
||||||
currentTable.Description = note
|
setTableNote(currentTable, note)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -654,6 +655,20 @@ func (r *Reader) parseDBML(content string) (*models.Database, error) {
|
|||||||
return db, nil
|
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
|
// parseColumn parses a DBML column definition
|
||||||
func (r *Reader) parseColumn(line, tableName, schemaName string) (*models.Column, *models.Constraint) {
|
func (r *Reader) parseColumn(line, tableName, schemaName string) (*models.Column, *models.Constraint) {
|
||||||
// Format: column_name type [attributes] // comment
|
// Format: column_name type [attributes] // comment
|
||||||
@@ -671,7 +686,7 @@ func (r *Reader) parseColumn(line, tableName, schemaName string) (*models.Column
|
|||||||
|
|
||||||
// Parse attributes in brackets
|
// Parse attributes in brackets
|
||||||
if attrs != "" {
|
if attrs != "" {
|
||||||
attrList := strings.Split(attrs, ",")
|
attrList := splitColumnAttrs(attrs)
|
||||||
|
|
||||||
for _, attr := range attrList {
|
for _, attr := range attrList {
|
||||||
attr = strings.TrimSpace(attr)
|
attr = strings.TrimSpace(attr)
|
||||||
@@ -754,6 +769,44 @@ func (r *Reader) parseColumn(line, tableName, schemaName string) (*models.Column
|
|||||||
return column, constraint
|
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) {
|
func splitInlineComment(line string) (content, inlineComment string) {
|
||||||
commentStart := strings.Index(line, "//")
|
commentStart := strings.Index(line, "//")
|
||||||
if commentStart == -1 {
|
if commentStart == -1 {
|
||||||
|
|||||||
@@ -1037,12 +1037,13 @@ func TestReader_ColumnPKOrderPreserved(t *testing.T) {
|
|||||||
func TestReader_MultilineTableNote(t *testing.T) {
|
func TestReader_MultilineTableNote(t *testing.T) {
|
||||||
dbmlContent := "Table \"info\".\"city\" {\n" +
|
dbmlContent := "Table \"info\".\"city\" {\n" +
|
||||||
" \"id_city\" serial [pk, not null, increment]\n" +
|
" \"id_city\" serial [pk, not null, increment]\n" +
|
||||||
" \"name\" text [not null]\n\n" +
|
" \"name\" text [not null, note: 'first, second, third']\n\n" +
|
||||||
" Note: '''\n" +
|
" note: '''\n" +
|
||||||
" Cities and municipalities worldwide.\n\n" +
|
" Cities and municipalities worldwide.\n\n" +
|
||||||
" SPATIAL:\n" +
|
" SPATIAL:\n" +
|
||||||
" Proximity queries use a GiST index.\n" +
|
" Proximity queries use a GiST index.\n" +
|
||||||
" '''\n" +
|
" '''\n" +
|
||||||
|
" Note: 'Short summary'\n" +
|
||||||
"}\n"
|
"}\n"
|
||||||
path := filepath.Join(t.TempDir(), "city.dbml")
|
path := filepath.Join(t.TempDir(), "city.dbml")
|
||||||
if err := os.WriteFile(path, []byte(dbmlContent), 0o644); err != nil {
|
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 {
|
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)
|
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 {
|
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)
|
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