Skip to content

Commit c126f70

Browse files
committed
add support for ADT , nested table type & incomplet object type
1 parent c5f8b8e commit c126f70

File tree

2 files changed

+172
-34
lines changed

2 files changed

+172
-34
lines changed

oracle/migrator.go

Lines changed: 57 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -485,19 +485,67 @@ func (m Migrator) DropConstraint(value interface{}, name string) error {
485485
}
486486

487487
// CreateType creates or replaces an Oracle user-defined type
488-
func (m Migrator) CreateType(typeName, typeKind, typeof string) error {
489-
if typeName == "" || typeKind == "" || typeof == "" {
490-
return fmt.Errorf("createType: both typeName and definition are required")
488+
func (m Migrator) CreateType(typeName string, args ...string) error {
489+
typeName = strings.TrimSpace(typeName)
490+
if typeName == "" {
491+
return fmt.Errorf("typeName is required")
492+
}
493+
var typeKind, typeOf string
494+
if len(args) > 0 {
495+
typeKind = args[0]
496+
}
497+
if len(args) > 1 {
498+
typeOf = args[1]
491499
}
492500

493-
sql := fmt.Sprintf(`CREATE OR REPLACE TYPE "%s" AS %s OF %s`, strings.ToLower(typeName), typeKind, typeof)
494-
return m.DB.Exec(sql).Error
501+
name := strings.ToLower(typeName)
502+
typeKind = strings.TrimSpace(typeKind)
503+
typeOf = strings.TrimSpace(typeOf)
504+
505+
// Incomplete object type
506+
if typeKind == "" && typeOf == "" {
507+
ddl := fmt.Sprintf(`CREATE TYPE "%s"`, name)
508+
return m.DB.Exec(ddl).Error
509+
}
510+
511+
k := strings.ToUpper(typeKind)
512+
var ddl string
513+
514+
switch {
515+
// Standalone varying array (varray) type and Standalone nested table type
516+
case strings.HasPrefix(k, "VARRAY") || strings.HasPrefix(k, "TABLE "):
517+
if typeOf == "" {
518+
return fmt.Errorf("typeof is required for collection types (VARRAY/TABLE)")
519+
}
520+
ddl = fmt.Sprintf(`CREATE OR REPLACE TYPE "%s" AS %s OF %s`, name, typeKind, typeOf)
521+
522+
// Abstract Data Type (ADT)
523+
case k == "OBJECT" || strings.HasPrefix(k, "OBJECT"):
524+
if typeOf == "" {
525+
return fmt.Errorf("attributes definition is required for OBJECT types")
526+
}
527+
attrs := typeOf
528+
if !strings.HasPrefix(attrs, "(") {
529+
attrs = "(" + attrs + ")"
530+
}
531+
ddl = fmt.Sprintf(`CREATE OR REPLACE TYPE "%s" AS OBJECT %s`, name, attrs)
532+
533+
default:
534+
// Invalid or unsupported types
535+
return fmt.Errorf("unsupported type kind %q (must be OBJECT, VARRAY, or TABLE)", typeKind)
536+
}
537+
538+
return m.DB.Exec(ddl).Error
495539
}
496540

497-
// DropType drops an Oracle user-defined type
541+
// DropType drops a user-defined type
498542
func (m Migrator) DropType(typeName string) error {
499-
sql := fmt.Sprintf(`DROP TYPE "%s" FORCE`, strings.ToLower(typeName))
500-
return m.DB.Exec(sql).Error
543+
typeName = strings.TrimSpace(typeName)
544+
if typeName == "" {
545+
return fmt.Errorf("dropType: typeName is required")
546+
}
547+
ddl := fmt.Sprintf(`DROP TYPE "%s" FORCE`, strings.ToLower(typeName))
548+
return m.DB.Exec(ddl).Error
501549
}
502550

503551
// HasType checks whether a user-defined type exists
@@ -507,7 +555,7 @@ func (m Migrator) HasType(typeName string) bool {
507555
}
508556

509557
var count int
510-
err := m.DB.Raw(`SELECT COUNT(*) FROM USER_TYPES WHERE TYPE_NAME = UPPER(?)`, typeName).Scan(&count).Error
558+
err := m.DB.Raw(`SELECT COUNT(*) FROM USER_TYPES WHERE UPPER(TYPE_NAME) = UPPER(?)`, typeName).Scan(&count).Error
511559
return err == nil && count > 0
512560
}
513561

tests/migrate_test.go

Lines changed: 115 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1976,42 +1976,51 @@ func TestOracleTypeCreateDrop(t *testing.T) {
19761976
t.Skip("Skipping Oracle type test: not running on Oracle")
19771977
}
19781978

1979-
const typeName = "email_list"
1980-
const tableName = "email_varray_tab"
1979+
const (
1980+
typeName = "email_list"
1981+
tableName = "email_varray_tab"
1982+
1983+
objectTypeName = "person_obj"
1984+
objectTableName = "person_obj_tab"
1985+
1986+
incompleteTypeName = "department_t"
1987+
unsupportedTypeName = "unsupported_type_t"
1988+
)
19811989

19821990
// Assert that DB.Migrator() is an oracle.Migrator (so we can use Oracle-specific methods)
19831991
m, ok := DB.Migrator().(oracle.Migrator)
19841992
if !ok {
19851993
t.Skip("Skipping: current dialect migrator is not Oracle-specific")
19861994
}
19871995

1988-
// Drop type if it exists
1989-
t.Run("drop_existing_type_if_any", func(t *testing.T) {
1990-
err := m.DropType(typeName)
1991-
if err != nil && !strings.Contains(err.Error(), "ORA-04043") {
1992-
t.Fatalf("Unexpected error dropping type: %v", err)
1996+
// Drop types if they exist
1997+
t.Run("drop_existing_types_if_any", func(t *testing.T) {
1998+
if err := m.DropType(typeName); err != nil && !strings.Contains(err.Error(), "ORA-04043") {
1999+
t.Fatalf("Unexpected error dropping type %s: %v", typeName, err)
2000+
}
2001+
if err := m.DropType(objectTypeName); err != nil && !strings.Contains(err.Error(), "ORA-04043") {
2002+
t.Fatalf("Unexpected error dropping type %s: %v", objectTypeName, err)
2003+
}
2004+
if err := m.DropType(incompleteTypeName); err != nil && !strings.Contains(err.Error(), "ORA-04043") {
2005+
t.Fatalf("Unexpected error dropping type %s: %v", incompleteTypeName, err)
19932006
}
19942007
})
19952008

19962009
// Create new VARRAY type
19972010
t.Run("create_varray_type", func(t *testing.T) {
19982011
err := m.CreateType(typeName, "VARRAY(10)", "VARCHAR2(60)")
19992012
if err != nil {
2000-
t.Fatalf("Failed to create Oracle type: %v", err)
2013+
t.Fatalf("Failed to create Oracle VARRAY type: %v", err)
20012014
}
20022015

2003-
// Verify it exists
2004-
var count int
2005-
if err := DB.Raw(`SELECT COUNT(*) FROM USER_TYPES WHERE TYPE_NAME = LOWER(?)`, typeName).Scan(&count).Error; err != nil {
2006-
t.Fatalf("Failed to verify created type: %v", err)
2007-
}
2008-
if count == 0 {
2009-
t.Fatalf("Expected Oracle type %s to exist", typeName)
2016+
// Verify it exists via HasType
2017+
if !m.HasType(typeName) {
2018+
t.Fatalf("Expected Oracle VARRAY type %s to exist", typeName)
20102019
}
20112020
})
20122021

2013-
// Create table using the custom type
2014-
t.Run("create_table_using_custom_type", func(t *testing.T) {
2022+
// Create table using the VARRAY type
2023+
t.Run("create_table_using_varray_type", func(t *testing.T) {
20152024
createTableSQL := fmt.Sprintf(`
20162025
CREATE TABLE "%s" (
20172026
"ID" NUMBER PRIMARY KEY,
@@ -2028,24 +2037,105 @@ func TestOracleTypeCreateDrop(t *testing.T) {
20282037
}
20292038
})
20302039

2031-
// Drop table and type
2032-
t.Run("drop_table_and_type", func(t *testing.T) {
2040+
// Create ADT (OBJECT) type
2041+
t.Run("create_object_type", func(t *testing.T) {
2042+
err := m.CreateType(objectTypeName, "OBJECT", `
2043+
first_name VARCHAR2(50),
2044+
last_name VARCHAR2(50),
2045+
age NUMBER
2046+
`)
2047+
if err != nil {
2048+
t.Fatalf("Failed to create Oracle OBJECT type: %v", err)
2049+
}
2050+
2051+
// Verify it exists via HasType
2052+
if !m.HasType(objectTypeName) {
2053+
t.Fatalf("Expected Oracle OBJECT type %s to exist", objectTypeName)
2054+
}
2055+
})
2056+
2057+
// Create table using the OBJECT type
2058+
t.Run("create_table_using_object_type", func(t *testing.T) {
2059+
createTableSQL := fmt.Sprintf(`
2060+
CREATE TABLE "%s" (
2061+
"ID" NUMBER PRIMARY KEY,
2062+
"PERSON" "%s"
2063+
)`, objectTableName, objectTypeName)
2064+
2065+
if err := DB.Exec(createTableSQL).Error; err != nil {
2066+
t.Fatalf("Failed to create table using object type %s: %v", objectTypeName, err)
2067+
}
2068+
2069+
// Verify table exists
2070+
if !m.HasTable(objectTableName) {
2071+
t.Fatalf("Expected table %s to exist", objectTableName)
2072+
}
2073+
})
2074+
2075+
// Create incomplete type (forward declaration)
2076+
t.Run("create_incomplete_type", func(t *testing.T) {
2077+
if err := m.CreateType(incompleteTypeName); err != nil {
2078+
t.Fatalf("Failed to create incomplete type %s: %v", incompleteTypeName, err)
2079+
}
2080+
if !m.HasType(incompleteTypeName) {
2081+
t.Fatalf("Expected incomplete type %s to exist", incompleteTypeName)
2082+
}
2083+
if err := m.DropType(incompleteTypeName); err != nil {
2084+
t.Fatalf("Failed to drop incomplete type %s: %v", incompleteTypeName, err)
2085+
}
2086+
if m.HasType(incompleteTypeName) {
2087+
t.Fatalf("Expected incomplete type %s to be dropped", incompleteTypeName)
2088+
}
2089+
})
2090+
2091+
// Unsupported type kinds should return an error and not create anything
2092+
t.Run("create_unsupported_type", func(t *testing.T) {
2093+
err := m.CreateType(unsupportedTypeName, "Unsupported", "Unsupported")
2094+
if err == nil {
2095+
t.Fatalf("Expected error when creating unsupported type %s, got nil", unsupportedTypeName)
2096+
}
2097+
2098+
// Ensure the type was NOT created
2099+
if m.HasType(unsupportedTypeName) {
2100+
t.Fatalf("Type %s should not exist after failed creation", unsupportedTypeName)
2101+
}
2102+
2103+
// Also ensure DropType is safe to call (idempotent)
2104+
if err := m.DropType(unsupportedTypeName); err != nil {
2105+
if !strings.Contains(strings.ToLower(err.Error()), "does not exist") {
2106+
t.Fatalf("Unexpected error dropping type %s: %v", unsupportedTypeName, err)
2107+
}
2108+
}
2109+
2110+
if m.HasType(unsupportedTypeName) {
2111+
t.Fatalf("Expected type %s to be absent after drop", unsupportedTypeName)
2112+
}
2113+
})
2114+
2115+
// Drop tables and types
2116+
t.Run("drop_tables_and_types", func(t *testing.T) {
2117+
if err := m.DropTable(objectTableName); err != nil {
2118+
t.Fatalf("Failed to drop table %s: %v", objectTableName, err)
2119+
}
20332120
if err := m.DropTable(tableName); err != nil {
20342121
t.Fatalf("Failed to drop table %s: %v", tableName, err)
20352122
}
20362123

2124+
// Drop types
2125+
if err := m.DropType(objectTypeName); err != nil {
2126+
t.Fatalf("Failed to drop type %s: %v", objectTypeName, err)
2127+
}
20372128
if err := m.DropType(typeName); err != nil {
20382129
t.Fatalf("Failed to drop type %s: %v", typeName, err)
20392130
}
20402131

2041-
// Verify type is gone
2042-
var count int
2043-
if err := DB.Raw(`SELECT COUNT(*) FROM USER_TYPES WHERE TYPE_NAME = LOWER(?)`, typeName).Scan(&count).Error; err != nil {
2044-
t.Fatalf("Failed to verify dropped type: %v", err)
2045-
}
2046-
if count > 0 {
2132+
// Verify types are gone via HasType
2133+
if m.HasType(typeName) {
20472134
t.Fatalf("Expected Oracle type %s to be dropped", typeName)
20482135
}
2136+
if m.HasType(objectTypeName) {
2137+
t.Fatalf("Expected Oracle type %s to be dropped", objectTypeName)
2138+
}
20492139
})
20502140
}
20512141

0 commit comments

Comments
 (0)