go-ddl-parser/oracle.go

229 lines
5.9 KiB
Go

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
}
}