diff --git a/database/drivers/mysql/parse.go b/database/drivers/mysql/parse.go index 1e59c13..7d5e699 100644 --- a/database/drivers/mysql/parse.go +++ b/database/drivers/mysql/parse.go @@ -87,6 +87,36 @@ func parse(log *log.Logger, conn string, schemaNames []string, filterTables func } } + foreignKeys, err := queryForeignKeys(log, db, schemaNames) + if err != nil { + return nil, err + } + for _, fk := range foreignKeys { + if !filterTables(fk.SchemaName, fk.TableName) { + log.Printf("skipping constraint %q because it is for filtered-out table %v.%v", fk.Name, fk.SchemaName, fk.TableName) + continue + } + + schema, ok := schemas[fk.SchemaName] + if !ok { + log.Printf("Should be impossible: constraint %q references unknown schema %q", fk.Name, fk.SchemaName) + continue + } + table, ok := schema[fk.TableName] + if !ok { + log.Printf("Should be impossible: constraint %q references unknown table %q in schema %q", fk.Name, fk.TableName, fk.SchemaName) + continue + } + + for _, col := range table { + if fk.ColumnName != col.Name { + continue + } + col.IsForeignKey = true + col.ForeignKey = fk + } + } + res := &database.Info{Schemas: make([]*database.Schema, 0, len(schemas))} for _, schema := range schemaNames { tables := schemas[schema] @@ -151,3 +181,39 @@ func toDBColumn(c *columns.Row, log *log.Logger) (*database.Column, *database.En return col, enum, nil } + +func queryForeignKeys(log *log.Logger, db *sql.DB, schemas []string) ([]*database.ForeignKey, error) { + // TODO: make this work with Gnorm generated types + const q = `SELECT lkc.TABLE_SCHEMA, lkc.TABLE_NAME, lkc.COLUMN_NAME, lkc.CONSTRAINT_NAME, lkc.POSITION_IN_UNIQUE_CONSTRAINT, lkc.REFERENCED_TABLE_NAME, lkc.REFERENCED_COLUMN_NAME + FROM information_schema.REFERENTIAL_CONSTRAINTS as rc + LEFT JOIN information_schema.KEY_COLUMN_USAGE as lkc + ON lkc.CONSTRAINT_SCHEMA = rc.CONSTRAINT_SCHEMA + AND lkc.CONSTRAINT_NAME = rc.CONSTRAINT_NAME + WHERE rc.CONSTRAINT_SCHEMA IN (%s)` + spots := make([]string, len(schemas)) + vals := make([]interface{}, len(schemas)) + for x := range schemas { + spots[x] = "?" + vals[x] = schemas[x] + } + query := fmt.Sprintf(q, strings.Join(spots, ", ")) + rows, err := db.Query(query, vals...) + if err != nil { + return nil, errors.WithMessage(err, "error querying foreign keys") + } + defer rows.Close() + var ret []*database.ForeignKey + + for rows.Next() { + fk := &database.ForeignKey{} + if err := rows.Scan(&fk.SchemaName, &fk.TableName, &fk.ColumnName, &fk.Name, &fk.UniqueConstraintPosition, &fk.ForeignTableName, &fk.ForeignColumnName); err != nil { + return nil, errors.WithMessage(err, "error scanning foreign key constraint") + } + ret = append(ret, fk) + } + if rows.Err() != nil { + return nil, errors.WithMessage(rows.Err(), "error reading foreign keys") + } + + return ret, nil +} diff --git a/database/drivers/postgres/parse.go b/database/drivers/postgres/parse.go index 553262f..249ffdd 100644 --- a/database/drivers/postgres/parse.go +++ b/database/drivers/postgres/parse.go @@ -120,6 +120,36 @@ func parse(log *log.Logger, conn string, schemaNames []string, filterTables func } } + foreignKeys, err := queryForeignKeys(log, db, schemaNames) + if err != nil { + return nil, err + } + for _, fk := range foreignKeys { + if !filterTables(fk.SchemaName, fk.TableName) { + log.Printf("skipping constraint %q because it is for filtered-out table %v.%v", fk.Name, fk.SchemaName, fk.TableName) + continue + } + + schema, ok := schemas[fk.SchemaName] + if !ok { + log.Printf("Should be impossible: constraint %q references unknown schema %q", fk.Name, fk.SchemaName) + continue + } + table, ok := schema[fk.TableName] + if !ok { + log.Printf("Should be impossible: constraint %q references unknown table %q in schema %q", fk.Name, fk.TableName, fk.SchemaName) + continue + } + + for _, col := range table { + if fk.ColumnName != col.Name { + continue + } + col.IsForeignKey = true + col.ForeignKey = fk + } + } + enums, err := queryEnums(log, db, schemaNames) if err != nil { return nil, err @@ -186,10 +216,10 @@ func queryPrimaryKeys(log *log.Logger, db *sql.DB, schemas []string) ([]*databas } query := fmt.Sprintf(q, strings.Join(spots, ", ")) rows, err := db.Query(query, vals...) - defer rows.Close() if err != nil { return nil, errors.WithMessage(err, "error querying keys") } + defer rows.Close() var ret []*database.PrimaryKey for rows.Next() { @@ -202,6 +232,45 @@ func queryPrimaryKeys(log *log.Logger, db *sql.DB, schemas []string) ([]*databas return ret, nil } +func queryForeignKeys(log *log.Logger, db *sql.DB, schemas []string) ([]*database.ForeignKey, error) { + // TODO: make this work with Gnorm generated types + const q = `SELECT rc.constraint_schema, lkc.table_name, lkc.column_name, lkc.constraint_name, lkc.position_in_unique_constraint, fkc.table_name, fkc.column_name + FROM information_schema.referential_constraints rc + LEFT JOIN information_schema.key_column_usage lkc + ON lkc.table_schema = rc.constraint_schema + AND lkc.constraint_name = rc.constraint_name + LEFT JOIN information_schema.key_column_usage fkc + ON fkc.table_schema = rc.constraint_schema + AND fkc.ordinal_position = lkc.position_in_unique_constraint + AND fkc.constraint_name = rc.unique_constraint_name + WHERE rc.constraint_schema IN (%s)` + spots := make([]string, len(schemas)) + vals := make([]interface{}, len(schemas)) + for x := range schemas { + spots[x] = fmt.Sprintf("$%v", x+1) + vals[x] = schemas[x] + } + query := fmt.Sprintf(q, strings.Join(spots, ", ")) + rows, err := db.Query(query, vals...) + if err != nil { + return nil, errors.WithMessage(err, "error querying foreign keys") + } + defer rows.Close() + var ret []*database.ForeignKey + + for rows.Next() { + fk := &database.ForeignKey{} + if err := rows.Scan(&fk.SchemaName, &fk.TableName, &fk.ColumnName, &fk.Name, &fk.UniqueConstraintPosition, &fk.ForeignTableName, &fk.ForeignColumnName); err != nil { + return nil, errors.WithMessage(err, "error scanning foreign key constraint") + } + ret = append(ret, fk) + } + if rows.Err() != nil { + return nil, errors.WithMessage(rows.Err(), "error reading foreign keys") + } + return ret, nil +} + func queryEnums(log *log.Logger, db *sql.DB, schemas []string) (map[string][]*database.Enum, error) { // TODO: make this work with Gnorm generated types const q = ` @@ -220,10 +289,10 @@ func queryEnums(log *log.Logger, db *sql.DB, schemas []string) (map[string][]*da } query := fmt.Sprintf(q, strings.Join(spots, ", ")) rows, err := db.Query(query, vals...) - defer rows.Close() if err != nil { return nil, errors.WithMessage(err, "error querying enum names") } + defer rows.Close() ret := map[string][]*database.Enum{} for rows.Next() { var name, schema string diff --git a/database/info.go b/database/info.go index 92ca821..13f5835 100644 --- a/database/info.go +++ b/database/info.go @@ -42,6 +42,17 @@ type PrimaryKey struct { Name string // the original name of the key constraint in the db } +// Foreign Key contains the definition of a database foreign key +type ForeignKey struct { + SchemaName string // the original name of the schema in the db + TableName string // the original name of the table in the db + ColumnName string // the original name of the column in the db + Name string // the original name of the foreign key constraint in the db + UniqueConstraintPosition int // the position of the unique constraint in the db + ForeignTableName string // the original name of the table in the db for the referenced table + ForeignColumnName string // the original name of the column in the db for the referenced column +} + // Column contains data about a column in a table. type Column struct { Name string // the original name of the column in the DB @@ -52,6 +63,8 @@ type Column struct { Nullable bool // true if the column is not NON NULL HasDefault bool // true if the column has a default IsPrimaryKey bool // true if the column is a primary key + IsForeignKey bool // true if the column is a foreign key + ForeignKey *ForeignKey // foreign key database definition Orig interface{} // the raw database column data } diff --git a/run/convert.go b/run/convert.go index 4c606a5..369c987 100644 --- a/run/convert.go +++ b/run/convert.go @@ -9,6 +9,8 @@ import ( "gnorm.org/gnorm/run/data" ) +type nameConverter func(s string) (string, error) + func makeData(log *log.Logger, info *database.Info, cfg *Config) (*data.DBData, error) { convert := func(s string) (string, error) { buf := &bytes.Buffer{} @@ -62,6 +64,8 @@ func makeData(log *log.Logger, info *database.Info, cfg *Config) (*data.DBData, DBName: t.Name, Schema: sch, ColumnsByName: make(map[string]*data.Column, len(t.Columns)), + FKByName: map[string]*data.ForeignKey{}, + FKRefsByName: map[string]*data.ForeignKey{}, } sch.Tables = append(sch.Tables, table) sch.TablesByName[table.DBName] = table @@ -71,15 +75,18 @@ func makeData(log *log.Logger, info *database.Info, cfg *Config) (*data.DBData, } for _, c := range t.Columns { col := &data.Column{ - DBName: c.Name, - DBType: c.Type, - IsArray: c.IsArray, - Length: c.Length, - UserDefined: c.UserDefined, - Nullable: c.Nullable, - HasDefault: c.HasDefault, - IsPrimaryKey: c.IsPrimaryKey, - Orig: c.Orig, + Table: table, + DBName: c.Name, + DBType: c.Type, + IsArray: c.IsArray, + Length: c.Length, + UserDefined: c.UserDefined, + Nullable: c.Nullable, + HasDefault: c.HasDefault, + IsPrimaryKey: c.IsPrimaryKey, + IsFK: c.IsForeignKey, + FKColumnRefsByName: map[string]*data.ForeignKeyColumn{}, + Orig: c.Orig, } table.Columns = append(table.Columns, col) table.ColumnsByName[col.DBName] = col @@ -102,12 +109,15 @@ func makeData(log *log.Logger, info *database.Info, cfg *Config) (*data.DBData, } table.PrimaryKeys = filterPrimaryKeyColumns(table.Columns) } + if err = mapSchemaForeignKeyReferences(s, sch, convert); err != nil { + return nil, err + } } return db, nil } -func filterPrimaryKeyColumns(columns []*data.Column) []*data.Column { - var pkColumns []*data.Column +func filterPrimaryKeyColumns(columns data.Columns) data.Columns { + var pkColumns data.Columns for _, column := range columns { if column.IsPrimaryKey { pkColumns = append(pkColumns, column) @@ -116,3 +126,95 @@ func filterPrimaryKeyColumns(columns []*data.Column) []*data.Column { return pkColumns } + +func mapSchemaForeignKeyReferences(isch *database.Schema, sch *data.Schema, convert nameConverter) error { + for _, t := range isch.Tables { + table, ok := sch.TablesByName[t.Name] + if !ok { + log.Printf("Unmapped table %v in %v", t.Name, isch.Name) + continue + } + + fkColumnsByFKNames := map[string]data.ForeignKeyColumns{} + + for _, c := range t.Columns { + column, ok := table.ColumnsByName[c.Name] + if !ok { + log.Printf("Unmapped column %v in %v.%v", c.Name, isch.Name, t.Name) + continue + } + + if column.IsFK { + refTable, ok := sch.TablesByName[c.ForeignKey.ForeignTableName] + if !ok { + log.Printf("Unmapped foreign table %v in %v", c.ForeignKey.ForeignTableName, isch.Name) + continue + } + refColumn, ok := refTable.ColumnsByName[c.ForeignKey.ForeignColumnName] + if !ok { + log.Printf("Unmapped foreign column %v in %v.%v", c.ForeignKey.ForeignColumnName, isch.Name, c.ForeignKey.ForeignTableName) + continue + } + + fkColumn := &data.ForeignKeyColumn{ + DBName: c.ForeignKey.Name, + ColumnDBName: column.DBName, + RefColumnDBName: refColumn.DBName, + Column: column, + RefColumn: refColumn, + } + column.FKColumn = fkColumn + + refColumn.HasFKRef = true + refColumn.FKColumnRefs = append(refColumn.FKColumnRefs, fkColumn) + refColumn.FKColumnRefsByName[fkColumn.DBName] = fkColumn + + if _, ok := fkColumnsByFKNames[fkColumn.DBName]; !ok { + fkColumnsByFKNames[fkColumn.DBName] = data.ForeignKeyColumns{fkColumn} + } else { + fkColumnsByFKNames[fkColumn.DBName] = append(fkColumnsByFKNames[fkColumn.DBName], fkColumn) + } + } + } + + for _, fkc := range fkColumnsByFKNames { + err := mapForeignTable(fkc, convert) + if err != nil { + return err + } + } + } + + return nil +} + +func mapForeignTable(fkc data.ForeignKeyColumns, convert nameConverter) error { + if len(fkc) == 0 { + return nil + } + + // All ForeignKeyColumns will point to same table/refTable and have the same name, use first one + table := fkc[0].Column.Table + refTable := fkc[0].RefColumn.Table + cName, err := convert(fkc[0].DBName) + if err != nil { + return errors.Wrap(err, "foreign key") + } + + fk := &data.ForeignKey{ + DBName: fkc[0].DBName, + Name: cName, + TableDBName: table.DBName, + RefTableDBName: refTable.DBName, + Table: table, + RefTable: refTable, + FKColumns: fkc, + } + + table.ForeignKeys = append(table.ForeignKeys, fk) + refTable.ForeignKeyRefs = append(table.ForeignKeyRefs, fk) + table.FKByName[fk.DBName] = fk + refTable.FKRefsByName[fk.DBName] = fk + + return nil +} diff --git a/run/data/data.go b/run/data/data.go index 7447101..675ccf7 100644 --- a/run/data/data.go +++ b/run/data/data.go @@ -48,12 +48,16 @@ type Schema struct { // Table is the data about a DB Table. type Table struct { - Name string // the converted name of the table - DBName string // the original name of the table in the DB - Schema *Schema `yaml:"-" json:"-"` // the schema this table is in - Columns Columns // Database columns - ColumnsByName map[string]*Column `yaml:"-" json:"-"` // dbname to column - PrimaryKeys Columns // Primary Key Columns + Name string // the converted name of the table + DBName string // the original name of the table in the DB + Schema *Schema `yaml:"-" json:"-"` // the schema this table is in + Columns Columns // Database columns + ColumnsByName map[string]*Column `yaml:"-" json:"-"` // dbname to column + PrimaryKeys Columns // Primary Key Columns + ForeignKeys ForeignKeys // Foreign Keys + ForeignKeyRefs ForeignKeys // Foreign Keys referencing this table + FKByName map[string]*ForeignKey `yaml:"-" json:"-"` // Foreign Keys by foreign key name + FKRefsByName map[string]*ForeignKey `yaml:"-" json:"-"` // Foreign Keys referencing this table by foreign key name } // Returns true if Table has one or more primary keys @@ -61,19 +65,55 @@ func (t *Table) HasPrimaryKey() bool { return len(t.PrimaryKeys) > 0 } +// Returns true if Table has one or more foreign keys +func (t *Table) HasForeignKeys() bool { + return len(t.ForeignKeys) > 0 +} + +// Returns true if one or more foreign keys reference Table +func (t *Table) HasForeignKeyRefs() bool { + return len(t.ForeignKeyRefs) > 0 +} + // Column is the data about a DB column of a table. type Column struct { - Name string // the converted name of the column - DBName string // the original name of the column in the DB - Type string // the converted name of the type - DBType string // the original type of the column in the DB - IsArray bool // true if the column type is an array - Length int // non-zero if the type has a length (e.g. varchar[16]) - UserDefined bool // true if the type is user-defined - Nullable bool // true if the column is not NON NULL - HasDefault bool // true if the column has a default - IsPrimaryKey bool // true if the column is a primary key - Orig interface{} `yaml:"-" json:"-"` // the raw database column data + Table *Table `yaml:"-" json:"-"` // the table this column is in + Name string // the converted name of the column + DBName string // the original name of the column in the DB + Type string // the converted name of the type + DBType string // the original type of the column in the DB + IsArray bool // true if the column type is an array + Length int // non-zero if the type has a length (e.g. varchar[16]) + UserDefined bool // true if the type is user-defined + Nullable bool // true if the column is not NON NULL + HasDefault bool // true if the column has a default + IsPrimaryKey bool // true if the column is a primary key + IsFK bool // true if the column is a foreign key + HasFKRef bool // true if the column is referenced by a foreign key + FKColumn *ForeignKeyColumn // foreign key column definition + FKColumnRefs ForeignKeyColumns // all foreign key columns referencing this column + FKColumnRefsByName map[string]*ForeignKeyColumn `yaml:"-" json:"-"` // all foreign key columns referencing this column by foreign key name + Orig interface{} `yaml:"-" json:"-"` // the raw database column data +} + +// Foreign Key contains the +type ForeignKey struct { + DBName string // the original name of the foreign key constraint in the db + Name string // the converted name of the foreign key constraint + TableDBName string // the original name of the table in the db + RefTableDBName string // the original name of the foreign table in the db + Table *Table `yaml:"-" json:"-"` // the foreign key table + RefTable *Table `yaml:"-" json:"-"` // the foreign key foreign table + FKColumns ForeignKeyColumns // all foreign key columns belonging to the foreign key +} + +// Foreign Column contains the definition of a database foreign key at the kcolumn level +type ForeignKeyColumn struct { + DBName string // the original name of the foreign key constraint in the db + ColumnDBName string // the original name of the column in the db + RefColumnDBName string // the original name of the foreign column in the db + Column *Column `yaml:"-" json:"-"` // the foreign key column + RefColumn *Column `yaml:"-" json:"-"` // the referenced column } // Enum represents a type that has a set of allowed values. @@ -198,6 +238,39 @@ func contains(list []string, s string) bool { return false } +// Foreign keys represents a list of ForeignKey +type ForeignKeys []*ForeignKey + +// DBNames returns the list of db foreign key names +func (fk ForeignKeys) DBNames() Strings { + names := make(Strings, len(fk)) + for x := range fk { + names[x] = fk[x].DBName + } + return names +} + +// Names returns the list of converted foreign key names +func (fk ForeignKeys) Names() Strings { + names := make(Strings, len(fk)) + for x := range fk { + names[x] = fk[x].Name + } + return names +} + +// ForeignKeyColumns represents a list of ForeignKeyColumn +type ForeignKeyColumns []*ForeignKeyColumn + +// DBNames returns the list of db foreign key names +func (fkc ForeignKeyColumns) DBNames() Strings { + names := make(Strings, len(fkc)) + for x := range fkc { + names[x] = fkc[x].DBName + } + return names +} + // Columns represents the ordered list of columns in a table. type Columns []*Column diff --git a/run/preview.go b/run/preview.go index 6517bb1..b169269 100644 --- a/run/preview.go +++ b/run/preview.go @@ -28,7 +28,7 @@ Enum: {{.Name}}({{$schema}}.{{.DBName}}) {{end -}} {{range .Tables}} Table: {{.Name}}({{$schema}}.{{.DBName}}) -{{makeTable .Columns "{{.Name}}|{{.DBName}}|{{.Type}}|{{.DBType}}|{{.IsArray}}|{{.IsPrimaryKey}}|{{.Length}}|{{.UserDefined}}|{{.Nullable}}|{{.HasDefault}}" "Name" "DBName" "Type" "DBType" "IsArray" "IsPrimaryKey" "Length" "UserDefined" "Nullable" "HasDefault"}} +{{makeTable .Columns "{{.Name}}|{{.DBName}}|{{.Type}}|{{.DBType}}|{{.IsArray}}|{{.IsPrimaryKey}}|{{.IsFK}}|{{.HasFKRef}}|{{.Length}}|{{.UserDefined}}|{{.Nullable}}|{{.HasDefault}}" "Name" "DBName" "Type" "DBType" "IsArray" "IsPrimaryKey" "IsFK" "HasFKRef" "Length" "UserDefined" "Nullable" "HasDefault"}} {{end -}} {{end -}} `)) diff --git a/run/preview_test.go b/run/preview_test.go index 0532972..3402941 100644 --- a/run/preview_test.go +++ b/run/preview_test.go @@ -68,6 +68,25 @@ func (dummyDriver) Parse(log *log.Logger, conn string, schemaNames []string, fil Type: "*string", Nullable: true, }}, + }, { + Name: "tb2", + Columns: []*database.Column{{ + Name: "col1", + Type: "int", + IsPrimaryKey: true, + }, { + Name: "col2", + Type: "int", + IsForeignKey: true, + ForeignKey: &database.ForeignKey{ + SchemaName: "schema", + TableName: "tb2", + ColumnName: "col2", + Name: "tb2_col2_fkey", + ForeignTableName: "table", + ForeignColumnName: "col1", + }, + }}, }}, Enums: []*database.Enum{{ Name: "enum", @@ -96,6 +115,13 @@ const expectYaml = `schemas: nullable: false hasdefault: false isprimarykey: true + isfk: false + hasfkref: true + fkcolumn: null + fkcolumnrefs: + - dbname: tb2_col2_fkey + columndbname: col2 + refcolumndbname: col1 - name: abc col2 dbname: col2 type: '*INTEGER' @@ -106,6 +132,10 @@ const expectYaml = `schemas: nullable: true hasdefault: false isprimarykey: false + isfk: false + hasfkref: false + fkcolumn: null + fkcolumnrefs: [] - name: abc col3 dbname: col3 type: "" @@ -116,6 +146,10 @@ const expectYaml = `schemas: nullable: false hasdefault: false isprimarykey: false + isfk: false + hasfkref: false + fkcolumn: null + fkcolumnrefs: [] - name: abc col4 dbname: col4 type: "" @@ -126,6 +160,10 @@ const expectYaml = `schemas: nullable: true hasdefault: false isprimarykey: false + isfk: false + hasfkref: false + fkcolumn: null + fkcolumnrefs: [] primarykeys: - name: abc col1 dbname: col1 @@ -137,6 +175,82 @@ const expectYaml = `schemas: nullable: false hasdefault: false isprimarykey: true + isfk: false + hasfkref: true + fkcolumn: null + fkcolumnrefs: + - dbname: tb2_col2_fkey + columndbname: col2 + refcolumndbname: col1 + foreignkeys: [] + foreignkeyrefs: + - dbname: tb2_col2_fkey + name: abc tb2_col2_fkey + tabledbname: tb2 + reftabledbname: table + fkcolumns: + - dbname: tb2_col2_fkey + columndbname: col2 + refcolumndbname: col1 + - name: abc tb2 + dbname: tb2 + columns: + - name: abc col1 + dbname: col1 + type: INTEGER + dbtype: int + isarray: false + length: 0 + userdefined: false + nullable: false + hasdefault: false + isprimarykey: true + isfk: false + hasfkref: false + fkcolumn: null + fkcolumnrefs: [] + - name: abc col2 + dbname: col2 + type: INTEGER + dbtype: int + isarray: false + length: 0 + userdefined: false + nullable: false + hasdefault: false + isprimarykey: false + isfk: true + hasfkref: false + fkcolumn: + dbname: tb2_col2_fkey + columndbname: col2 + refcolumndbname: col1 + fkcolumnrefs: [] + primarykeys: + - name: abc col1 + dbname: col1 + type: INTEGER + dbtype: int + isarray: false + length: 0 + userdefined: false + nullable: false + hasdefault: false + isprimarykey: true + isfk: false + hasfkref: false + fkcolumn: null + fkcolumnrefs: [] + foreignkeys: + - dbname: tb2_col2_fkey + name: abc tb2_col2_fkey + tabledbname: tb2 + reftabledbname: table + fkcolumns: + - dbname: tb2_col2_fkey + columndbname: col2 + refcolumndbname: col1 + foreignkeyrefs: [] enums: - name: abc enum dbname: enum @@ -157,14 +271,23 @@ Enum: abc enum(schema.enum) Table: abc table(schema.table) -+----------+--------+----------+---------+---------+--------------+--------+-------------+----------+------------+ -| Name | DBName | Type | DBType | IsArray | IsPrimaryKey | Length | UserDefined | Nullable | HasDefault | -+----------+--------+----------+---------+---------+--------------+--------+-------------+----------+------------+ -| abc col1 | col1 | INTEGER | int | false | true | 0 | false | false | false | -| abc col2 | col2 | *INTEGER | *int | false | false | 0 | false | true | false | -| abc col3 | col3 | | string | false | false | 0 | false | false | false | -| abc col4 | col4 | | *string | false | false | 0 | false | true | false | -+----------+--------+----------+---------+---------+--------------+--------+-------------+----------+------------+ ++----------+--------+----------+---------+---------+--------------+-------+----------+--------+-------------+----------+------------+ +| Name | DBName | Type | DBType | IsArray | IsPrimaryKey | IsFK | HasFKRef | Length | UserDefined | Nullable | HasDefault | ++----------+--------+----------+---------+---------+--------------+-------+----------+--------+-------------+----------+------------+ +| abc col1 | col1 | INTEGER | int | false | true | false | true | 0 | false | false | false | +| abc col2 | col2 | *INTEGER | *int | false | false | false | false | 0 | false | true | false | +| abc col3 | col3 | | string | false | false | false | false | 0 | false | false | false | +| abc col4 | col4 | | *string | false | false | false | false | 0 | false | true | false | ++----------+--------+----------+---------+---------+--------------+-------+----------+--------+-------------+----------+------------+ + + +Table: abc tb2(schema.tb2) ++----------+--------+---------+--------+---------+--------------+-------+----------+--------+-------------+----------+------------+ +| Name | DBName | Type | DBType | IsArray | IsPrimaryKey | IsFK | HasFKRef | Length | UserDefined | Nullable | HasDefault | ++----------+--------+---------+--------+---------+--------------+-------+----------+--------+-------------+----------+------------+ +| abc col1 | col1 | INTEGER | int | false | true | false | false | 0 | false | false | false | +| abc col2 | col2 | INTEGER | int | false | false | true | false | 0 | false | false | false | ++----------+--------+---------+--------+---------+--------------+-------+----------+--------+-------------+----------+------------+ ` @@ -219,7 +342,17 @@ var expectJSON = ` "UserDefined": false, "Nullable": false, "HasDefault": false, - "IsPrimaryKey": true + "IsPrimaryKey": true, + "IsFK": false, + "HasFKRef": true, + "FKColumn": null, + "FKColumnRefs": [ + { + "DBName": "tb2_col2_fkey", + "ColumnDBName": "col2", + "RefColumnDBName": "col1" + } + ] }, { "Name": "abc col2", @@ -231,7 +364,11 @@ var expectJSON = ` "UserDefined": false, "Nullable": true, "HasDefault": false, - "IsPrimaryKey": false + "IsPrimaryKey": false, + "IsFK": false, + "HasFKRef": false, + "FKColumn": null, + "FKColumnRefs": null }, { "Name": "abc col3", @@ -243,7 +380,11 @@ var expectJSON = ` "UserDefined": false, "Nullable": false, "HasDefault": false, - "IsPrimaryKey": false + "IsPrimaryKey": false, + "IsFK": false, + "HasFKRef": false, + "FKColumn": null, + "FKColumnRefs": null }, { "Name": "abc col4", @@ -255,7 +396,11 @@ var expectJSON = ` "UserDefined": false, "Nullable": true, "HasDefault": false, - "IsPrimaryKey": false + "IsPrimaryKey": false, + "IsFK": false, + "HasFKRef": false, + "FKColumn": null, + "FKColumnRefs": null } ], "PrimaryKeys": [ @@ -269,9 +414,111 @@ var expectJSON = ` "UserDefined": false, "Nullable": false, "HasDefault": false, - "IsPrimaryKey": true + "IsPrimaryKey": true, + "IsFK": false, + "HasFKRef": true, + "FKColumn": null, + "FKColumnRefs": [ + { + "DBName": "tb2_col2_fkey", + "ColumnDBName": "col2", + "RefColumnDBName": "col1" + } + ] + } + ], + "ForeignKeys": null, + "ForeignKeyRefs": [ + { + "DBName": "tb2_col2_fkey", + "Name": "abc tb2_col2_fkey", + "TableDBName": "tb2", + "RefTableDBName": "table", + "FKColumns": [ + { + "DBName": "tb2_col2_fkey", + "ColumnDBName": "col2", + "RefColumnDBName": "col1" + } + ] } ] + }, + { + "Name": "abc tb2", + "DBName": "tb2", + "Columns": [ + { + "Name": "abc col1", + "DBName": "col1", + "Type": "INTEGER", + "DBType": "int", + "IsArray": false, + "Length": 0, + "UserDefined": false, + "Nullable": false, + "HasDefault": false, + "IsPrimaryKey": true, + "IsFK": false, + "HasFKRef": false, + "FKColumn": null, + "FKColumnRefs": null + }, + { + "Name": "abc col2", + "DBName": "col2", + "Type": "INTEGER", + "DBType": "int", + "IsArray": false, + "Length": 0, + "UserDefined": false, + "Nullable": false, + "HasDefault": false, + "IsPrimaryKey": false, + "IsFK": true, + "HasFKRef": false, + "FKColumn": { + "DBName": "tb2_col2_fkey", + "ColumnDBName": "col2", + "RefColumnDBName": "col1" + }, + "FKColumnRefs": null + } + ], + "PrimaryKeys": [ + { + "Name": "abc col1", + "DBName": "col1", + "Type": "INTEGER", + "DBType": "int", + "IsArray": false, + "Length": 0, + "UserDefined": false, + "Nullable": false, + "HasDefault": false, + "IsPrimaryKey": true, + "IsFK": false, + "HasFKRef": false, + "FKColumn": null, + "FKColumnRefs": null + } + ], + "ForeignKeys": [ + { + "DBName": "tb2_col2_fkey", + "Name": "abc tb2_col2_fkey", + "TableDBName": "tb2", + "RefTableDBName": "table", + "FKColumns": [ + { + "DBName": "tb2_col2_fkey", + "ColumnDBName": "col2", + "RefColumnDBName": "col1" + } + ] + } + ], + "ForeignKeyRefs": null } ], "Enums": [ diff --git a/site/content/templates/data.md b/site/content/templates/data.md index 4134a80..4188f56 100644 --- a/site/content/templates/data.md +++ b/site/content/templates/data.md @@ -56,13 +56,13 @@ Data representing the entire DB schema(s). | Schemas | list of [Schemas](#schema) | all the schemas parsed by gnorm | SchemasByName | map[string][Schema](#schema) | map of schema DBName to Schema - ### Column Column is the data about a DB column of a table. | Property | Type | Description | | --- | ---- | --- | +| Table | [Table](#table) | the table this column is in | Name | string | the converted name of the column | DBName | string | the original name of the column in the DB | Type |string | the converted name of the type @@ -73,10 +73,13 @@ Column is the data about a DB column of a table. | Nullable | boolean | true if the column is not NON NULL | HasDefault | boolean | true if the column has a default | IsPrimaryKey | boolean | true if the column is a primary key +| IsFK | boolean | true if the column is a foreign key +| HasFKRef | boolean | true if the column is referenced by a foreign key +| FKColumn | [ForeignKeyColumn](#foreignKeyColumn) | foreign key column definition +| FKColumnRefs | [ForeignKeyColumns](#foreignKeyColumns) | all foreign key columns referencing this column +| FKColumnRefsByName | map[string][ForeignKeyColumn](#foreignKeyColumn) | all foreign key columns referencing this column by foreign key name | Orig | db-specific | the raw database column data (different per db type) - - ### Columns Columns is an ordered list of [Column](#column) values from a table. Columns @@ -133,13 +136,48 @@ have the following properties: |DBName | string | the original label of the enum in the DB |Value | int | the value for this enum value (order) +### ForeignKey + +| Property | Type | Description | +| --- | ---- | ---| +| DBName | string | the original name of the foreign key constraint in the db +| Name | string | the converted name of the foreign key constraint +| TableDBName | string | the original name of the table in the db +| RefTableDBName | string | the original name of the foreign table in the db +| Table | [Table](#table) | the foreign key table +| RefTable | [Table](#table) | the foreign key foreign table +| FKColumns | [ForeignKeyColumns](#foreignKeyColumns) | all foreign key columns belonging to the foreign key + +### ForeignKeys + +| Property | Type | Description | +| --- | ---- | ---| +| DBNames | [Strings](#strings) | the list of DBNames of all the foreign keys +| Names | [Strings](#strings) | the list of converted names of all the foreign keys + +### ForeignKeyColumn + +| Property | Type | Description | +| --- | ---- | ---| +| DBName | string | the original name of the foreign key constraint in the db +| ColumnDBName | string | the original name of the column in the db +| RefColumnDBName | string | the original name of the foreign column in the db +| Column | [Column](#column) | the foreign key column +| RefColumn | [Column](#column) | the referenced column + +### ForeignKeyColumns + +| Property | Type | Description | +| --- | ---- | ---| +| DBNames | [Strings](#strings) | the list of DBNames of all the foreign keys + ### Schema A schema represents a namespace of tables and enums in a database. | Property | Type | Description | | --- | ---- | --- | -| Name | string the converted name of the schema +| Name | string | the converted name of the schema | DBName | string | the original name of the schema in the DB | Tables | [Tables](#tables) | the list of [Table](#table) values in this schema | Enums | [Enums](#enums) | the list of [Enum](#enum) values in this schema @@ -165,6 +203,10 @@ Strings is a list of string values with the following methods | ColumnsByName | map[string][Column](#column) | map of column dbname to column | PrimaryKeys | [Columns](#columns) | primary key columns | HasPrimaryKey | bool | does the column have at least one primary key +| ForeignKeys | [ForeignKeys](#foreignKeys) | foreign keys +| ForeignKeyRefs | [ForeignKeys](#foreignKeys) | foreign keys referencing this table +| FKByName | map[string][ForeignKey](#foreignKey) | foreign keys by foreign key name +| FKRefsByName | map[string][ForeignKey](#foreignKey) | foreign keys referencing this table by name ### Tables