Add oracle support
parent
b31c3d327b
commit
270241207e
|
@ -5,3 +5,4 @@ DDL-Parser is a simple go module to parse all table columns from a running datab
|
||||||
The following SQL databases are supported and tested:
|
The following SQL databases are supported and tested:
|
||||||
|
|
||||||
- MariaDB *10.6*
|
- MariaDB *10.6*
|
||||||
|
- Oracle *21*
|
1
go.mod
1
go.mod
|
@ -11,5 +11,6 @@ require (
|
||||||
|
|
||||||
require (
|
require (
|
||||||
filippo.io/edwards25519 v1.1.0 // indirect
|
filippo.io/edwards25519 v1.1.0 // indirect
|
||||||
|
github.com/sijms/go-ora/v2 v2.8.11 // indirect
|
||||||
golang.org/x/sys v0.19.0 // indirect
|
golang.org/x/sys v0.19.0 // indirect
|
||||||
)
|
)
|
||||||
|
|
2
go.sum
2
go.sum
|
@ -8,5 +8,7 @@ github.com/go-sql-driver/mysql v1.8.1 h1:LedoTUt/eveggdHS9qUFC1EFSa8bU2+1pZjSRpv
|
||||||
github.com/go-sql-driver/mysql v1.8.1/go.mod h1:wEBSXgmK//2ZFJyE+qWnIsVGmvmEKlqwuVSjsCm7DZg=
|
github.com/go-sql-driver/mysql v1.8.1/go.mod h1:wEBSXgmK//2ZFJyE+qWnIsVGmvmEKlqwuVSjsCm7DZg=
|
||||||
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
||||||
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||||
|
github.com/sijms/go-ora/v2 v2.8.11 h1:oQtSX145kCYSjnrmWdtqp2LON9wOQW09wPJ5pIEn5Tg=
|
||||||
|
github.com/sijms/go-ora/v2 v2.8.11/go.mod h1:EHxlY6x7y9HAsdfumurRfTd+v8NrEOTR3Xl4FWlH6xk=
|
||||||
golang.org/x/sys v0.19.0 h1:q5f1RH2jigJ1MoAWp2KTp3gm5zAGFUTarQZ5U386+4o=
|
golang.org/x/sys v0.19.0 h1:q5f1RH2jigJ1MoAWp2KTp3gm5zAGFUTarQZ5U386+4o=
|
||||||
golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||||
|
|
|
@ -17,7 +17,7 @@ func TestGetTableSimple(t *testing.T) {
|
||||||
mDb := NewMariaDb(db)
|
mDb := NewMariaDb(db)
|
||||||
|
|
||||||
// Create test table
|
// Create test table
|
||||||
tableName, err := createMariadbTable(db,
|
tableName, err := createTable(db,
|
||||||
`
|
`
|
||||||
id INT(10) PRIMARY KEY NOT NULL AUTO_INCREMENT,
|
id INT(10) PRIMARY KEY NOT NULL AUTO_INCREMENT,
|
||||||
txt VARCHAR(100) DEFAULT 'Ich bins, der Tim!',
|
txt VARCHAR(100) DEFAULT 'Ich bins, der Tim!',
|
||||||
|
@ -28,7 +28,7 @@ func TestGetTableSimple(t *testing.T) {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Failed to create table: %s", err)
|
t.Fatalf("Failed to create table: %s", err)
|
||||||
}
|
}
|
||||||
defer dropMariadbTable(db, tableName)
|
defer dropTable(db, tableName)
|
||||||
|
|
||||||
// Get columns
|
// Get columns
|
||||||
table, err := mDb.GetTable(RequireEnvString("MARIADB_DB", t), tableName)
|
table, err := mDb.GetTable(RequireEnvString("MARIADB_DB", t), tableName)
|
||||||
|
@ -99,17 +99,17 @@ func TestGetTableFK(t *testing.T) {
|
||||||
mDb := NewMariaDb(db)
|
mDb := NewMariaDb(db)
|
||||||
|
|
||||||
// Create table we reference to
|
// Create table we reference to
|
||||||
referenceTableName, err := createMariadbTable(db, `
|
referenceTableName, err := createTable(db, `
|
||||||
id_to_ref INT(10) PRIMARY KEY NOT NULL AUTO_INCREMENT,
|
id_to_ref INT(10) PRIMARY KEY NOT NULL AUTO_INCREMENT,
|
||||||
rand VARCHAR(10) NOT NULL`,
|
rand VARCHAR(10) NOT NULL`,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Failed to create table: %s", err)
|
t.Fatalf("Failed to create table: %s", err)
|
||||||
}
|
}
|
||||||
defer dropMariadbTable(db, referenceTableName)
|
defer dropTable(db, referenceTableName)
|
||||||
|
|
||||||
// Create table with reference
|
// Create table with reference
|
||||||
tableName, err := createMariadbTable(db, `
|
tableName, err := createTable(db, `
|
||||||
id INT(10) PRIMARY KEY NOT NULL AUTO_INCREMENT,
|
id INT(10) PRIMARY KEY NOT NULL AUTO_INCREMENT,
|
||||||
other_id INT(10) NOT NULL,
|
other_id INT(10) NOT NULL,
|
||||||
CONSTRAINT fk_test_constraint_for_you FOREIGN KEY(other_id) REFERENCES `+referenceTableName+`(id_to_ref)
|
CONSTRAINT fk_test_constraint_for_you FOREIGN KEY(other_id) REFERENCES `+referenceTableName+`(id_to_ref)
|
||||||
|
@ -117,7 +117,7 @@ func TestGetTableFK(t *testing.T) {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Failed to create table: %s", err)
|
t.Fatalf("Failed to create table: %s", err)
|
||||||
}
|
}
|
||||||
defer dropMariadbTable(db, tableName)
|
defer dropTable(db, tableName)
|
||||||
|
|
||||||
table, err := mDb.GetTable(RequireEnvString("MARIADB_DB", t), tableName)
|
table, err := mDb.GetTable(RequireEnvString("MARIADB_DB", t), tableName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -177,17 +177,17 @@ func TestGetTables(t *testing.T) {
|
||||||
mDb := NewMariaDb(db)
|
mDb := NewMariaDb(db)
|
||||||
|
|
||||||
// Create two simple tables
|
// Create two simple tables
|
||||||
tableName1, err := createMariadbTable(db, `idTab1 INT(10) NOT NULL`)
|
tableName1, err := createTable(db, `idTab1 INT(10) NOT NULL`)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Failed to create table: %s", err)
|
t.Fatalf("Failed to create table: %s", err)
|
||||||
}
|
}
|
||||||
defer dropMariadbTable(db, tableName1)
|
defer dropTable(db, tableName1)
|
||||||
|
|
||||||
tableName2, err := createMariadbTable(db, `idTab2 INT(10) NOT NULL`)
|
tableName2, err := createTable(db, `idTab2 INT(10) NOT NULL`)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Failed to create table: %s", err)
|
t.Fatalf("Failed to create table: %s", err)
|
||||||
}
|
}
|
||||||
defer dropMariadbTable(db, tableName2)
|
defer dropTable(db, tableName2)
|
||||||
|
|
||||||
tables, err := mDb.GetTables(RequireEnvString("MARIADB_DB", t))
|
tables, err := mDb.GetTables(RequireEnvString("MARIADB_DB", t))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -279,9 +279,9 @@ func ConnectToMariadb(t *testing.T) *sql.DB {
|
||||||
return db
|
return db
|
||||||
}
|
}
|
||||||
|
|
||||||
// createMariadbTable creates a table with the provided column configuration
|
// createTable creates a table with the provided column configuration
|
||||||
// in statement and returns the created table name
|
// in statement and returns the created table name
|
||||||
func createMariadbTable(db *sql.DB, statement string) (string, error) {
|
func createTable(db *sql.DB, statement string) (string, error) {
|
||||||
name, _ := GenerateRandomString(8)
|
name, _ := GenerateRandomString(8)
|
||||||
name = "ddl_test_" + name
|
name = "ddl_test_" + name
|
||||||
sql := fmt.Sprintf("CREATE TABLE %s (%s)", name, statement)
|
sql := fmt.Sprintf("CREATE TABLE %s (%s)", name, statement)
|
||||||
|
@ -292,7 +292,7 @@ func createMariadbTable(db *sql.DB, statement string) (string, error) {
|
||||||
}
|
}
|
||||||
return name, err
|
return name, err
|
||||||
}
|
}
|
||||||
func dropMariadbTable(db *sql.DB, tableName string) error {
|
func dropTable(db *sql.DB, tableName string) error {
|
||||||
_, err := db.Exec("DROP TABLE " + tableName)
|
_, err := db.Exec("DROP TABLE " + tableName)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,228 @@
|
||||||
|
package ddl
|
||||||
|
|
||||||
|
import (
|
||||||
|
"database/sql"
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"git.rpjosh.de/RPJosh/go-logger"
|
||||||
|
)
|
||||||
|
|
||||||
|
type OracleTableType string
|
||||||
|
|
||||||
|
const (
|
||||||
|
OracleTable OracleTableType = "TABLE"
|
||||||
|
OracleView OracleTableType = "VIEW"
|
||||||
|
)
|
||||||
|
|
||||||
|
var _ DbSystem = &OracleDb{}
|
||||||
|
var _ Columner = &OracleColumn{}
|
||||||
|
|
||||||
|
// OracleDb implements "DbSystem" for an oracle database
|
||||||
|
type OracleDb struct {
|
||||||
|
db *sql.DB
|
||||||
|
}
|
||||||
|
|
||||||
|
type OracleColumn struct {
|
||||||
|
*Column
|
||||||
|
|
||||||
|
// Weather this column has the auto_increment flag
|
||||||
|
AutoIncrement bool
|
||||||
|
|
||||||
|
// Character lenght or numeric precision on the LEFT side
|
||||||
|
// of the dot
|
||||||
|
DataTypeLenght int
|
||||||
|
|
||||||
|
// Decimal precision on the RIGHT side of the dot
|
||||||
|
Scale int
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *OracleColumn) GetExtraInfos() string {
|
||||||
|
return "Oracle!"
|
||||||
|
}
|
||||||
|
func (c *OracleColumn) GetSpecificInfos() any {
|
||||||
|
return c
|
||||||
|
}
|
||||||
|
func (s *OracleDb) newColumn() *OracleColumn {
|
||||||
|
c := &OracleColumn{}
|
||||||
|
c.Column = &Column{}
|
||||||
|
c.Column.Extras = c
|
||||||
|
return c
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewMariaDb initializes a new database parser for an oracle database
|
||||||
|
func NewOracleDb(db *sql.DB) *OracleDb {
|
||||||
|
return &OracleDb{
|
||||||
|
db: db,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *OracleDb) GetTable(schema, name string) (*Table, error) {
|
||||||
|
ssql := `
|
||||||
|
SELECT
|
||||||
|
col.OWNER,
|
||||||
|
col.table_name,
|
||||||
|
col.COLUMN_NAME,
|
||||||
|
col.DATA_DEFAULT,
|
||||||
|
col.NULLABLE,
|
||||||
|
col.DATA_TYPE,
|
||||||
|
COALESCE(col.DATA_PRECISION, col.DATA_LENGTH, 0), COALESCE(col.DATA_SCALE, 0),
|
||||||
|
col.IDENTITY_COLUMN, con.CONSTRAINT_TYPE,
|
||||||
|
coms.COMMENTS,
|
||||||
|
-- Foreign key data
|
||||||
|
act.OWNER, act.table_name, act.COLUMN_NAME
|
||||||
|
FROM all_tab_columns col
|
||||||
|
LEFT JOIN all_cons_columns cc ON cc.TABLE_NAME = col.TABLE_NAME AND col.COLUMN_NAME = cc.COLUMN_NAME
|
||||||
|
LEFT JOIN all_constraints con ON cc.CONSTRAINT_NAME = con.CONSTRAINT_NAME
|
||||||
|
LEFT JOIN all_cons_columns act ON con.r_owner = act.owner
|
||||||
|
AND con.r_constraint_name = act.constraint_name
|
||||||
|
LEFT JOIN dba_col_comments coms ON coms.OWNER = col.OWNER AND coms.TABLE_NAME = col.TABLE_NAME
|
||||||
|
AND coms.COLUMN_NAME = col.COLUMN_NAME
|
||||||
|
WHERE col.table_name = UPPER(:0)
|
||||||
|
AND col.OWNER = UPPER(:1)
|
||||||
|
ORDER BY col.column_id
|
||||||
|
`
|
||||||
|
rows, err := s.db.Query(ssql, name, schema)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to query all_tab_columns: %s", err)
|
||||||
|
}
|
||||||
|
defer rows.Close()
|
||||||
|
|
||||||
|
lastColumnName := ""
|
||||||
|
table := &Table{}
|
||||||
|
count := 0
|
||||||
|
for rows.Next() {
|
||||||
|
var tableSchema, tableName, isNullable, identity string
|
||||||
|
var fkOwner, fkTable, fkColumn, keyType, comment sql.NullString
|
||||||
|
column := s.newColumn()
|
||||||
|
|
||||||
|
if err := rows.Scan(
|
||||||
|
&tableSchema, &tableName,
|
||||||
|
&column.Name, &column.DefaultValue, &isNullable,
|
||||||
|
&column.InternalType, &column.DataTypeLenght, &column.Scale,
|
||||||
|
&identity, &keyType, &comment,
|
||||||
|
&fkOwner, &fkTable, &fkColumn,
|
||||||
|
); err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to scan row: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Apply data
|
||||||
|
column.CanBeNull = isNullable == "Y"
|
||||||
|
column.Type = s.GetDataType(column.InternalType, column)
|
||||||
|
column.PrimaryKey = identity == "YES" || (keyType.Valid && keyType.String == "P")
|
||||||
|
if fkColumn.Valid {
|
||||||
|
column.ForeignKey = true
|
||||||
|
column.ForeignKeyColumn.Column = fkColumn.String
|
||||||
|
column.ForeignKeyColumn.Name = fkTable.String
|
||||||
|
column.ForeignKeyColumn.Schema = fkOwner.String
|
||||||
|
}
|
||||||
|
|
||||||
|
// The default value contains the raw single quotes of the create statement
|
||||||
|
if column.DefaultValue.Valid {
|
||||||
|
column.DefaultValue.String = strings.TrimPrefix(column.DefaultValue.String, "'")
|
||||||
|
column.DefaultValue.String = strings.TrimSuffix(column.DefaultValue.String, "'")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set comment
|
||||||
|
if comment.Valid {
|
||||||
|
column.Comment = strings.ReplaceAll(comment.String, "\\n", "\n")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initialize new table metadata
|
||||||
|
if count == 0 {
|
||||||
|
table.Schema = tableSchema
|
||||||
|
table.Name = tableName
|
||||||
|
}
|
||||||
|
|
||||||
|
// It's possible that we get the same column twice for different keyTypes.
|
||||||
|
// Always prefer the primary or foreign key constraint
|
||||||
|
if lastColumnName == column.Name {
|
||||||
|
if column.ForeignKey || column.PrimaryKey {
|
||||||
|
// Don't skip, but remove the last one
|
||||||
|
table.Columns = table.Columns[:len(table.Columns)-1]
|
||||||
|
} else {
|
||||||
|
// We use the primary key or foreign key
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
lastColumnName = column.Name
|
||||||
|
|
||||||
|
table.Columns = append(table.Columns, column.Column)
|
||||||
|
count += 1
|
||||||
|
}
|
||||||
|
|
||||||
|
// We got no data
|
||||||
|
if count == 0 {
|
||||||
|
return nil, fmt.Errorf("%s.%s was not found", schema, name)
|
||||||
|
}
|
||||||
|
|
||||||
|
return table, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *OracleDb) GetTables(schema string) ([]*Table, error) {
|
||||||
|
return s.GetTablesByType(schema, OracleTable)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *OracleDb) GetTablesByType(schema string, typ OracleTableType) ([]*Table, error) {
|
||||||
|
sql := `
|
||||||
|
SELECT DISTINCT
|
||||||
|
OWNER,
|
||||||
|
OBJECT_NAME,
|
||||||
|
OBJECT_TYPE
|
||||||
|
FROM ALL_OBJECTS
|
||||||
|
WHERE OBJECT_TYPE = :0
|
||||||
|
AND OWNER <> 'SYS'
|
||||||
|
AND OWNER = :1
|
||||||
|
ORDER BY OBJECT_NAME ASC
|
||||||
|
`
|
||||||
|
rows, err := s.db.Query(sql, string(typ), schema)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to query all_objects: %s", err)
|
||||||
|
}
|
||||||
|
defer rows.Close()
|
||||||
|
|
||||||
|
rtc := []*Table{}
|
||||||
|
for rows.Next() {
|
||||||
|
var tableSchema, tableName, tableType string
|
||||||
|
if err := rows.Scan(&tableSchema, &tableName, &tableType); err != nil {
|
||||||
|
return rtc, fmt.Errorf("failed to scan row: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
t, err := s.GetTable(tableSchema, tableName)
|
||||||
|
if err != nil {
|
||||||
|
return rtc, fmt.Errorf("failed to get data for %s.%s: %s", tableSchema, tableName, err)
|
||||||
|
}
|
||||||
|
rtc = append(rtc, t)
|
||||||
|
}
|
||||||
|
|
||||||
|
return rtc, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *OracleDb) GetDataType(internalType string, col *OracleColumn) DataType {
|
||||||
|
internalType = strings.ToLower(internalType)
|
||||||
|
|
||||||
|
// Remove any data type length (for some datatypes they are returned...)
|
||||||
|
if lastBracket := strings.Index(internalType, "("); lastBracket != -1 {
|
||||||
|
internalType = internalType[:lastBracket]
|
||||||
|
}
|
||||||
|
|
||||||
|
switch internalType {
|
||||||
|
case "varchar", "varchar2", "nvarchar", "nvarchar2":
|
||||||
|
return StringType
|
||||||
|
case "double":
|
||||||
|
return DoubleType
|
||||||
|
case "date", "timestamp", "timestamptz":
|
||||||
|
return DateType
|
||||||
|
default:
|
||||||
|
// A number can either be a double or a int
|
||||||
|
if internalType == "number" {
|
||||||
|
if col.Scale == 0 {
|
||||||
|
return IntType
|
||||||
|
} else {
|
||||||
|
return DoubleType
|
||||||
|
}
|
||||||
|
}
|
||||||
|
logger.Warning("OracleDb: received unknown data type column: %s", internalType)
|
||||||
|
return UnknownType
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,300 @@
|
||||||
|
package ddl
|
||||||
|
|
||||||
|
import (
|
||||||
|
"database/sql"
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"git.rpjosh.de/RPJosh/go-logger"
|
||||||
|
"github.com/google/go-cmp/cmp"
|
||||||
|
goOra "github.com/sijms/go-ora/v2"
|
||||||
|
)
|
||||||
|
|
||||||
|
// TestGetTableSimple tests the construction of a Table struct
|
||||||
|
// with all supported data types and fields
|
||||||
|
func TestGetTableSimpleOracle(t *testing.T) {
|
||||||
|
db := ConnectToOracle(t)
|
||||||
|
oDb := NewOracleDb(db)
|
||||||
|
|
||||||
|
// Create test table
|
||||||
|
tableName, err := createTable(db,
|
||||||
|
`
|
||||||
|
id NUMERIC(10,0) PRIMARY KEY NOT NULL,
|
||||||
|
txt VARCHAR2(100) DEFAULT 'Ich bins, der Tim!',
|
||||||
|
dte DATE NOT NULL
|
||||||
|
`,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Failed to create table: %s", err)
|
||||||
|
}
|
||||||
|
tableName = strings.ToUpper(tableName)
|
||||||
|
defer dropTable(db, tableName)
|
||||||
|
|
||||||
|
// Comment table
|
||||||
|
if err := addOracleComment(db, tableName, "DTE", `Hallo ihr da!\nZeilenumbrüche`); err != nil {
|
||||||
|
t.Fatalf("Failed to comment table: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get columns
|
||||||
|
table, err := oDb.GetTable(RequireEnvString("ORACLE_USER", t), tableName)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Failed to get columns: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
expected := &Table{
|
||||||
|
Name: strings.ToUpper(tableName),
|
||||||
|
Schema: RequireEnvString("ORACLE_USER", t),
|
||||||
|
}
|
||||||
|
columns := []*OracleColumn{
|
||||||
|
{
|
||||||
|
Column: &Column{
|
||||||
|
Name: "ID",
|
||||||
|
PrimaryKey: true,
|
||||||
|
CanBeNull: false,
|
||||||
|
Type: IntType,
|
||||||
|
InternalType: "NUMBER",
|
||||||
|
},
|
||||||
|
DataTypeLenght: 10,
|
||||||
|
Scale: 0,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Column: &Column{
|
||||||
|
Name: "TXT",
|
||||||
|
PrimaryKey: false,
|
||||||
|
CanBeNull: true,
|
||||||
|
Type: StringType,
|
||||||
|
InternalType: "VARCHAR2",
|
||||||
|
DefaultValue: sql.NullString{
|
||||||
|
Valid: true,
|
||||||
|
String: "Ich bins, der Tim!",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
AutoIncrement: false,
|
||||||
|
DataTypeLenght: 100,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Column: &Column{
|
||||||
|
Name: "DTE",
|
||||||
|
PrimaryKey: false,
|
||||||
|
CanBeNull: false,
|
||||||
|
Type: DateType,
|
||||||
|
InternalType: "DATE",
|
||||||
|
Comment: "Hallo ihr da!\nZeilenumbrüche",
|
||||||
|
},
|
||||||
|
AutoIncrement: false,
|
||||||
|
DataTypeLenght: 7,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, c := range columns {
|
||||||
|
c.Extras = c
|
||||||
|
expected.Columns = append(expected.Columns, c.Column)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Compare struct
|
||||||
|
if diff := cmp.Diff(table, expected); diff != "" {
|
||||||
|
t.Errorf("Mismatch of columns (-want +got):\n%s", diff)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestGetTableSimple tests the construction of a Table struct
|
||||||
|
// that references another table
|
||||||
|
func TestGetTableOracleFK(t *testing.T) {
|
||||||
|
db := ConnectToOracle(t)
|
||||||
|
mDb := NewOracleDb(db)
|
||||||
|
|
||||||
|
// Create table we reference to
|
||||||
|
referenceTableName, err := createTable(db, `
|
||||||
|
id_to_ref NUMBER(10,0) PRIMARY KEY NOT NULL,
|
||||||
|
rand VARCHAR2(10) NOT NULL`,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Failed to create table: %s", err)
|
||||||
|
}
|
||||||
|
defer dropTable(db, referenceTableName)
|
||||||
|
|
||||||
|
// Create table with reference
|
||||||
|
tableName, err := createTable(db, `
|
||||||
|
id NUMBER(10,0) PRIMARY KEY NOT NULL,
|
||||||
|
other_id NUMBER(10,0) NOT NULL,
|
||||||
|
CONSTRAINT fk_test_constraint_for_you FOREIGN KEY(other_id) REFERENCES `+referenceTableName+`(id_to_ref)
|
||||||
|
`)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Failed to create table: %s", err)
|
||||||
|
}
|
||||||
|
defer dropTable(db, tableName)
|
||||||
|
|
||||||
|
table, err := mDb.GetTable(RequireEnvString("ORACLE_USER", t), tableName)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Failed to get columns: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
expected := &Table{
|
||||||
|
Name: strings.ToUpper(tableName),
|
||||||
|
Schema: RequireEnvString("ORACLE_USER", t),
|
||||||
|
}
|
||||||
|
columns := []*OracleColumn{
|
||||||
|
{
|
||||||
|
Column: &Column{
|
||||||
|
Name: "ID",
|
||||||
|
PrimaryKey: true,
|
||||||
|
CanBeNull: false,
|
||||||
|
Type: IntType,
|
||||||
|
InternalType: "NUMBER",
|
||||||
|
},
|
||||||
|
DataTypeLenght: 10,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Column: &Column{
|
||||||
|
Name: "OTHER_ID",
|
||||||
|
PrimaryKey: false,
|
||||||
|
CanBeNull: false,
|
||||||
|
Type: IntType,
|
||||||
|
InternalType: "NUMBER",
|
||||||
|
ForeignKey: true,
|
||||||
|
ForeignKeyColumn: ForeignColumn{
|
||||||
|
Name: strings.ToUpper(referenceTableName),
|
||||||
|
Schema: RequireEnvString("ORACLE_USER", t),
|
||||||
|
Column: "ID_TO_REF",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
DataTypeLenght: 10,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, c := range columns {
|
||||||
|
c.Extras = c
|
||||||
|
expected.Columns = append(expected.Columns, c.Column)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Compare struct
|
||||||
|
if diff := cmp.Diff(table, expected); diff != "" {
|
||||||
|
t.Errorf("Mismatch (-want +got):\n%s", diff)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestGetTableSimple tests the selecting of multiple tables to a []Table array
|
||||||
|
func TestGetTablesOracle(t *testing.T) {
|
||||||
|
db := ConnectToOracle(t)
|
||||||
|
mDb := NewOracleDb(db)
|
||||||
|
|
||||||
|
// Create two simple tables
|
||||||
|
tableName1, err := createTable(db, `idTab1 NUMBER(10,0) NOT NULL`)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Failed to create table: %s", err)
|
||||||
|
}
|
||||||
|
tableName1 = strings.ToUpper(tableName1)
|
||||||
|
defer dropTable(db, tableName1)
|
||||||
|
|
||||||
|
tableName2, err := createTable(db, `idTab2 NUMBER(10,0) NOT NULL`)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Failed to create table: %s", err)
|
||||||
|
}
|
||||||
|
tableName2 = strings.ToUpper(tableName2)
|
||||||
|
defer dropTable(db, tableName2)
|
||||||
|
|
||||||
|
tables, err := mDb.GetTables(RequireEnvString("ORACLE_USER", t))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Failed to get tables: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
found1 := 0
|
||||||
|
found2 := 0
|
||||||
|
for _, tt := range tables {
|
||||||
|
if tt.Name == tableName1 {
|
||||||
|
found1 = found1 + 1
|
||||||
|
|
||||||
|
// Compare table
|
||||||
|
expected := &Table{
|
||||||
|
Name: tt.Name,
|
||||||
|
Schema: RequireEnvString("ORACLE_USER", t),
|
||||||
|
}
|
||||||
|
columns := []*OracleColumn{
|
||||||
|
{
|
||||||
|
Column: &Column{
|
||||||
|
Name: "IDTAB1",
|
||||||
|
CanBeNull: false,
|
||||||
|
Type: IntType,
|
||||||
|
InternalType: "NUMBER",
|
||||||
|
},
|
||||||
|
DataTypeLenght: 10,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, c := range columns {
|
||||||
|
c.Extras = c
|
||||||
|
expected.Columns = append(expected.Columns, c.Column)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Compare struct
|
||||||
|
if diff := cmp.Diff(tt, expected); diff != "" {
|
||||||
|
t.Errorf("TestGetTables() mismatch of tab1: (-want +got):\n%s", diff)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if tt.Name == tableName2 {
|
||||||
|
found2 = found2 + 1
|
||||||
|
|
||||||
|
// Compare table
|
||||||
|
expected := &Table{
|
||||||
|
Name: tt.Name,
|
||||||
|
Schema: RequireEnvString("ORACLE_USER", t),
|
||||||
|
}
|
||||||
|
columns := []*OracleColumn{
|
||||||
|
{
|
||||||
|
Column: &Column{
|
||||||
|
Name: "IDTAB2",
|
||||||
|
CanBeNull: false,
|
||||||
|
Type: IntType,
|
||||||
|
InternalType: "NUMBER",
|
||||||
|
},
|
||||||
|
DataTypeLenght: 10,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, c := range columns {
|
||||||
|
c.Extras = c
|
||||||
|
expected.Columns = append(expected.Columns, c.Column)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Compare struct
|
||||||
|
if diff := cmp.Diff(tt, expected); diff != "" {
|
||||||
|
t.Errorf("Mismatch of tab2: (-want +got):\n%s", diff)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// We expected to find exactly one single table
|
||||||
|
if found1 != 1 {
|
||||||
|
t.Errorf("Found %d instances of tab1. Expected 1 (len(rtc) = %d)", found1, len(tables))
|
||||||
|
}
|
||||||
|
if found2 != 1 {
|
||||||
|
t.Errorf("Found %d instances of tab2. Expected 1 (len(rtc) = %d)", found1, len(tables))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func addOracleComment(db *sql.DB, tbl string, column string, comment string) error {
|
||||||
|
comment = strings.ReplaceAll(comment, "\n", `'||char(10)||'`)
|
||||||
|
sql := fmt.Sprintf("COMMENT ON COLUMN \"%s\".\"%s\" IS '%s'", tbl, column, comment)
|
||||||
|
_, err := db.Exec(sql)
|
||||||
|
if err != nil {
|
||||||
|
logger.Debug("Statement for create comment:\n%s", sql)
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func ConnectToOracle(t *testing.T) *sql.DB {
|
||||||
|
conString := goOra.BuildUrl(
|
||||||
|
RequireEnvString("ORACLE_SERVER", t),
|
||||||
|
RequireEnvInt("ORACLE_PORT", t),
|
||||||
|
RequireEnvString("ORACLE_SERVICE", t),
|
||||||
|
RequireEnvString("ORACLE_USER", t),
|
||||||
|
RequireEnvString("ORACLE_PASSWORD", t),
|
||||||
|
map[string]string{},
|
||||||
|
)
|
||||||
|
|
||||||
|
db, err := sql.Open("oracle", conString)
|
||||||
|
if err != nil {
|
||||||
|
panic(fmt.Sprintf("Failed to open DB connection: %s", err))
|
||||||
|
}
|
||||||
|
|
||||||
|
return db
|
||||||
|
}
|
|
@ -4,6 +4,7 @@ import (
|
||||||
"crypto/rand"
|
"crypto/rand"
|
||||||
"math/big"
|
"math/big"
|
||||||
"os"
|
"os"
|
||||||
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
@ -14,11 +15,27 @@ func RequireEnvString(name string, t *testing.T) string {
|
||||||
if strVal, isSet := os.LookupEnv(name); isSet {
|
if strVal, isSet := os.LookupEnv(name); isSet {
|
||||||
return strVal
|
return strVal
|
||||||
} else {
|
} else {
|
||||||
t.Errorf("Required environment variable %q not set", name)
|
t.Fatalf("Required environment variable %q not set", name)
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func RequireEnvInt(name string, t *testing.T) int {
|
||||||
|
if strVal, isSet := os.LookupEnv(name); isSet {
|
||||||
|
if intVal, err := strconv.Atoi(strVal); err != nil {
|
||||||
|
t.Fatalf("Invalid number value given for the environment variable %q: %s", name, strVal)
|
||||||
|
} else if intVal < 1 {
|
||||||
|
t.Fatalf("Environment variable %q has to be greater than 0", name)
|
||||||
|
} else {
|
||||||
|
return intVal
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
t.Fatalf("Required environment variable %q not set", name)
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
func DumpStruct(a ...interface{}) string {
|
func DumpStruct(a ...interface{}) string {
|
||||||
dump := spew.Sdump(a...)
|
dump := spew.Sdump(a...)
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue