diff --git a/lib/dbsteward.go b/lib/dbsteward.go
index 1b94481..7366bf4 100644
--- a/lib/dbsteward.go
+++ b/lib/dbsteward.go
@@ -473,7 +473,7 @@ func (dbsteward *DBSteward) doXmlDataInsert(defFile string, dataFile string) {
defFileModified := defFile + ".xmldatainserted"
dbsteward.Info("Saving modified dbsteward definition as %s", defFileModified)
- err = xml.SaveDefinition(defFileModified, defDoc)
+ err = xml.SaveDefinition(dbsteward.Logger(), defFileModified, defDoc)
dbsteward.FatalIfError(err, "saving file")
}
func (dbsteward *DBSteward) doXmlSort(files []string) {
@@ -493,7 +493,7 @@ func (dbsteward *DBSteward) doXmlConvert(files []string) {
doc, err := xml.LoadDefintion(file)
dbsteward.FatalIfError(err, "Could not load %s", file)
xml.SqlFormatConvert(doc)
- convertedXml, err := xml.FormatXml(doc)
+ convertedXml, err := xml.FormatXml(dbsteward.Logger(), doc)
dbsteward.FatalIfError(err, "formatting xml")
convertedXml = strings.Replace(convertedXml, "pgdbxml>", "dbsteward>", -1)
err = util.WriteFile(convertedXml, convertedFileName)
@@ -509,7 +509,7 @@ func (dbsteward *DBSteward) doXmlSlonyId(files []string, slonyOut string) {
outputPrefix := dbsteward.calculateFileOutputPrefix(files)
compositeFile := outputPrefix + "_composite.xml"
dbsteward.Info("Saving composite as %s", compositeFile)
- err = xml.SaveDefinition(compositeFile, dbDoc)
+ err = xml.SaveDefinition(dbsteward.Logger(), compositeFile, dbDoc)
dbsteward.FatalIfError(err, "saving file")
dbsteward.Info("Slony ID numbering any missing attributes")
@@ -521,7 +521,7 @@ func (dbsteward *DBSteward) doXmlSlonyId(files []string, slonyOut string) {
slonyIdNumberedFile = slonyOut
}
dbsteward.Info("Saving Slony ID numbered XML as %s", slonyIdNumberedFile)
- err = xml.SaveDefinition(slonyIdNumberedFile, slonyIdDoc)
+ err = xml.SaveDefinition(dbsteward.Logger(), slonyIdNumberedFile, slonyIdDoc)
dbsteward.FatalIfError(err, "saving file")
}
func (dbsteward *DBSteward) doBuild(files []string, dataFiles []string, addendums uint) {
@@ -542,13 +542,13 @@ func (dbsteward *DBSteward) doBuild(files []string, dataFiles []string, addendum
outputPrefix := dbsteward.calculateFileOutputPrefix(files)
compositeFile := outputPrefix + "_composite.xml"
dbsteward.Info("Saving composite as %s", compositeFile)
- err = xml.SaveDefinition(compositeFile, dbDoc)
+ err = xml.SaveDefinition(dbsteward.Logger(), compositeFile, dbDoc)
dbsteward.FatalIfError(err, "saving file")
if addendumsDoc != nil {
addendumsFile := outputPrefix + "_addendums.xml"
dbsteward.Info("Saving addendums as %s", addendumsFile)
- err = xml.SaveDefinition(compositeFile, addendumsDoc)
+ err = xml.SaveDefinition(dbsteward.Logger(), compositeFile, addendumsDoc)
dbsteward.FatalIfError(err, "saving file")
}
@@ -573,13 +573,13 @@ func (dbsteward *DBSteward) doDiff(oldFiles []string, newFiles []string, dataFil
oldOutputPrefix := dbsteward.calculateFileOutputPrefix(oldFiles)
oldCompositeFile := oldOutputPrefix + "_composite.xml"
dbsteward.Info("Saving composite as %s", oldCompositeFile)
- err = xml.SaveDefinition(oldCompositeFile, oldDbDoc)
+ err = xml.SaveDefinition(dbsteward.Logger(), oldCompositeFile, oldDbDoc)
dbsteward.FatalIfError(err, "saving file")
newOutputPrefix := dbsteward.calculateFileOutputPrefix(newFiles)
newCompositeFile := newOutputPrefix + "_composite.xml"
dbsteward.Info("Saving composite as %s", newCompositeFile)
- err = xml.SaveDefinition(newCompositeFile, newDbDoc)
+ err = xml.SaveDefinition(dbsteward.Logger(), newCompositeFile, newDbDoc)
dbsteward.FatalIfError(err, "saving file")
err = dbsteward.Lookup().OperationsConstructor().BuildUpgrade(
@@ -591,7 +591,7 @@ func (dbsteward *DBSteward) doDiff(oldFiles []string, newFiles []string, dataFil
func (dbsteward *DBSteward) doExtract(dbHost string, dbPort uint, dbName, dbUser, dbPass string, outputFile string) {
output := dbsteward.Lookup().OperationsConstructor().ExtractSchema(dbHost, dbPort, dbName, dbUser, dbPass)
dbsteward.Info("Saving extracted database schema to %s", outputFile)
- err := xml.SaveDefinition(outputFile, output)
+ err := xml.SaveDefinition(dbsteward.Logger(), outputFile, output)
dbsteward.FatalIfError(err, "saving file")
}
func (dbsteward *DBSteward) doDbDataDiff(files []string, dataFiles []string, addendums uint, dbHost string, dbPort uint, dbName, dbUser, dbPass string) {
@@ -614,12 +614,12 @@ func (dbsteward *DBSteward) doDbDataDiff(files []string, dataFiles []string, add
outputPrefix := dbsteward.calculateFileOutputPrefix(files)
compositeFile := outputPrefix + "_composite.xml"
dbsteward.Info("Saving composite as %s", compositeFile)
- err = xml.SaveDefinition(compositeFile, dbDoc)
+ err = xml.SaveDefinition(dbsteward.Logger(), compositeFile, dbDoc)
dbsteward.FatalIfError(err, "saving file")
output, err := dbsteward.Lookup().OperationsConstructor().CompareDbData(dbDoc, dbHost, dbPort, dbName, dbUser, dbPass)
dbsteward.FatalIfError(err, "comparing data")
- err = xml.SaveDefinition(compositeFile, output)
+ err = xml.SaveDefinition(dbsteward.Logger(), compositeFile, output)
dbsteward.FatalIfError(err, "saving file")
}
func (dbsteward *DBSteward) doSqlDiff(oldSql, newSql []string, outputFile string) {
diff --git a/lib/encoding/xml/column.go b/lib/encoding/xml/column.go
index c79490e..773be33 100644
--- a/lib/encoding/xml/column.go
+++ b/lib/encoding/xml/column.go
@@ -3,6 +3,7 @@ package xml
import (
"encoding/xml"
"fmt"
+ "log/slog"
"github.com/dbsteward/dbsteward/lib/ir"
)
@@ -42,18 +43,72 @@ type Column struct {
AfterAddPostStage3 string `xml:"afterAddPostStage3,attr,omitempty"`
}
+func ColumnsFromIR(l *slog.Logger, cols []*ir.Column) ([]*Column, error) {
+ if len(cols) == 0 {
+ return nil, nil
+ }
+ var rv []*Column
+ for _, c := range cols {
+ if c != nil {
+ nc, err := ColumnFromIR(l, c)
+ if err != nil {
+ return nil, err
+ }
+ rv = append(rv, nc)
+ }
+ }
+ return rv, nil
+}
+
+func ColumnFromIR(l *slog.Logger, col *ir.Column) (*Column, error) {
+ if col == nil {
+ return nil, nil
+ }
+ l = l.With(slog.String("column", col.Name))
+ l.Debug("converting column")
+ defer l.Debug("done converting column")
+ rv := Column{
+ Name: col.Name,
+ Type: col.Type,
+ Nullable: col.Nullable,
+ Default: col.Default,
+ Description: col.Description,
+ Unique: col.Unique,
+ Check: col.Check,
+ SerialStart: col.SerialStart,
+ OldColumnName: col.OldColumnName,
+ ConvertUsing: col.ConvertUsing,
+ ForeignSchema: col.ForeignSchema,
+ ForeignTable: col.ForeignTable,
+ ForeignColumn: col.ForeignColumn,
+ ForeignKeyName: col.ForeignKeyName,
+ ForeignIndexName: col.ForeignIndexName,
+ ForeignOnUpdate: string(col.ForeignOnUpdate),
+ ForeignOnDelete: string(col.ForeignOnDelete),
+ Statistics: col.Statistics,
+ BeforeAddStage1: col.BeforeAddStage1,
+ AfterAddStage1: col.AfterAddStage1,
+ BeforeAddStage2: col.BeforeAddStage2,
+ AfterAddStage2: col.AfterAddStage2,
+ BeforeAddStage3: col.BeforeAddStage3,
+ AfterAddStage3: col.AfterAddStage3,
+ // Ignoring depricated fields for now
+ }
+ return &rv, nil
+}
+
// Implement some custom unmarshalling behavior
-func (self *Column) UnmarshalXML(decoder *xml.Decoder, start xml.StartElement) error {
+func (col *Column) UnmarshalXML(decoder *xml.Decoder, start xml.StartElement) error {
type colAlias Column // prevents recursion while decoding, as type aliases have no methods
// set defaults
- col := &colAlias{
+ ca := &colAlias{
Nullable: true, // as in SQL NULL
}
- err := decoder.DecodeElement(col, &start)
+ err := decoder.DecodeElement(ca, &start)
if err != nil {
return err
}
- *self = Column(*col)
+ *col = Column(*col)
return nil
}
diff --git a/lib/encoding/xml/constraint.go b/lib/encoding/xml/constraint.go
index ef4e1ad..d0f3928 100644
--- a/lib/encoding/xml/constraint.go
+++ b/lib/encoding/xml/constraint.go
@@ -2,6 +2,7 @@ package xml
import (
"fmt"
+ "log/slog"
"strings"
"github.com/dbsteward/dbsteward/lib/ir"
@@ -16,6 +17,29 @@ type Constraint struct {
ForeignTable string `xml:"foreignTable,attr,omitempty"`
}
+func ConstraintsFromIR(l *slog.Logger, cs []*ir.Constraint) ([]*Constraint, error) {
+ if len(cs) == 0 {
+ return nil, nil
+ }
+ var rv []*Constraint
+ for _, c := range cs {
+ if c != nil {
+ rv = append(
+ rv,
+ &Constraint{
+ Name: c.Name,
+ Type: string(c.Type),
+ Definition: c.Definition,
+ ForeignIndexName: c.ForeignIndexName,
+ ForeignSchema: c.ForeignSchema,
+ ForeignTable: c.ForeignTable,
+ },
+ )
+ }
+ }
+ return rv, nil
+}
+
func (c *Constraint) ToIR() (*ir.Constraint, error) {
rv := ir.Constraint{
Name: c.Name,
@@ -32,17 +56,17 @@ func (c *Constraint) ToIR() (*ir.Constraint, error) {
return &rv, nil
}
-func (self *Constraint) IdentityMatches(other *Constraint) bool {
+func (c *Constraint) IdentityMatches(other *Constraint) bool {
if other == nil {
return false
}
- return strings.EqualFold(self.Name, other.Name)
+ return strings.EqualFold(c.Name, other.Name)
}
-func (self *Constraint) Merge(overlay *Constraint) {
+func (c *Constraint) Merge(overlay *Constraint) {
if overlay == nil {
return
}
- self.Type = overlay.Type
- self.Definition = overlay.Definition
+ c.Type = overlay.Type
+ c.Definition = overlay.Definition
}
diff --git a/lib/encoding/xml/data_rows.go b/lib/encoding/xml/data_rows.go
index 614a42e..2c8ddf5 100644
--- a/lib/encoding/xml/data_rows.go
+++ b/lib/encoding/xml/data_rows.go
@@ -1,6 +1,7 @@
package xml
import (
+ "log/slog"
"strings"
"github.com/dbsteward/dbsteward/lib/ir"
@@ -15,6 +16,47 @@ type DataRows struct {
TabRows []string `xml:"tabrow"`
}
+func DataRowsFromIR(l *slog.Logger, rows *ir.DataRows) (*DataRows, error) {
+ if rows == nil {
+ return nil, nil
+ }
+ rv := DataRows{
+ TabRowDelimiter: rows.TabRowDelimiter,
+ Columns: rows.Columns,
+ }
+ rv.TabRows = append(rv.TabRows, rows.TabRows...)
+ for _, row := range rows.Rows {
+ if row != nil {
+ rv.Rows = append(
+ rv.Rows,
+ &DataRow{
+ Columns: DataColFromIR(l, row.Columns),
+ Delete: row.Delete,
+ },
+ )
+ }
+ }
+ return &rv, nil
+}
+
+func DataColFromIR(l *slog.Logger, cols []*ir.DataCol) []*DataCol {
+ var rv []*DataCol
+ for _, col := range cols {
+ if col != nil {
+ rv = append(
+ rv,
+ &DataCol{
+ Null: col.Null,
+ Empty: col.Empty,
+ Sql: col.Sql,
+ Text: col.Text,
+ },
+ )
+ }
+ }
+ return rv
+}
+
func (dr *DataRows) ToIR() (*ir.DataRows, error) {
if dr == nil {
return nil, nil
@@ -76,20 +118,20 @@ func (dc *DataCol) ToIR() (*ir.DataCol, error) {
return &rv, nil
}
-func (self *DataRows) AddColumn(name string, value string) error {
- if self.HasColumn(name) {
+func (drs *DataRows) AddColumn(name string, value string) error {
+ if drs.HasColumn(name) {
return errors.Errorf("already has column %s", name)
}
- self.Columns = append(self.Columns, name)
- for _, row := range self.Rows {
+ drs.Columns = append(drs.Columns, name)
+ for _, row := range drs.Rows {
// TODO(feat) what about nulls?
row.Columns = append(row.Columns, &DataCol{Text: value})
}
return nil
}
-func (self *DataRows) HasColumn(name string) bool {
- for _, col := range self.Columns {
+func (drs *DataRows) HasColumn(name string) bool {
+ for _, col := range drs.Columns {
if col == name {
return true
}
@@ -98,13 +140,13 @@ func (self *DataRows) HasColumn(name string) bool {
}
// Replaces TabRows with Rows
-func (self *DataRows) ConvertTabRows() {
- delimiter := util.CoalesceStr(self.TabRowDelimiter, "\t")
+func (drs *DataRows) ConvertTabRows() {
+ delimiter := util.CoalesceStr(drs.TabRowDelimiter, "\t")
delimiter = strings.ReplaceAll(delimiter, "\\t", "\t")
delimiter = strings.ReplaceAll(delimiter, "\\n", "\n")
- self.TabRowDelimiter = ""
+ drs.TabRowDelimiter = ""
- for _, tabrow := range self.TabRows {
+ for _, tabrow := range drs.TabRows {
tabcols := strings.Split(tabrow, delimiter)
row := &DataRow{
Columns: make([]*DataCol, len(tabcols)),
@@ -119,26 +161,26 @@ func (self *DataRows) ConvertTabRows() {
}
}
}
- self.TabRows = nil
+ drs.TabRows = nil
}
-func (self *DataRows) GetColMap(row *DataRow) map[string]*DataCol {
- return self.GetColMapKeys(row, self.Columns)
+func (drs *DataRows) GetColMap(row *DataRow) map[string]*DataCol {
+ return drs.GetColMapKeys(row, drs.Columns)
}
-func (self *DataRows) GetColMapKeys(row *DataRow, keys []string) map[string]*DataCol {
+func (drs *DataRows) GetColMapKeys(row *DataRow, keys []string) map[string]*DataCol {
out := map[string]*DataCol{}
for i, col := range row.Columns {
- if util.IStrsContains(keys, self.Columns[i]) {
- out[self.Columns[i]] = col
+ if util.IStrsContains(keys, drs.Columns[i]) {
+ out[drs.Columns[i]] = col
}
}
return out
}
-func (self *DataRows) TryGetRowMatchingColMap(colmap map[string]*DataCol) *DataRow {
- for _, row := range self.Rows {
- if self.RowMatchesColMap(row, colmap) {
+func (drs *DataRows) TryGetRowMatchingColMap(colmap map[string]*DataCol) *DataRow {
+ for _, row := range drs.Rows {
+ if drs.RowMatchesColMap(row, colmap) {
return row
}
}
@@ -146,10 +188,10 @@ func (self *DataRows) TryGetRowMatchingColMap(colmap map[string]*DataCol) *DataR
}
// `row` matches `colmap` if all the columns in colmap match a column in the row
-func (self *DataRows) RowMatchesColMap(row *DataRow, colmap map[string]*DataCol) bool {
+func (drs *DataRows) RowMatchesColMap(row *DataRow, colmap map[string]*DataCol) bool {
for colName, col := range colmap {
// find the corresponding column
- idx := util.IStrsIndex(self.Columns, colName)
+ idx := util.IStrsIndex(drs.Columns, colName)
if idx < 0 {
return false // the column doesn't exist in this DataRows
}
@@ -162,11 +204,11 @@ func (self *DataRows) RowMatchesColMap(row *DataRow, colmap map[string]*DataCol)
return true
}
-func (self *DataRows) tryGetColIndexesOfNames(names []string) ([]int, bool) {
+func (drs *DataRows) tryGetColIndexesOfNames(names []string) ([]int, bool) {
out := make([]int, len(names))
for i, name := range names {
found := false
- for j, col := range self.Columns {
+ for j, col := range drs.Columns {
if strings.EqualFold(name, col) {
out[i] = j
found = true
@@ -181,8 +223,8 @@ func (self *DataRows) tryGetColIndexesOfNames(names []string) ([]int, bool) {
}
// checks to see that ownRow == otherRow, accounting for possible differences in column count or order
-func (self *DataRows) RowEquals(ownRow, otherRow *DataRow, otherColumns []string) bool {
- if len(self.Columns) != len(otherColumns) {
+func (drs *DataRows) RowEquals(ownRow, otherRow *DataRow, otherColumns []string) bool {
+ if len(drs.Columns) != len(otherColumns) {
return false
}
@@ -190,7 +232,7 @@ func (self *DataRows) RowEquals(ownRow, otherRow *DataRow, otherColumns []string
return false
}
- otherIndexes, ok := self.tryGetColIndexesOfNames(otherColumns)
+ otherIndexes, ok := drs.tryGetColIndexesOfNames(otherColumns)
if !ok {
return false
}
@@ -204,19 +246,19 @@ func (self *DataRows) RowEquals(ownRow, otherRow *DataRow, otherColumns []string
return false
}
-func (self *DataCol) Equals(other *DataCol) bool {
- if self == nil || other == nil {
+func (dc *DataCol) Equals(other *DataCol) bool {
+ if dc == nil || other == nil {
return false
}
- if self.Null && other.Null {
+ if dc.Null && other.Null {
return true
}
- if self.Empty && other.Empty {
+ if dc.Empty && other.Empty {
return true
}
- if self.Sql != other.Sql {
+ if dc.Sql != other.Sql {
return false
}
// TODO(feat) something other than plain old string equality?
- return self.Text == other.Text
+ return dc.Text == other.Text
}
diff --git a/lib/encoding/xml/database.go b/lib/encoding/xml/database.go
index ff87af8..58a46d2 100644
--- a/lib/encoding/xml/database.go
+++ b/lib/encoding/xml/database.go
@@ -2,6 +2,7 @@ package xml
import (
"fmt"
+ "log/slog"
"github.com/dbsteward/dbsteward/lib/ir"
)
@@ -22,11 +23,43 @@ type RoleAssignment struct {
CustomRoles DelimitedList `xml:"customRole,omitempty"`
}
+func RoleAssignmentFromIR(l *slog.Logger, i *ir.RoleAssignment) *RoleAssignment {
+ l.Debug("translating role assignments")
+ defer l.Debug("done translating role assignments")
+ ra := RoleAssignment{
+ Application: i.Application,
+ Owner: i.Owner,
+ Replication: i.Replication,
+ ReadOnly: i.ReadOnly,
+ CustomRoles: i.CustomRoles,
+ }
+ return &ra
+}
+
type ConfigParam struct {
Name string `xml:"name,attr"`
Value string `xml:"value,attr"`
}
+func ConfigParamsFromIR(l *slog.Logger, c []*ir.ConfigParam) []*ConfigParam {
+ l.Debug("translating config parameters")
+ defer l.Debug("done translating config parameters")
+ if len(c) == 0 {
+ return nil
+ }
+ var cp []*ConfigParam
+ for _, ircp := range c {
+ cp = append(
+ cp,
+ &ConfigParam{
+ Name: ircp.Name,
+ Value: ircp.Value,
+ },
+ )
+ }
+ return cp
+}
+
func (db *Database) ToIR() (*ir.Database, error) {
if db == nil {
return nil, nil
diff --git a/lib/encoding/xml/datatype.go b/lib/encoding/xml/datatype.go
index 5932542..a3c0680 100644
--- a/lib/encoding/xml/datatype.go
+++ b/lib/encoding/xml/datatype.go
@@ -1,6 +1,10 @@
package xml
-import "github.com/dbsteward/dbsteward/lib/ir"
+import (
+ "log/slog"
+
+ "github.com/dbsteward/dbsteward/lib/ir"
+)
type DataType struct {
Name string `xml:"name,attr,omitempty"`
@@ -12,26 +16,116 @@ type DataType struct {
DomainConstraints []*DataTypeDomainConstraint `xml:"domainConstraint"`
}
+func TypesFromIR(l *slog.Logger, types []*ir.TypeDef) ([]*DataType, error) {
+ if len(types) == 0 {
+ return nil, nil
+ }
+ var rv []*DataType
+ for _, t := range types {
+ if t != nil {
+ ndt, err := TypeFromIR(l, t)
+ if err != nil {
+ return nil, err
+ }
+ rv = append(rv, ndt)
+ }
+ }
+ return rv, nil
+}
+
+func TypeFromIR(l *slog.Logger, t *ir.TypeDef) (*DataType, error) {
+ l = l.With(slog.String("TypeDef", t.Name))
+ ndt := DataType{
+ Name: t.Name,
+ Kind: t.Kind.String(),
+ // SlonySetId Not in the IR
+ EnumValues: DataTypeEnumValuesFromIR(l, t.EnumValues),
+ CompositeFields: DataTypeCompositFieldsFromIR(l, t.CompositeFields),
+ DomainType: DataTypeDomainTypeFromIR(l, t.DomainType),
+ DomainConstraints: DataTypeDomainConstraintsFromIR(l, t.DomainConstraints),
+ }
+ return &ndt, nil
+}
+
type DataTypeEnumValue struct {
Value string `xml:"name,attr"`
}
+func DataTypeEnumValuesFromIR(l *slog.Logger, vals []ir.DataTypeEnumValue) []*DataTypeEnumValue {
+ if len(vals) == 0 {
+ return nil
+ }
+ var rv []*DataTypeEnumValue
+ for _, val := range vals {
+ rv = append(
+ rv,
+ &DataTypeEnumValue{Value: string(val)},
+ )
+ }
+ return rv
+}
+
type DataTypeCompositeField struct {
Name string `xml:"name,attr"`
Type string `xml:"type,attr"`
}
+func DataTypeCompositFieldsFromIR(l *slog.Logger, vals []ir.DataTypeCompositeField) []*DataTypeCompositeField {
+ if len(vals) == 0 {
+ return nil
+ }
+ var rv []*DataTypeCompositeField
+ for _, val := range vals {
+ rv = append(
+ rv,
+ &DataTypeCompositeField{
+ Name: val.Name,
+ Type: val.Type,
+ },
+ )
+ }
+ return rv
+}
+
type DataTypeDomainType struct {
BaseType string `xml:"baseType,attr"`
Default string `xml:"default,attr,omitempty"`
Nullable bool `xml:"null,attr,omitempty"`
}
+func DataTypeDomainTypeFromIR(l *slog.Logger, d *ir.DataTypeDomainType) *DataTypeDomainType {
+ if d == nil {
+ return nil
+ }
+ return &DataTypeDomainType{
+ BaseType: d.BaseType,
+ Default: d.Default,
+ Nullable: d.Nullable,
+ }
+}
+
type DataTypeDomainConstraint struct {
Name string `xml:"name,attr,omitempty"`
Check string `xml:",chardata"`
}
-func (self *DataType) ToIR() (*ir.TypeDef, error) {
+func DataTypeDomainConstraintsFromIR(l *slog.Logger, cons []ir.DataTypeDomainConstraint) []*DataTypeDomainConstraint {
+ if len(cons) == 0 {
+ return nil
+ }
+ var rv []*DataTypeDomainConstraint
+ for _, con := range cons {
+ rv = append(
+ rv,
+ &DataTypeDomainConstraint{
+ Name: con.Name,
+ Check: con.Check,
+ },
+ )
+ }
+ return rv
+}
+
+func (dt *DataType) ToIR() (*ir.TypeDef, error) {
panic("todo")
}
diff --git a/lib/encoding/xml/document.go b/lib/encoding/xml/document.go
index 7518bb6..66eda97 100644
--- a/lib/encoding/xml/document.go
+++ b/lib/encoding/xml/document.go
@@ -2,6 +2,7 @@ package xml
import (
"encoding/xml"
+ "log/slog"
"github.com/dbsteward/dbsteward/lib/ir"
"github.com/dbsteward/dbsteward/lib/util"
@@ -22,10 +23,46 @@ type IncludeFile struct {
Name string `xml:"name,attr"`
}
+func IncludeFilesFromIR(l *slog.Logger, files []*ir.IncludeFile) ([]*IncludeFile, error) {
+ if len(files) == 0 {
+ return nil, nil
+ }
+ var rv []*IncludeFile
+ for _, file := range files {
+ if file != nil {
+ rv = append(
+ rv,
+ &IncludeFile{
+ Name: file.Name,
+ },
+ )
+ }
+ }
+ return rv, nil
+}
+
type InlineAssembly struct {
Name string `xml:"name,attr"`
}
+func InlineAssemblyFromIR(l *slog.Logger, recs []*ir.InlineAssembly) ([]*InlineAssembly, error) {
+ if len(recs) == 0 {
+ return nil, nil
+ }
+ var rv []*InlineAssembly
+ for _, rec := range recs {
+ if rec != nil {
+ rv = append(
+ rv,
+ &InlineAssembly{
+ Name: rec.Name,
+ },
+ )
+ }
+ }
+ return rv, nil
+}
+
type Sql struct {
Author string `xml:"author,attr"`
Ticket string `xml:"ticket,attr"`
@@ -41,33 +78,33 @@ type Sql struct {
// Errors may arise if this operation cannot be completed for some reason.
// No semantic validation is performed at this point, as that's outside
// of the scope of an "xml" package - see `ir.Definition.Validate()`
-func (self *Document) ToIR() (*ir.Definition, error) {
- includeFiles, err := util.MapErr(self.IncludeFiles, (*IncludeFile).ToIR)
+func (doc *Document) ToIR() (*ir.Definition, error) {
+ includeFiles, err := util.MapErr(doc.IncludeFiles, (*IncludeFile).ToIR)
if err != nil {
return nil, errors.Wrap(err, "could not process includeFile tags")
}
- inlineAssembly, err := util.MapErr(self.InlineAssembly, (*InlineAssembly).ToIR)
+ inlineAssembly, err := util.MapErr(doc.InlineAssembly, (*InlineAssembly).ToIR)
if err != nil {
return nil, errors.Wrap(err, "could not process inlineAssembly tags")
}
- database, err := self.Database.ToIR()
+ database, err := doc.Database.ToIR()
if err != nil {
return nil, errors.Wrap(err, "could not process database tag")
}
- schemas, err := util.MapErr(self.Schemas, (*Schema).ToIR)
+ schemas, err := util.MapErr(doc.Schemas, (*Schema).ToIR)
if err != nil {
return nil, errors.Wrap(err, "could not process schema tags")
}
- languages, err := util.MapErr(self.Languages, (*Language).ToIR)
+ languages, err := util.MapErr(doc.Languages, (*Language).ToIR)
if err != nil {
return nil, errors.Wrap(err, "could not process language tags")
}
- sql, err := util.MapErr(self.Sql, (*Sql).ToIR)
+ sql, err := util.MapErr(doc.Sql, (*Sql).ToIR)
if err != nil {
return nil, errors.Wrap(err, "could not process sql tags")
}
@@ -82,22 +119,45 @@ func (self *Document) ToIR() (*ir.Definition, error) {
}, nil
}
-// FromModel builds a Document from a ir.Definition
-//
-// Errors may arise if this operation cannot be completed for some reason.
-func (self *Document) FromModel(def *ir.Definition) error {
- return errors.New("not implemented")
+// FromIR builds an XML Document from an ir.Definition
+func FromIR(l *slog.Logger, def *ir.Definition) (*Document, error) {
+ l = l.With(slog.String("operation", "translate IR to XML"))
+ l.Debug("starting conversion")
+ defer l.Debug("complted conversion")
+ doc := Document{
+ Database: &Database{
+ SqlFormat: string(def.Database.SqlFormat),
+ Roles: RoleAssignmentFromIR(l, def.Database.Roles),
+ ConfigParams: ConfigParamsFromIR(l, def.Database.ConfigParams),
+ },
+ }
+ var err error
+ doc.Schemas, err = SchemasFromIR(l, def.Schemas)
+ if err != nil {
+ return nil, err
+ }
+ doc.IncludeFiles, err = IncludeFilesFromIR(l, def.IncludeFiles)
+ if err != nil {
+ return nil, err
+ }
+ doc.InlineAssembly, err = InlineAssemblyFromIR(l, def.InlineAssembly)
+ if err != nil {
+ return nil, err
+ }
+ // Languages
+ // SQL
+ return &doc, nil
}
// TODO should there be a ir.IncludeFile, or should we always overlay when converting to model?
-func (self *IncludeFile) ToIR() (*ir.IncludeFile, error) {
- return &ir.IncludeFile{Name: self.Name}, nil
+func (doc *IncludeFile) ToIR() (*ir.IncludeFile, error) {
+ return &ir.IncludeFile{Name: doc.Name}, nil
}
-func (self *InlineAssembly) ToIR() (*ir.InlineAssembly, error) {
- return &ir.InlineAssembly{Name: self.Name}, nil
+func (doc *InlineAssembly) ToIR() (*ir.InlineAssembly, error) {
+ return &ir.InlineAssembly{Name: doc.Name}, nil
}
-func (self *Sql) ToIR() (*ir.Sql, error) {
+func (sql *Sql) ToIR() (*ir.Sql, error) {
panic("todo")
}
diff --git a/lib/encoding/xml/file.go b/lib/encoding/xml/file.go
index 71301d3..d3d39c8 100644
--- a/lib/encoding/xml/file.go
+++ b/lib/encoding/xml/file.go
@@ -23,23 +23,24 @@ func LoadDefintion(file string) (*ir.Definition, error) {
return ReadDef(f)
}
-func SaveDefinition(filename string, def *ir.Definition) error {
+func SaveDefinition(l *slog.Logger, filename string, def *ir.Definition) error {
+ l = l.With(slog.String("filename", filename))
f, err := os.Create(filename)
if err != nil {
return fmt.Errorf("could not open file %s for writing: %w", filename, err)
}
defer f.Close()
- err = WriteDef(f, def)
+ err = WriteDef(l, f, def)
if err != nil {
return fmt.Errorf("could not write XML document to '%s': %w", filename, err)
}
return nil
}
-func FormatXml(def *ir.Definition) (string, error) {
+func FormatXml(l *slog.Logger, def *ir.Definition) (string, error) {
buf := &bytes.Buffer{}
- err := WriteDef(buf, def)
+ err := WriteDef(l, buf, def)
if err != nil {
return "", fmt.Errorf("could not marshal definition: %w", err)
}
@@ -76,7 +77,7 @@ func XmlCompositeAddendums(l *slog.Logger, files []string, addendums uint) (*ir.
return nil, nil, fmt.Errorf("failed to composite xml file %s: %w", file, err)
}
}
- formatted, err := FormatXml(composite)
+ formatted, err := FormatXml(l, composite)
if err != nil {
return nil, nil, err
}
diff --git a/lib/encoding/xml/foreignkey.go b/lib/encoding/xml/foreignkey.go
index 8ed65d5..02662a6 100644
--- a/lib/encoding/xml/foreignkey.go
+++ b/lib/encoding/xml/foreignkey.go
@@ -1,6 +1,7 @@
package xml
import (
+ "log/slog"
"strings"
"github.com/dbsteward/dbsteward/lib/ir"
@@ -17,6 +18,31 @@ type ForeignKey struct {
OnDelete string `xml:"onDelete,attr,omitempty"`
}
+func ForeignKeysFromIR(l *slog.Logger, ks []*ir.ForeignKey) ([]*ForeignKey, error) {
+ if len(ks) == 0 {
+ return nil, nil
+ }
+ var rv []*ForeignKey
+ for _, k := range ks {
+ if k != nil {
+ rv = append(
+ rv,
+ &ForeignKey{
+ Columns: k.Columns,
+ ForeignSchema: k.ForeignSchema,
+ ForeignTable: k.ForeignTable,
+ ForeignColumns: k.ForeignColumns,
+ ConstraintName: k.ConstraintName,
+ IndexName: k.IndexName,
+ OnUpdate: string(k.OnUpdate),
+ OnDelete: string(k.OnDelete),
+ },
+ )
+ }
+ }
+ return rv, nil
+}
+
func (fk *ForeignKey) ToIR() (*ir.ForeignKey, error) {
rv := ir.ForeignKey{
Columns: fk.Columns,
diff --git a/lib/encoding/xml/function.go b/lib/encoding/xml/function.go
index 84de4f0..8404751 100644
--- a/lib/encoding/xml/function.go
+++ b/lib/encoding/xml/function.go
@@ -2,6 +2,7 @@ package xml
import (
"fmt"
+ "log/slog"
"github.com/dbsteward/dbsteward/lib/ir"
)
@@ -20,12 +21,62 @@ type Function struct {
Grants []*Grant `xml:"grant"`
}
+func FunctionsFromIR(l *slog.Logger, funcs []*ir.Function) ([]*Function, error) {
+ if len(funcs) == 0 {
+ return nil, nil
+ }
+ var rv []*Function
+ for _, f := range funcs {
+ if f != nil {
+ ll := l.With(slog.String("function", f.Name))
+ nf := Function{
+ Name: f.Name,
+ Owner: f.Owner,
+ Description: f.Description,
+ Returns: f.Returns,
+ CachePolicy: f.CachePolicy,
+ ForceRedefine: f.ForceRedefine,
+ SecurityDefiner: f.SecurityDefiner,
+ Parameters: FunctionParametersFromIR(ll, f.Parameters),
+ Definitions: FunctionDefitionsFromIR(ll, f.Definitions),
+ }
+ var err error
+ nf.Grants, err = GrantsFromIR(ll, f.Grants)
+ if err != nil {
+ return nil, err
+ }
+ rv = append(rv, &nf)
+ }
+ }
+ return rv, nil
+}
+
type FunctionParameter struct {
Name string `xml:"name,attr,omitempty"`
Type string `xml:"type,attr"`
Direction string `xml:"direction,attr,omitempty"`
}
+func FunctionParametersFromIR(l *slog.Logger, params []*ir.FunctionParameter) []*FunctionParameter {
+ if len(params) == 0 {
+ return nil
+ }
+ var rv []*FunctionParameter
+ for _, param := range params {
+ if param != nil {
+ rv = append(
+ rv,
+ &FunctionParameter{
+ Name: param.Name,
+ Type: param.Type,
+ Direction: string(param.Direction),
+ },
+ )
+ }
+ }
+ return rv
+}
+
func (fp *FunctionParameter) ToIR() (*ir.FunctionParameter, error) {
if fp == nil {
return nil, nil
@@ -48,6 +99,26 @@ type FunctionDefinition struct {
Text string `xml:",cdata"`
}
+func FunctionDefitionsFromIR(l *slog.Logger, defs []*ir.FunctionDefinition) []*FunctionDefinition {
+ if len(defs) == 0 {
+ return nil
+ }
+ var rv []*FunctionDefinition
+ for _, def := range defs {
+ if def != nil {
+ rv = append(
+ rv,
+ &FunctionDefinition{
+ SqlFormat: string(def.SqlFormat),
+ Language: def.Language,
+ Text: def.Text,
+ },
+ )
+ }
+ }
+ return rv
+}
+
func (fd *FunctionDefinition) ToIR() (*ir.FunctionDefinition, error) {
if fd == nil {
return nil, nil
diff --git a/lib/encoding/xml/grant.go b/lib/encoding/xml/grant.go
index 2472eda..45d8f16 100644
--- a/lib/encoding/xml/grant.go
+++ b/lib/encoding/xml/grant.go
@@ -1,6 +1,10 @@
package xml
-import "github.com/dbsteward/dbsteward/lib/ir"
+import (
+ "log/slog"
+
+ "github.com/dbsteward/dbsteward/lib/ir"
+)
type Grant struct {
Roles DelimitedList `xml:"role,attr,omitempty"`
@@ -8,6 +12,26 @@ type Grant struct {
With string `xml:"with,attr,omitempty"`
}
+func GrantsFromIR(l *slog.Logger, gs []*ir.Grant) ([]*Grant, error) {
+ if len(gs) == 0 {
+ return nil, nil
+ }
+ var rv []*Grant
+ for _, g := range gs {
+ if g != nil {
+ rv = append(
+ rv,
+ &Grant{
+ Roles: g.Roles,
+ Permissions: g.Permissions,
+ With: g.With,
+ },
+ )
+ }
+ }
+ return rv, nil
+}
+
func (g *Grant) ToIR() (*ir.Grant, error) {
rv := ir.Grant{
Roles: g.Roles,
diff --git a/lib/encoding/xml/index.go b/lib/encoding/xml/index.go
index ee30ecc..0f10eda 100644
--- a/lib/encoding/xml/index.go
+++ b/lib/encoding/xml/index.go
@@ -2,6 +2,7 @@ package xml
import (
"fmt"
+ "log/slog"
"strings"
"github.com/dbsteward/dbsteward/lib/ir"
@@ -23,6 +24,72 @@ type IndexDim struct {
Value string `xml:",chardata"`
}
+func IndexesFromIR(l *slog.Logger, idxs []*ir.Index) ([]*Index, error) {
+ if len(idxs) == 0 {
+ return nil, nil
+ }
+ var rv []*Index
+ for _, idx := range idxs {
+ if idx != nil {
+ rv = append(rv, IndexFromIR(l, idx))
+ }
+ }
+ return rv, nil
+}
+
+func IndexFromIR(l *slog.Logger, idx *ir.Index) *Index {
+ l = l.With(slog.String("index", idx.Name))
+ l.Debug("converting index")
+ defer l.Debug("done converting index")
+ return &Index{
+ Name: idx.Name,
+ Using: string(idx.Using),
+ Unique: idx.Unique,
+ Concurrently: idx.Concurrently,
+ Dimensions: IndexDimensionsFromIR(l, idx.Dimensions),
+ Conditions: IndexConditionsFromIR(l, idx.Conditions),
+ }
+}
+
+func IndexDimensionsFromIR(l *slog.Logger, dims []*ir.IndexDim) []*IndexDim {
+ if len(dims) == 0 {
+ return nil
+ }
+ var rv []*IndexDim
+ for _, dim := range dims {
+ if dim != nil {
+ rv = append(
+ rv,
+ &IndexDim{
+ Name: dim.Name,
+ Sql: dim.Sql,
+ Value: dim.Value,
+ },
+ )
+ }
+ }
+ return rv
+}
+
+func IndexConditionsFromIR(l *slog.Logger, conds []*ir.IndexCond) []*IndexCond {
+ if len(conds) == 0 {
+ return nil
+ }
+ var rv []*IndexCond
+ for _, cond := range conds {
+ if cond != nil {
+ rv = append(
+ rv,
+ &IndexCond{
+ SqlFormat: string(cond.SqlFormat),
+ Condition: cond.Condition,
+ },
+ )
+ }
+ }
+ return rv
+}
+
func (id *IndexDim) ToIR() (*ir.IndexDim, error) {
return &ir.IndexDim{
Name: id.Name,
@@ -93,66 +160,66 @@ func newIndexType(s string) (ir.IndexType, error) {
return "", fmt.Errorf("invalid index type '%s'", s)
}
-func (self *Index) AddDimensionNamed(name, value string) {
+func (idx *Index) AddDimensionNamed(name, value string) {
// TODO(feat) sanity check
- self.Dimensions = append(self.Dimensions, &IndexDim{
+ idx.Dimensions = append(idx.Dimensions, &IndexDim{
Name: name,
Value: value,
})
}
-func (self *Index) AddDimension(value string) {
- self.AddDimensionNamed(
- fmt.Sprintf("%s_%d", self.Name, len(self.Dimensions)+1),
+func (idx *Index) AddDimension(value string) {
+ idx.AddDimensionNamed(
+ fmt.Sprintf("%s_%d", idx.Name, len(idx.Dimensions)+1),
value,
)
}
-func (self *Index) TryGetCondition(sqlFormat string) util.Opt[*IndexCond] {
+func (idx *Index) TryGetCondition(sqlFormat string) util.Opt[*IndexCond] {
// TODO(go,core) fallback to returning empty sqlformat condition if it exists
- return util.Find(self.Conditions, func(c *IndexCond) bool {
+ return util.Find(idx.Conditions, func(c *IndexCond) bool {
return strings.EqualFold(c.SqlFormat, sqlFormat)
})
}
-func (self *Index) IdentityMatches(other *Index) bool {
+func (idx *Index) IdentityMatches(other *Index) bool {
if other == nil {
return false
}
- return strings.EqualFold(self.Name, other.Name)
+ return strings.EqualFold(idx.Name, other.Name)
}
-func (self *Index) Equals(other *Index, sqlFormat string) bool {
- if self == nil || other == nil {
+func (idx *Index) Equals(other *Index, sqlFormat string) bool {
+ if idx == nil || other == nil {
// nil != nil in this case
return false
}
- if !strings.EqualFold(self.Name, other.Name) {
+ if !strings.EqualFold(idx.Name, other.Name) {
return false
}
- if self.Unique != other.Unique {
+ if idx.Unique != other.Unique {
return false
}
- if self.Concurrently != other.Concurrently {
+ if idx.Concurrently != other.Concurrently {
return false
}
- if !strings.EqualFold(self.Using, other.Using) {
+ if !strings.EqualFold(idx.Using, other.Using) {
return false
}
- if len(self.Dimensions) != len(other.Dimensions) {
+ if len(idx.Dimensions) != len(other.Dimensions) {
return false
}
// dimension order matters
- for i, dim := range self.Dimensions {
+ for i, dim := range idx.Dimensions {
if !dim.Equals(other.Dimensions[i]) {
return false
}
}
// if any conditions are defined, there must be a condition for the requested sqlFormat, and the two must be textually equal
- if len(self.Conditions) > 0 || len(other.Conditions) > 0 {
- if self.TryGetCondition(sqlFormat).Equals(other.TryGetCondition(sqlFormat)) {
+ if len(idx.Conditions) > 0 || len(other.Conditions) > 0 {
+ if idx.TryGetCondition(sqlFormat).Equals(other.TryGetCondition(sqlFormat)) {
return false
}
}
@@ -160,28 +227,28 @@ func (self *Index) Equals(other *Index, sqlFormat string) bool {
return true
}
-func (self *Index) Merge(overlay *Index) {
+func (idx *Index) Merge(overlay *Index) {
if overlay == nil {
return
}
- self.Using = overlay.Using
- self.Unique = overlay.Unique
- self.Dimensions = overlay.Dimensions
+ idx.Using = overlay.Using
+ idx.Unique = overlay.Unique
+ idx.Dimensions = overlay.Dimensions
}
-func (self *IndexDim) Equals(other *IndexDim) bool {
- if self == nil || other == nil {
+func (idxd *IndexDim) Equals(other *IndexDim) bool {
+ if idxd == nil || other == nil {
return false
}
// name does _not_ matter for equality - it's a dbsteward concept
- return self.Value == other.Value
+ return idxd.Value == other.Value
}
-func (self *IndexCond) Equals(other *IndexCond) bool {
- if self == nil || other == nil {
+func (idxc *IndexCond) Equals(other *IndexCond) bool {
+ if idxc == nil || other == nil {
return false
}
- return strings.EqualFold(self.SqlFormat, other.SqlFormat) &&
- strings.TrimSpace(self.Condition) == strings.TrimSpace(other.Condition)
+ return strings.EqualFold(idxc.SqlFormat, other.SqlFormat) &&
+ strings.TrimSpace(idxc.Condition) == strings.TrimSpace(other.Condition)
}
diff --git a/lib/encoding/xml/oneighty_test.go b/lib/encoding/xml/oneighty_test.go
new file mode 100644
index 0000000..5bf4395
--- /dev/null
+++ b/lib/encoding/xml/oneighty_test.go
@@ -0,0 +1,32 @@
+package xml
+
+import (
+ "log/slog"
+ "os"
+ "testing"
+
+ "github.com/dbsteward/dbsteward/lib/ir"
+ "github.com/stretchr/testify/assert"
+)
+
+// one eighty test uses an IR to build an XML document
+// then extracts it and ensures it results in the
+// same IR
+
+// TODO list: Things that don't work yet but are feature improvements
+
+func TestOneEighty(t *testing.T) {
+ const role = "postgres"
+ irSchema := ir.FullFeatureSchema(role)
+ handler := slog.NewTextHandler(os.Stderr, &slog.HandlerOptions{Level: slog.LevelDebug})
+ logger := slog.New(handler)
+ xmlDoc, err := FromIR(logger, &irSchema)
+ if err != nil {
+ t.Fatal(err)
+ }
+ reflectedSchema, err := xmlDoc.ToIR()
+ if err != nil {
+ t.Fatal(err)
+ }
+ assert.Equal(t, ir.FullFeatureSchema(role), *reflectedSchema, "reflection does not match original")
+}
diff --git a/lib/encoding/xml/partition.go b/lib/encoding/xml/partition.go
index 714ccb4..3eab6c8 100644
--- a/lib/encoding/xml/partition.go
+++ b/lib/encoding/xml/partition.go
@@ -2,6 +2,7 @@ package xml
import (
"fmt"
+ "log/slog"
"github.com/dbsteward/dbsteward/lib/ir"
)
@@ -13,6 +14,39 @@ type TablePartition struct {
Segments []*TablePartitionSegment `xml:"tablePartitionSegment"`
}
+func TablePartitionFromIR(l *slog.Logger, p *ir.TablePartition) (*TablePartition, error) {
+ if p == nil {
+ return nil, nil
+ }
+ rv := TablePartition{
+ Type: string(p.Type),
+ SqlFormat: string(p.SqlFormat),
+ }
+ for _, opt := range p.Options {
+ if opt != nil {
+ rv.Options = append(
+ rv.Options,
+ &TablePartitionOption{
+ Name: opt.Name,
+ Value: opt.Value,
+ },
+ )
+ }
+ }
+ for _, seg := range p.Segments {
+ if seg != nil {
+ rv.Segments = append(
+ rv.Segments,
+ &TablePartitionSegment{
+ Name: seg.Name,
+ Value: seg.Value,
+ },
+ )
+ }
+ }
+ return &rv, nil
+}
+
func (tp *TablePartition) ToIR() (*ir.TablePartition, error) {
if tp == nil {
return nil, nil
diff --git a/lib/encoding/xml/schema.go b/lib/encoding/xml/schema.go
index 6c1d6ed..0d53021 100644
--- a/lib/encoding/xml/schema.go
+++ b/lib/encoding/xml/schema.go
@@ -1,6 +1,8 @@
package xml
import (
+ "log/slog"
+
"github.com/dbsteward/dbsteward/lib/ir"
"github.com/dbsteward/dbsteward/lib/util"
"github.com/pkg/errors"
@@ -20,41 +22,98 @@ type Schema struct {
Views []*View `xml:"view"`
}
-func (self *Schema) ToIR() (*ir.Schema, error) {
- tables, err := util.MapErr(self.Tables, (*Table).ToIR)
+func SchemasFromIR(l *slog.Logger, in []*ir.Schema) ([]*Schema, error) {
+ l.Debug("start converting schemas")
+ defer l.Debug("done converting schemas")
+ if len(in) == 0 {
+ return nil, nil
+ }
+ var rv []*Schema
+ for _, irsch := range in {
+ if irsch != nil {
+ nsch, err := SchemaFromIR(l, irsch)
+ if err != nil {
+ return nil, err
+ }
+ rv = append(rv, nsch)
+ }
+ }
+ return rv, nil
+}
+
+func SchemaFromIR(l *slog.Logger, in *ir.Schema) (*Schema, error) {
+ l = l.With(slog.String("schema", in.Name))
+ l.Debug("start converting schema")
+ defer l.Debug("done converting schema")
+ rv := Schema{
+ Name: in.Name,
+ Description: in.Description,
+ Owner: in.Owner,
+ SlonySetId: in.SlonySetId.Ptr(),
+ }
+ var err error
+ rv.Tables, err = TablesFromIR(l, in.Tables)
+ if err != nil {
+ return nil, err
+ }
+ rv.Grants, err = GrantsFromIR(l, in.Grants)
+ if err != nil {
+ return nil, err
+ }
+ rv.Types, err = TypesFromIR(l, in.Types)
+ if err != nil {
+ return nil, err
+ }
+ rv.Sequences, err = SequencesFromIR(l, in.Sequences)
+ if err != nil {
+ return nil, err
+ }
+ rv.Functions, err = FunctionsFromIR(l, in.Functions)
+ if err != nil {
+ return nil, err
+ }
+ rv.Views, err = ViewsFromIR(l, in.Views)
+ if err != nil {
+ return nil, err
+ }
+ return &rv, nil
+}
+
+func (sch *Schema) ToIR() (*ir.Schema, error) {
+ tables, err := util.MapErr(sch.Tables, (*Table).ToIR)
if err != nil {
return nil, errors.Wrap(err, "could not process schema table tags")
}
- grants, err := util.MapErr(self.Grants, (*Grant).ToIR)
+ grants, err := util.MapErr(sch.Grants, (*Grant).ToIR)
if err != nil {
return nil, errors.Wrap(err, "could not process schema grant tags")
}
- types, err := util.MapErr(self.Types, (*DataType).ToIR)
+ types, err := util.MapErr(sch.Types, (*DataType).ToIR)
if err != nil {
return nil, errors.Wrap(err, "could not process schema type tags")
}
- sequences, err := util.MapErr(self.Sequences, (*Sequence).ToIR)
+ sequences, err := util.MapErr(sch.Sequences, (*Sequence).ToIR)
if err != nil {
return nil, errors.Wrap(err, "could not process schema sequence tags")
}
- functions, err := util.MapErr(self.Functions, (*Function).ToIR)
+ functions, err := util.MapErr(sch.Functions, (*Function).ToIR)
if err != nil {
return nil, errors.Wrap(err, "could not process schema function tags")
}
- triggers, err := util.MapErr(self.Triggers, (*Trigger).ToIR)
+ triggers, err := util.MapErr(sch.Triggers, (*Trigger).ToIR)
if err != nil {
return nil, errors.Wrap(err, "could not process schema trigger tags")
}
- views, err := util.MapErr(self.Views, (*View).ToIR)
+ views, err := util.MapErr(sch.Views, (*View).ToIR)
if err != nil {
return nil, errors.Wrap(err, "could not process schema view tags")
}
return &ir.Schema{
- Name: self.Name,
- Description: self.Description,
- Owner: self.Owner,
- SlonySetId: util.SomePtr(self.SlonySetId),
+ Name: sch.Name,
+ Description: sch.Description,
+ Owner: sch.Owner,
+ SlonySetId: util.SomePtr(sch.SlonySetId),
Tables: tables,
Grants: grants,
Types: types,
diff --git a/lib/encoding/xml/sequence.go b/lib/encoding/xml/sequence.go
index ed8ac29..7cc3b3a 100644
--- a/lib/encoding/xml/sequence.go
+++ b/lib/encoding/xml/sequence.go
@@ -2,27 +2,63 @@ package xml
import (
"fmt"
+ "log/slog"
"github.com/dbsteward/dbsteward/lib/ir"
"github.com/dbsteward/dbsteward/lib/util"
)
type Sequence struct {
- Name string `xml:"name,attr"`
- Owner string `xml:"owner,attr,omitempty"`
- Description string `xml:"description,attr,omitempty"`
- Cache *int `xml:"cache,attr,omitempty"`
- Start *int `xml:"start,attr,omitempty"`
- Min *int `xml:"min,attr,omitempty"`
- Max *int `xml:"max,attr,omitempty"`
- Increment *int `xml:"inc,attr,omitempty"`
- Cycle bool `xml:"cycle,attr,omitempty"`
- OwnedByColumn string `xml:"ownedBy,attr,omitempty"`
+ Name string `xml:"name,attr"`
+ Owner string `xml:"owner,attr,omitempty"`
+ Description string `xml:"description,attr,omitempty"`
+ Cache *int `xml:"cache,attr,omitempty"`
+ Start *int `xml:"start,attr,omitempty"`
+ Min *int `xml:"min,attr,omitempty"`
+ Max *int `xml:"max,attr,omitempty"`
+ Increment *int `xml:"inc,attr,omitempty"`
+ Cycle bool `xml:"cycle,attr,omitempty"`
+ OwnedBySchema string
+ OwnedByTable string
+ OwnedByColumn string
SlonyId int `xml:"slonyId,attr,omitempty"`
SlonySetId *int `xml:"slonySetId,attr,omitempty"`
Grants []*Grant `xml:"grant"`
}
+func SequencesFromIR(l *slog.Logger, seqs []*ir.Sequence) ([]*Sequence, error) {
+ if len(seqs) == 0 {
+ return nil, nil
+ }
+ var rv []*Sequence
+ for _, seq := range seqs {
+ if seq != nil {
+ ll := l.With(slog.String("sequence", seq.Name))
+ ns := Sequence{
+ Name: seq.Name,
+ Owner: seq.Owner,
+ Description: seq.Description,
+ Cache: seq.Cache.Ptr(),
+ Start: seq.Start.Ptr(),
+ Min: seq.Min.Ptr(),
+ Max: seq.Max.Ptr(),
+ Increment: seq.Increment.Ptr(),
+ Cycle: seq.Cycle,
+ OwnedBySchema: seq.OwnedBySchema,
+ OwnedByTable: seq.OwnedByTable,
+ OwnedByColumn: seq.OwnedByColumn,
+ }
+ var err error
+ ns.Grants, err = GrantsFromIR(ll, seq.Grants)
+ if err != nil {
+ return nil, err
+ }
+ rv = append(rv, &ns)
+ }
+ }
+ return rv, nil
+}
+
func (s *Sequence) ToIR() (*ir.Sequence, error) {
rv := ir.Sequence{
Name: s.Name,
@@ -34,6 +70,8 @@ func (s *Sequence) ToIR() (*ir.Sequence, error) {
Max: util.SomePtr(s.Max),
Increment: util.SomePtr(s.Increment),
Cycle: s.Cycle,
+ OwnedBySchema: s.OwnedBySchema,
+ OwnedByTable: s.OwnedByTable,
OwnedByColumn: s.OwnedByColumn,
}
diff --git a/lib/encoding/xml/table.go b/lib/encoding/xml/table.go
index 7347b86..0c28aea 100644
--- a/lib/encoding/xml/table.go
+++ b/lib/encoding/xml/table.go
@@ -2,6 +2,7 @@ package xml
import (
"fmt"
+ "log/slog"
"github.com/dbsteward/dbsteward/lib/ir"
)
@@ -35,6 +36,99 @@ type TableOption struct {
Value string `xml:"value"`
}
+func TablesFromIR(l *slog.Logger, irts []*ir.Table) ([]*Table, error) {
+ if len(irts) == 0 {
+ return nil, nil
+ }
+ l.Debug("start converting tables")
+ defer l.Debug("done converting tables")
+ if len(irts) == 0 {
+ return nil, nil
+ }
+ var rv []*Table
+ for _, irt := range irts {
+ if irt != nil {
+ t, err := TableFromIR(l, irt)
+ if err != nil {
+ return nil, err
+ }
+ rv = append(rv, t)
+ }
+ }
+ return rv, nil
+}
+
+func TableFromIR(l *slog.Logger, irt *ir.Table) (*Table, error) {
+ l = l.With(slog.String("table", irt.Name))
+ l.Debug("start converting table")
+ defer l.Debug("done converting table")
+ t := Table{
+ Name: irt.Name,
+ Description: irt.Description,
+ Owner: irt.Owner,
+ PrimaryKey: irt.PrimaryKey,
+ PrimaryKeyName: irt.PrimaryKeyName,
+ ClusterIndex: irt.ClusterIndex,
+ InheritsTable: irt.InheritsTable,
+ InheritsSchema: irt.InheritsSchema,
+ OldTableName: irt.OldTableName,
+ OldSchemaName: irt.OldSchemaName,
+ // SlonySetId: Does not appear in the IR
+ // SlonyID: Does not appear in the IR
+ TableOptions: TableOptionsFromIR(l, irt.TableOptions),
+ }
+ var err error
+ t.Partitioning, err = TablePartitionFromIR(l, irt.Partitioning)
+ if err != nil {
+ return nil, err
+ }
+ t.Columns, err = ColumnsFromIR(l, irt.Columns)
+ if err != nil {
+ return nil, err
+ }
+ t.ForeignKeys, err = ForeignKeysFromIR(l, irt.ForeignKeys)
+ if err != nil {
+ return nil, err
+ }
+ t.Indexes, err = IndexesFromIR(l, irt.Indexes)
+ if err != nil {
+ return nil, err
+ }
+ t.Constraints, err = ConstraintsFromIR(l, irt.Constraints)
+ if err != nil {
+ return nil, err
+ }
+ t.Grants, err = GrantsFromIR(l, irt.Grants)
+ if err != nil {
+ return nil, err
+ }
+ t.Rows, err = DataRowsFromIR(l, irt.Rows)
+ if err != nil {
+ return nil, err
+ }
+ return &t, nil
+}
+
+func TableOptionsFromIR(l *slog.Logger, irops []*ir.TableOption) []*TableOption {
+ if len(irops) == 0 {
+ return nil
+ }
+ var rv []*TableOption
+ for _, irop := range irops {
+ if irop != nil {
+ rv = append(
+ rv,
+ &TableOption{
+ SqlFormat: string(irop.SqlFormat),
+ Name: irop.Name,
+ Value: irop.Value,
+ },
+ )
+ }
+ }
+ return rv
+}
+
func (topt TableOption) ToIR() (*ir.TableOption, error) {
sqlFormat, err := ir.NewSqlFormat(topt.SqlFormat)
if err != nil {
diff --git a/lib/encoding/xml/view.go b/lib/encoding/xml/view.go
index 09a1867..781e1b3 100644
--- a/lib/encoding/xml/view.go
+++ b/lib/encoding/xml/view.go
@@ -2,6 +2,7 @@ package xml
import (
"fmt"
+ "log/slog"
"github.com/dbsteward/dbsteward/lib/ir"
)
@@ -16,11 +17,56 @@ type View struct {
Queries []*ViewQuery `xml:"viewQuery"`
}
+func ViewsFromIR(l *slog.Logger, views []*ir.View) ([]*View, error) {
+ if len(views) == 0 {
+ return nil, nil
+ }
+ var rv []*View
+ for _, view := range views {
+ if view != nil {
+ ll := l.With(slog.String("view", view.Name))
+ nv := View{
+ Name: view.Name,
+ Description: view.Description,
+ Owner: view.Owner,
+ DependsOnViews: view.DependsOnViews,
+ Queries: ViewQueriesFromIR(ll, view.Queries),
+ }
+ var err error
+ nv.Grants, err = GrantsFromIR(ll, view.Grants)
+ if err != nil {
+ return nil, err
+ }
+ rv = append(rv, &nv)
+ }
+ }
+ return rv, nil
+}
+
type ViewQuery struct {
SqlFormat string `xml:"sqlFormat,attr,omitempty"`
Text string `xml:",cdata"`
}
+func ViewQueriesFromIR(l *slog.Logger, queries []*ir.ViewQuery) []*ViewQuery {
+ if len(queries) == 0 {
+ return nil
+ }
+ var rv []*ViewQuery
+ for _, query := range queries {
+ if query != nil {
+ rv = append(
+ rv,
+ &ViewQuery{
+ SqlFormat: string(query.SqlFormat),
+ Text: query.Text,
+ },
+ )
+ }
+ }
+ return rv
+}
+
func (vq *ViewQuery) ToIR() (*ir.ViewQuery, error) {
if vq == nil {
return nil, nil
diff --git a/lib/encoding/xml/xml.go b/lib/encoding/xml/xml.go
index f2aa398..6d16f95 100644
--- a/lib/encoding/xml/xml.go
+++ b/lib/encoding/xml/xml.go
@@ -13,6 +13,7 @@ package xml
import (
"encoding/xml"
"io"
+ "log/slog"
"github.com/dbsteward/dbsteward/lib/ir"
"github.com/pkg/errors"
@@ -31,7 +32,7 @@ func ReadDoc(r io.Reader) (*Document, error) {
// WriteDoc writes the given `Document` to the given `io.Writer`
// in XML that conforms to the DTD at the project root
-func WriteDoc(w io.Writer, doc *Document) error {
+func WriteDoc(l *slog.Logger, w io.Writer, doc *Document) error {
// TODO(go,nth) get rid of empty closing tags like =>
// Go doesn't natively support this (yet?), and google is being google about it
// https://github.com/golang/go/issues/21399
@@ -50,11 +51,10 @@ func ReadDef(r io.Reader) (*ir.Definition, error) {
return doc.ToIR()
}
-func WriteDef(w io.Writer, def *ir.Definition) error {
- doc := &Document{}
- err := doc.FromModel(def)
+func WriteDef(l *slog.Logger, w io.Writer, def *ir.Definition) error {
+ doc, err := FromIR(l, def)
if err != nil {
return err
}
- return WriteDoc(w, doc)
+ return WriteDoc(l, w, doc)
}
diff --git a/lib/ir/foreignkey.go b/lib/ir/foreignkey.go
index 206cd7b..c1dcfcc 100644
--- a/lib/ir/foreignkey.go
+++ b/lib/ir/foreignkey.go
@@ -17,7 +17,7 @@ const (
func NewForeignKeyAction(s string) (ForeignKeyAction, error) {
if s == "" {
- return ForeignKeyActionNoAction, nil
+ return "", nil
}
fka := ForeignKeyAction(s)
if fka.Equals(ForeignKeyActionNoAction) {
@@ -53,31 +53,31 @@ type ForeignKey struct {
OnDelete ForeignKeyAction
}
-func (self *ForeignKey) GetReferencedKey() KeyNames {
- cols := self.ForeignColumns
+func (fk *ForeignKey) GetReferencedKey() KeyNames {
+ cols := fk.ForeignColumns
if len(cols) == 0 {
- cols = self.Columns
+ cols = fk.Columns
}
return KeyNames{
- Schema: self.ForeignSchema,
- Table: self.ForeignTable,
+ Schema: fk.ForeignSchema,
+ Table: fk.ForeignTable,
Columns: cols,
- KeyName: self.ConstraintName,
+ KeyName: fk.ConstraintName,
}
}
-func (self *ForeignKey) IdentityMatches(other *ForeignKey) bool {
- if self == nil || other == nil {
+func (fk *ForeignKey) IdentityMatches(other *ForeignKey) bool {
+ if fk == nil || other == nil {
return false
}
// TODO(go,core) validate this constraint/index name matching behavior
// TODO(feat) case sensitivity
- return strings.EqualFold(self.ConstraintName, other.ConstraintName)
+ return strings.EqualFold(fk.ConstraintName, other.ConstraintName)
}
-func (self *ForeignKey) Validate(doc *Definition, schema *Schema, table *Table) []error {
+func (fk *ForeignKey) Validate(doc *Definition, schema *Schema, table *Table) []error {
out := []error{}
- if self.ConstraintName == "" {
+ if fk.ConstraintName == "" {
out = append(out, fmt.Errorf("foreign key in table %s.%s must have a constraint name", schema.Name, table.Name))
}
// TODO(go,3) validate reference, remove other codepaths
diff --git a/lib/util/lists.go b/lib/util/lists.go
index c47f7e9..136e3d8 100644
--- a/lib/util/lists.go
+++ b/lib/util/lists.go
@@ -127,6 +127,9 @@ func Map[S ~[]T, T, U any](slice S, f func(T) U) []U {
}
func MapErr[S ~[]T, T, U any](slice S, f func(T) (U, error)) ([]U, error) {
+ if len(slice) == 0 {
+ return nil, nil
+ }
out := make([]U, len(slice))
for i, t := range slice {
u, err := f(t)