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:
@@ -0,0 +1,237 @@
|
||||
package models
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sort"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Directive is a dialect-specific instruction embedded in a source schema
|
||||
// (currently DBML) that is preserved losslessly in the intermediate model and
|
||||
// consumed only by the writer for its namespace. Directives are stored in the
|
||||
// Metadata map of the object they apply to, under DirectivesMetadataKey.
|
||||
//
|
||||
// Example DBML: `@postgres: partition by RANGE (created_at)` parses to
|
||||
// Directive{Namespace: "postgres", Key: "partition", Args: "partition by RANGE (created_at)"}.
|
||||
type Directive struct {
|
||||
// Namespace is the dialect the directive targets, e.g. "postgres" or "sqlite".
|
||||
Namespace string `json:"namespace" yaml:"namespace"`
|
||||
// Key is the lowercased first token of Args, used for duplicate detection
|
||||
// and writer dispatch.
|
||||
Key string `json:"key,omitempty" yaml:"key,omitempty"`
|
||||
// Args is the verbatim argument text following the "@namespace:" prefix.
|
||||
Args string `json:"args" yaml:"args"`
|
||||
// Line is the 1-based source line the directive was read from, when known.
|
||||
Line int `json:"line,omitempty" yaml:"line,omitempty"`
|
||||
}
|
||||
|
||||
// DirectivesMetadataKey is the Metadata map key under which the ordered list of
|
||||
// dialect directives for an object is stored.
|
||||
const DirectivesMetadataKey = "directives"
|
||||
|
||||
// DirectiveKey derives the Key for a directive from its argument text: the
|
||||
// lowercased first whitespace-delimited token.
|
||||
func DirectiveKey(args string) string {
|
||||
fields := strings.Fields(args)
|
||||
if len(fields) == 0 {
|
||||
return ""
|
||||
}
|
||||
return strings.ToLower(fields[0])
|
||||
}
|
||||
|
||||
// AddDirective appends d to the directive list stored in meta. The caller is
|
||||
// responsible for ensuring meta is non-nil (all Init* constructors allocate it).
|
||||
// If d.Key is empty it is derived from d.Args.
|
||||
func AddDirective(meta map[string]any, d Directive) {
|
||||
if meta == nil {
|
||||
return
|
||||
}
|
||||
if d.Key == "" {
|
||||
d.Key = DirectiveKey(d.Args)
|
||||
}
|
||||
existing := GetDirectives(meta)
|
||||
existing = append(existing, d)
|
||||
meta[DirectivesMetadataKey] = existing
|
||||
}
|
||||
|
||||
// GetDirectives returns the directives stored in meta, sorted deterministically
|
||||
// by (Namespace, Line, Args). It tolerates both a freshly built []Directive and
|
||||
// the []any of map[string]any produced by a JSON/YAML round-trip.
|
||||
func GetDirectives(meta map[string]any) []Directive {
|
||||
if meta == nil {
|
||||
return nil
|
||||
}
|
||||
raw, ok := meta[DirectivesMetadataKey]
|
||||
if !ok || raw == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
var out []Directive
|
||||
switch v := raw.(type) {
|
||||
case []Directive:
|
||||
out = append(out, v...)
|
||||
case []any:
|
||||
for _, item := range v {
|
||||
if d, ok := directiveFromAny(item); ok {
|
||||
out = append(out, d)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
sort.SliceStable(out, func(i, j int) bool {
|
||||
if out[i].Namespace != out[j].Namespace {
|
||||
return out[i].Namespace < out[j].Namespace
|
||||
}
|
||||
if out[i].Line != out[j].Line {
|
||||
return out[i].Line < out[j].Line
|
||||
}
|
||||
return out[i].Args < out[j].Args
|
||||
})
|
||||
return out
|
||||
}
|
||||
|
||||
// directiveFromAny decodes a single directive from the loosely typed forms that
|
||||
// survive a JSON or YAML round-trip (map[string]any / map[any]any).
|
||||
func directiveFromAny(item any) (Directive, bool) {
|
||||
switch m := item.(type) {
|
||||
case Directive:
|
||||
return m, true
|
||||
case map[string]any:
|
||||
return directiveFromStringMap(m), true
|
||||
case map[any]any:
|
||||
sm := make(map[string]any, len(m))
|
||||
for k, val := range m {
|
||||
if ks, ok := k.(string); ok {
|
||||
sm[ks] = val
|
||||
}
|
||||
}
|
||||
return directiveFromStringMap(sm), true
|
||||
}
|
||||
return Directive{}, false
|
||||
}
|
||||
|
||||
func directiveFromStringMap(m map[string]any) Directive {
|
||||
d := Directive{}
|
||||
if s, ok := m["namespace"].(string); ok {
|
||||
d.Namespace = s
|
||||
}
|
||||
if s, ok := m["key"].(string); ok {
|
||||
d.Key = s
|
||||
}
|
||||
if s, ok := m["args"].(string); ok {
|
||||
d.Args = s
|
||||
}
|
||||
switch n := m["line"].(type) {
|
||||
case int:
|
||||
d.Line = n
|
||||
case int64:
|
||||
d.Line = int(n)
|
||||
case float64:
|
||||
d.Line = int(n)
|
||||
}
|
||||
if d.Key == "" {
|
||||
d.Key = DirectiveKey(d.Args)
|
||||
}
|
||||
return d
|
||||
}
|
||||
|
||||
// DirectivesForNamespace returns the directives in meta that target ns, in the
|
||||
// deterministic order of GetDirectives.
|
||||
func DirectivesForNamespace(meta map[string]any, ns string) []Directive {
|
||||
all := GetDirectives(meta)
|
||||
if len(all) == 0 {
|
||||
return nil
|
||||
}
|
||||
out := make([]Directive, 0, len(all))
|
||||
for _, d := range all {
|
||||
if d.Namespace == ns {
|
||||
out = append(out, d)
|
||||
}
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
// HasDirective reports whether meta contains a directive with the given
|
||||
// namespace and key.
|
||||
func HasDirective(meta map[string]any, ns, key string) bool {
|
||||
for _, d := range GetDirectives(meta) {
|
||||
if d.Namespace == ns && d.Key == key {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// DirectiveSpec describes a documented directive in the catalog.
|
||||
type DirectiveSpec struct {
|
||||
// Singleton means only one directive with this namespace/key may appear at
|
||||
// a single location; a second one is a parse error.
|
||||
Singleton bool
|
||||
// Locations lists the location kinds the directive is valid at
|
||||
// ("database", "table", "column", "index").
|
||||
Locations []string
|
||||
}
|
||||
|
||||
// Location kinds a directive may attach to.
|
||||
const (
|
||||
DirectiveLocationDatabase = "database"
|
||||
DirectiveLocationTable = "table"
|
||||
DirectiveLocationColumn = "column"
|
||||
DirectiveLocationIndex = "index"
|
||||
)
|
||||
|
||||
// DirectiveCatalog is the set of documented directives per namespace. It is used
|
||||
// for strict-mode validation in readers and writers; unknown namespaces/keys are
|
||||
// still preserved losslessly when strict mode is off.
|
||||
var DirectiveCatalog = map[string]map[string]DirectiveSpec{
|
||||
"postgres": {
|
||||
"partition": {Singleton: true, Locations: []string{DirectiveLocationTable}},
|
||||
"tablespace": {Singleton: true, Locations: []string{DirectiveLocationTable, DirectiveLocationIndex}},
|
||||
"inherits": {Singleton: true, Locations: []string{DirectiveLocationTable}},
|
||||
"with": {Singleton: false, Locations: []string{DirectiveLocationTable, DirectiveLocationIndex}},
|
||||
"storage": {Singleton: true, Locations: []string{DirectiveLocationColumn}},
|
||||
"compression": {Singleton: true, Locations: []string{DirectiveLocationColumn}},
|
||||
"identity": {Singleton: true, Locations: []string{DirectiveLocationColumn}},
|
||||
},
|
||||
"sqlite": {
|
||||
"without": {Singleton: true, Locations: []string{DirectiveLocationTable}},
|
||||
"strict": {Singleton: true, Locations: []string{DirectiveLocationTable}},
|
||||
"collate": {Singleton: true, Locations: []string{DirectiveLocationColumn}},
|
||||
},
|
||||
}
|
||||
|
||||
// LookupDirectiveSpec returns the catalog spec for a namespace/key and whether
|
||||
// it is documented.
|
||||
func LookupDirectiveSpec(ns, key string) (DirectiveSpec, bool) {
|
||||
keys, ok := DirectiveCatalog[ns]
|
||||
if !ok {
|
||||
return DirectiveSpec{}, false
|
||||
}
|
||||
spec, ok := keys[key]
|
||||
return spec, ok
|
||||
}
|
||||
|
||||
// DirectiveLocationAllowed reports whether a documented directive may appear at
|
||||
// the given location. Unknown directives (not in the catalog) are allowed
|
||||
// everywhere so they can be preserved.
|
||||
func DirectiveLocationAllowed(ns, key, location string) bool {
|
||||
spec, ok := LookupDirectiveSpec(ns, key)
|
||||
if !ok {
|
||||
return true
|
||||
}
|
||||
for _, l := range spec.Locations {
|
||||
if l == location {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// FormatDirectiveLine renders a directive back to its DBML source form, e.g.
|
||||
// "@postgres: partition by RANGE (created_at)" or "@postgres(id): identity always".
|
||||
func FormatDirectiveLine(d Directive, target string) string {
|
||||
if target != "" {
|
||||
return fmt.Sprintf("@%s(%s): %s", d.Namespace, target, d.Args)
|
||||
}
|
||||
return fmt.Sprintf("@%s: %s", d.Namespace, d.Args)
|
||||
}
|
||||
@@ -0,0 +1,133 @@
|
||||
package models
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestDirectiveKey(t *testing.T) {
|
||||
cases := map[string]string{
|
||||
"partition by RANGE (created_at)": "partition",
|
||||
"WITHOUT ROWID": "without",
|
||||
" strict ": "strict",
|
||||
"": "",
|
||||
}
|
||||
for args, want := range cases {
|
||||
if got := DirectiveKey(args); got != want {
|
||||
t.Errorf("DirectiveKey(%q) = %q, want %q", args, got, want)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestAddDirectiveDerivesKey(t *testing.T) {
|
||||
meta := map[string]any{}
|
||||
AddDirective(meta, Directive{Namespace: "postgres", Args: "partition by RANGE (x)", Line: 2})
|
||||
AddDirective(meta, Directive{Namespace: "postgres", Key: "tablespace", Args: "tablespace fast", Line: 3})
|
||||
|
||||
got := GetDirectives(meta)
|
||||
if len(got) != 2 {
|
||||
t.Fatalf("got %d directives, want 2", len(got))
|
||||
}
|
||||
if got[0].Key != "partition" {
|
||||
t.Errorf("derived key = %q, want %q", got[0].Key, "partition")
|
||||
}
|
||||
if got[1].Key != "tablespace" {
|
||||
t.Errorf("explicit key = %q, want %q", got[1].Key, "tablespace")
|
||||
}
|
||||
}
|
||||
|
||||
func TestAddDirectiveNilMeta(t *testing.T) {
|
||||
// Must not panic.
|
||||
AddDirective(nil, Directive{Namespace: "postgres", Args: "strict"})
|
||||
}
|
||||
|
||||
func TestGetDirectivesOrdering(t *testing.T) {
|
||||
meta := map[string]any{}
|
||||
AddDirective(meta, Directive{Namespace: "sqlite", Args: "strict", Line: 9})
|
||||
AddDirective(meta, Directive{Namespace: "postgres", Args: "with (b)", Line: 5})
|
||||
AddDirective(meta, Directive{Namespace: "postgres", Args: "with (a)", Line: 5})
|
||||
AddDirective(meta, Directive{Namespace: "postgres", Args: "partition by x", Line: 2})
|
||||
|
||||
got := GetDirectives(meta)
|
||||
wantArgs := []string{"partition by x", "with (a)", "with (b)", "strict"}
|
||||
if len(got) != len(wantArgs) {
|
||||
t.Fatalf("got %d directives, want %d", len(got), len(wantArgs))
|
||||
}
|
||||
for i, w := range wantArgs {
|
||||
if got[i].Args != w {
|
||||
t.Errorf("directive[%d].Args = %q, want %q", i, got[i].Args, w)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetDirectivesTolerantDecodeAfterJSON(t *testing.T) {
|
||||
meta := map[string]any{}
|
||||
AddDirective(meta, Directive{Namespace: "postgres", Args: "partition by RANGE (created_at)", Line: 4})
|
||||
AddDirective(meta, Directive{Namespace: "sqlite", Args: "without rowid", Line: 6})
|
||||
|
||||
blob, err := json.Marshal(meta)
|
||||
if err != nil {
|
||||
t.Fatalf("marshal: %v", err)
|
||||
}
|
||||
var round map[string]any
|
||||
if err := json.Unmarshal(blob, &round); err != nil {
|
||||
t.Fatalf("unmarshal: %v", err)
|
||||
}
|
||||
|
||||
got := GetDirectives(round)
|
||||
if len(got) != 2 {
|
||||
t.Fatalf("got %d directives after JSON round-trip, want 2", len(got))
|
||||
}
|
||||
if got[0].Namespace != "postgres" || got[0].Key != "partition" || got[0].Line != 4 {
|
||||
t.Errorf("post-JSON directive[0] = %+v", got[0])
|
||||
}
|
||||
if got[0].Args != "partition by RANGE (created_at)" {
|
||||
t.Errorf("post-JSON args not verbatim: %q", got[0].Args)
|
||||
}
|
||||
if got[1].Namespace != "sqlite" || got[1].Key != "without" {
|
||||
t.Errorf("post-JSON directive[1] = %+v", got[1])
|
||||
}
|
||||
}
|
||||
|
||||
func TestDirectivesForNamespaceAndHasDirective(t *testing.T) {
|
||||
meta := map[string]any{}
|
||||
AddDirective(meta, Directive{Namespace: "postgres", Args: "partition by x", Line: 1})
|
||||
AddDirective(meta, Directive{Namespace: "sqlite", Args: "strict", Line: 2})
|
||||
|
||||
pg := DirectivesForNamespace(meta, "postgres")
|
||||
if len(pg) != 1 || pg[0].Key != "partition" {
|
||||
t.Errorf("DirectivesForNamespace(postgres) = %+v", pg)
|
||||
}
|
||||
if !HasDirective(meta, "sqlite", "strict") {
|
||||
t.Error("HasDirective(sqlite, strict) = false, want true")
|
||||
}
|
||||
if HasDirective(meta, "postgres", "tablespace") {
|
||||
t.Error("HasDirective(postgres, tablespace) = true, want false")
|
||||
}
|
||||
}
|
||||
|
||||
func TestDirectiveLocationAllowed(t *testing.T) {
|
||||
if !DirectiveLocationAllowed("postgres", "partition", DirectiveLocationTable) {
|
||||
t.Error("partition should be allowed at table level")
|
||||
}
|
||||
if DirectiveLocationAllowed("postgres", "partition", DirectiveLocationColumn) {
|
||||
t.Error("partition should not be allowed at column level")
|
||||
}
|
||||
// Unknown directives are allowed everywhere so they can be preserved.
|
||||
if !DirectiveLocationAllowed("postgres", "bogus", DirectiveLocationDatabase) {
|
||||
t.Error("unknown key should be allowed everywhere")
|
||||
}
|
||||
if !DirectiveLocationAllowed("madeup", "x", DirectiveLocationTable) {
|
||||
t.Error("unknown namespace should be allowed everywhere")
|
||||
}
|
||||
}
|
||||
|
||||
func TestFormatDirectiveLine(t *testing.T) {
|
||||
d := Directive{Namespace: "postgres", Key: "identity", Args: "identity always"}
|
||||
if got := FormatDirectiveLine(d, ""); got != "@postgres: identity always" {
|
||||
t.Errorf("FormatDirectiveLine no target = %q", got)
|
||||
}
|
||||
if got := FormatDirectiveLine(d, "id"); got != "@postgres(id): identity always" {
|
||||
t.Errorf("FormatDirectiveLine with target = %q", got)
|
||||
}
|
||||
}
|
||||
+59
-53
@@ -24,16 +24,17 @@ const (
|
||||
|
||||
// Database represents the complete database schema
|
||||
type Database struct {
|
||||
Name string `json:"name" yaml:"name"`
|
||||
Description string `json:"description,omitempty" yaml:"description,omitempty" xml:"description,omitempty"`
|
||||
Schemas []*Schema `json:"schemas" yaml:"schemas" xml:"schemas"`
|
||||
Domains []*Domain `json:"domains,omitempty" yaml:"domains,omitempty" xml:"domains,omitempty"`
|
||||
Comment string `json:"comment,omitempty" yaml:"comment,omitempty" xml:"comment,omitempty"`
|
||||
DatabaseType DatabaseType `json:"database_type,omitempty" yaml:"database_type,omitempty" xml:"database_type,omitempty"`
|
||||
DatabaseVersion string `json:"database_version,omitempty" yaml:"database_version,omitempty" xml:"database_version,omitempty"`
|
||||
SourceFormat string `json:"source_format,omitempty" yaml:"source_format,omitempty" xml:"source_format,omitempty"` // Source Format of the database.
|
||||
UpdatedAt string `json:"updatedat,omitempty" yaml:"updatedat,omitempty" xml:"updatedat,omitempty"`
|
||||
GUID string `json:"guid" yaml:"guid" xml:"guid"`
|
||||
Name string `json:"name" yaml:"name"`
|
||||
Description string `json:"description,omitempty" yaml:"description,omitempty" xml:"description,omitempty"`
|
||||
Schemas []*Schema `json:"schemas" yaml:"schemas" xml:"schemas"`
|
||||
Domains []*Domain `json:"domains,omitempty" yaml:"domains,omitempty" xml:"domains,omitempty"`
|
||||
Comment string `json:"comment,omitempty" yaml:"comment,omitempty" xml:"comment,omitempty"`
|
||||
DatabaseType DatabaseType `json:"database_type,omitempty" yaml:"database_type,omitempty" xml:"database_type,omitempty"`
|
||||
DatabaseVersion string `json:"database_version,omitempty" yaml:"database_version,omitempty" xml:"database_version,omitempty"`
|
||||
SourceFormat string `json:"source_format,omitempty" yaml:"source_format,omitempty" xml:"source_format,omitempty"` // Source Format of the database.
|
||||
Metadata map[string]any `json:"metadata,omitempty" yaml:"metadata,omitempty" xml:"-"`
|
||||
UpdatedAt string `json:"updatedat,omitempty" yaml:"updatedat,omitempty" xml:"updatedat,omitempty"`
|
||||
GUID string `json:"guid" yaml:"guid" xml:"guid"`
|
||||
}
|
||||
|
||||
// SQLName returns the database name in lowercase for SQL compatibility.
|
||||
@@ -226,22 +227,23 @@ func (d *Sequence) SQLName() string {
|
||||
|
||||
// Column represents a table column
|
||||
type Column struct {
|
||||
Name string `json:"name" yaml:"name" xml:"name"`
|
||||
Description string `json:"description,omitempty" yaml:"description,omitempty" xml:"description,omitempty"`
|
||||
Table string `json:"table" yaml:"table" xml:"table"`
|
||||
Schema string `json:"schema" yaml:"schema" xml:"schema"`
|
||||
Type string `json:"type" yaml:"type" xml:"type"`
|
||||
Length int `json:"length,omitempty" yaml:"length,omitempty" xml:"length,omitempty"`
|
||||
Precision int `json:"precision,omitempty" yaml:"precision,omitempty" xml:"precision,omitempty"`
|
||||
Scale int `json:"scale,omitempty" yaml:"scale,omitempty" xml:"scale,omitempty"`
|
||||
NotNull bool `json:"not_null" yaml:"not_null" xml:"not_null"`
|
||||
Default any `json:"default,omitempty" yaml:"default,omitempty" xml:"default,omitempty"`
|
||||
AutoIncrement bool `json:"auto_increment" yaml:"auto_increment" xml:"auto_increment"`
|
||||
IsPrimaryKey bool `json:"is_primary_key" yaml:"is_primary_key" xml:"is_primary_key"`
|
||||
Comment string `json:"comment,omitempty" yaml:"comment,omitempty" xml:"comment,omitempty"`
|
||||
Collation string `json:"collation,omitempty" yaml:"collation,omitempty" xml:"collation,omitempty"`
|
||||
Sequence uint `json:"sequence,omitempty" yaml:"sequence,omitempty" xml:"sequence,omitempty"`
|
||||
GUID string `json:"guid" yaml:"guid" xml:"guid"`
|
||||
Name string `json:"name" yaml:"name" xml:"name"`
|
||||
Description string `json:"description,omitempty" yaml:"description,omitempty" xml:"description,omitempty"`
|
||||
Table string `json:"table" yaml:"table" xml:"table"`
|
||||
Schema string `json:"schema" yaml:"schema" xml:"schema"`
|
||||
Type string `json:"type" yaml:"type" xml:"type"`
|
||||
Length int `json:"length,omitempty" yaml:"length,omitempty" xml:"length,omitempty"`
|
||||
Precision int `json:"precision,omitempty" yaml:"precision,omitempty" xml:"precision,omitempty"`
|
||||
Scale int `json:"scale,omitempty" yaml:"scale,omitempty" xml:"scale,omitempty"`
|
||||
NotNull bool `json:"not_null" yaml:"not_null" xml:"not_null"`
|
||||
Default any `json:"default,omitempty" yaml:"default,omitempty" xml:"default,omitempty"`
|
||||
AutoIncrement bool `json:"auto_increment" yaml:"auto_increment" xml:"auto_increment"`
|
||||
IsPrimaryKey bool `json:"is_primary_key" yaml:"is_primary_key" xml:"is_primary_key"`
|
||||
Comment string `json:"comment,omitempty" yaml:"comment,omitempty" xml:"comment,omitempty"`
|
||||
Collation string `json:"collation,omitempty" yaml:"collation,omitempty" xml:"collation,omitempty"`
|
||||
Metadata map[string]any `json:"metadata,omitempty" yaml:"metadata,omitempty" xml:"-"`
|
||||
Sequence uint `json:"sequence,omitempty" yaml:"sequence,omitempty" xml:"sequence,omitempty"`
|
||||
GUID string `json:"guid" yaml:"guid" xml:"guid"`
|
||||
}
|
||||
|
||||
// SQLName returns the column name in lowercase for SQL compatibility.
|
||||
@@ -252,19 +254,20 @@ func (d *Column) SQLName() string {
|
||||
// Index represents a database index for optimizing query performance.
|
||||
// Indexes can be unique, partial, or include additional columns.
|
||||
type Index struct {
|
||||
Name string `json:"name" yaml:"name" xml:"name"`
|
||||
Description string `json:"description,omitempty" yaml:"description,omitempty" xml:"description,omitempty"`
|
||||
Table string `json:"table,omitempty" yaml:"table,omitempty" xml:"table,omitempty"`
|
||||
Schema string `json:"schema,omitempty" yaml:"schema,omitempty" xml:"schema,omitempty"`
|
||||
Columns []string `json:"columns" yaml:"columns" xml:"columns"`
|
||||
Unique bool `json:"unique" yaml:"unique" xml:"unique"`
|
||||
Type string `json:"type" yaml:"type" xml:"type"` // btree, hash, gin, gist, etc.
|
||||
Where string `json:"where,omitempty" yaml:"where,omitempty" xml:"where,omitempty"` // partial index condition
|
||||
Concurrent bool `json:"concurrent,omitempty" yaml:"concurrent,omitempty" xml:"concurrent,omitempty"`
|
||||
Include []string `json:"include,omitempty" yaml:"include,omitempty" xml:"include,omitempty"` // INCLUDE columns
|
||||
Comment string `json:"comment,omitempty" yaml:"comment,omitempty" xml:"comment,omitempty"`
|
||||
Sequence uint `json:"sequence,omitempty" yaml:"sequence,omitempty" xml:"sequence,omitempty"`
|
||||
GUID string `json:"guid" yaml:"guid" xml:"guid"`
|
||||
Name string `json:"name" yaml:"name" xml:"name"`
|
||||
Description string `json:"description,omitempty" yaml:"description,omitempty" xml:"description,omitempty"`
|
||||
Table string `json:"table,omitempty" yaml:"table,omitempty" xml:"table,omitempty"`
|
||||
Schema string `json:"schema,omitempty" yaml:"schema,omitempty" xml:"schema,omitempty"`
|
||||
Columns []string `json:"columns" yaml:"columns" xml:"columns"`
|
||||
Unique bool `json:"unique" yaml:"unique" xml:"unique"`
|
||||
Type string `json:"type" yaml:"type" xml:"type"` // btree, hash, gin, gist, etc.
|
||||
Where string `json:"where,omitempty" yaml:"where,omitempty" xml:"where,omitempty"` // partial index condition
|
||||
Concurrent bool `json:"concurrent,omitempty" yaml:"concurrent,omitempty" xml:"concurrent,omitempty"`
|
||||
Include []string `json:"include,omitempty" yaml:"include,omitempty" xml:"include,omitempty"` // INCLUDE columns
|
||||
Comment string `json:"comment,omitempty" yaml:"comment,omitempty" xml:"comment,omitempty"`
|
||||
Metadata map[string]any `json:"metadata,omitempty" yaml:"metadata,omitempty" xml:"-"`
|
||||
Sequence uint `json:"sequence,omitempty" yaml:"sequence,omitempty" xml:"sequence,omitempty"`
|
||||
GUID string `json:"guid" yaml:"guid" xml:"guid"`
|
||||
}
|
||||
|
||||
// SQLName returns the index name in lowercase for SQL compatibility.
|
||||
@@ -393,10 +396,11 @@ func (d *Script) SQLName() string {
|
||||
// InitDatabase initializes a new Database with empty slices
|
||||
func InitDatabase(name string) *Database {
|
||||
return &Database{
|
||||
Name: name,
|
||||
Schemas: make([]*Schema, 0),
|
||||
Domains: make([]*Domain, 0),
|
||||
GUID: uuid.New().String(),
|
||||
Name: name,
|
||||
Schemas: make([]*Schema, 0),
|
||||
Domains: make([]*Domain, 0),
|
||||
Metadata: make(map[string]any),
|
||||
GUID: uuid.New().String(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -431,22 +435,24 @@ func InitTable(name, schema string) *Table {
|
||||
// InitColumn initializes a new Column
|
||||
func InitColumn(name, table, schema string) *Column {
|
||||
return &Column{
|
||||
Name: name,
|
||||
Table: table,
|
||||
Schema: schema,
|
||||
GUID: uuid.New().String(),
|
||||
Name: name,
|
||||
Table: table,
|
||||
Schema: schema,
|
||||
Metadata: make(map[string]any),
|
||||
GUID: uuid.New().String(),
|
||||
}
|
||||
}
|
||||
|
||||
// InitIndex initializes a new Index with empty slices
|
||||
func InitIndex(name, table, schema string) *Index {
|
||||
return &Index{
|
||||
Name: name,
|
||||
Table: table,
|
||||
Schema: schema,
|
||||
Columns: make([]string, 0),
|
||||
Include: make([]string, 0),
|
||||
GUID: uuid.New().String(),
|
||||
Name: name,
|
||||
Table: table,
|
||||
Schema: schema,
|
||||
Columns: make([]string, 0),
|
||||
Include: make([]string, 0),
|
||||
Metadata: make(map[string]any),
|
||||
GUID: uuid.New().String(),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user