Skip to content
This repository was archived by the owner on Aug 30, 2025. It is now read-only.

Commit 932fd20

Browse files
author
alishakawaguchi
authored
Fixes columns being created in wrong order during schema reconciliation (#3459)
1 parent 6f16a61 commit 932fd20

File tree

3 files changed

+46
-136
lines changed

3 files changed

+46
-136
lines changed

internal/integration-tests/worker/workflow/mysql_test.go

Lines changed: 7 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -706,17 +706,7 @@ func test_mysql_schema_reconciliation_compare_schemas(
706706
require.NoError(t, err, "failed to get source triggers")
707707
destTriggers, err := destManager.GetSchemaTableTriggers(ctx, schematables)
708708
require.NoError(t, err, "failed to get destination triggers")
709-
710-
require.Len(t, srcTriggers, len(destTriggers), "source and destination have different number of triggers")
711-
destTriggersMap := make(map[string]*sqlmanager_shared.TableTrigger)
712-
for _, trigger := range destTriggers {
713-
destTriggersMap[trigger.Fingerprint] = trigger
714-
}
715-
for _, trigger := range srcTriggers {
716-
destTrigger, ok := destTriggersMap[trigger.Fingerprint]
717-
require.True(t, ok, "destination missing trigger with fingerprint %s", trigger.Fingerprint)
718-
require.Equal(t, trigger.Definition, destTrigger.Definition, "trigger definitions do not match for fingerprint %s", trigger.Fingerprint)
719-
}
709+
assert_fingerprints_match_in_source_and_target(t, srcTriggers, destTriggers, "triggers")
720710

721711
t.Logf("checking table constraints are the same in source and destination")
722712
srcConstraints, err := srcManager.GetTableConstraintsByTables(ctx, schema, tables)
@@ -733,57 +723,23 @@ func test_mysql_schema_reconciliation_compare_schemas(
733723
require.Equal(t, srcfk, destfk, "foreign key constraints do not match for table %s", table)
734724
require.Equal(t, srcNonFk, destNonFk, "non-foreign key constraints do not match for table %s", table)
735725

736-
for _, fk := range srcfk {
737-
require.Contains(t, destfk, fk, "destination missing foreign key constraint in table %s", table)
738-
}
739-
for _, fk := range destfk {
740-
require.Contains(t, srcfk, fk, "source missing foreign key constraint in table %s", table)
741-
}
742-
743-
for _, nonFk := range srcNonFk {
744-
require.Contains(t, destNonFk, nonFk, "destination missing non-foreign key constraint in table %s", table)
745-
}
746-
for _, nonFk := range destNonFk {
747-
require.Contains(t, srcNonFk, nonFk, "source missing non-foreign key constraint in table %s", table)
748-
}
726+
assert_fingerprints_match_in_source_and_target(t, srcfk, destfk, "foreign key constraints")
727+
assert_fingerprints_match_in_source_and_target(t, srcNonFk, destNonFk, "non-foreign key constraints")
749728
}
750729

751730
t.Logf("checking functions are the same in source and destination")
752-
srcFunctions, err := srcManager.GetSchemaTableDataTypes(ctx, schematables)
731+
srcDatatypes, err := srcManager.GetSchemaTableDataTypes(ctx, schematables)
753732
require.NoError(t, err, "failed to get source functions")
754-
destFunctions, err := destManager.GetSchemaTableDataTypes(ctx, schematables)
733+
destDatatypes, err := destManager.GetSchemaTableDataTypes(ctx, schematables)
755734
require.NoError(t, err, "failed to get destination functions")
756-
757-
require.Len(t, srcFunctions.Functions, len(destFunctions.Functions), "source and destination have different number of functions")
758-
for _, function := range srcFunctions.Functions {
759-
require.Contains(t, destFunctions.Functions, function, "destination missing function with fingerprint %s", function.Fingerprint)
760-
}
761-
for _, function := range destFunctions.Functions {
762-
require.Contains(t, srcFunctions.Functions, function, "source missing function with fingerprint %s", function.Fingerprint)
763-
}
735+
assert_fingerprints_match_in_source_and_target(t, srcDatatypes.Functions, destDatatypes.Functions, "functions")
764736

765737
t.Logf("checking columns are the same in source and destination")
766738
srcColumns, err := srcManager.GetColumnsByTables(ctx, schematables)
767739
require.NoError(t, err, "failed to get source columns")
768740
destColumns, err := destManager.GetColumnsByTables(ctx, schematables)
769741
require.NoError(t, err, "failed to get destination columns")
770-
771-
srcColsMap := map[string]*sqlmanager_shared.TableColumn{}
772-
for _, column := range srcColumns {
773-
srcColsMap[column.Fingerprint] = column
774-
}
775-
destColsMap := map[string]*sqlmanager_shared.TableColumn{}
776-
for _, column := range destColumns {
777-
destColsMap[column.Fingerprint] = column
778-
}
779-
780-
require.Len(t, srcColumns, len(destColumns), "source and destination have different number of columns")
781-
for _, column := range srcColumns {
782-
require.Contains(t, destColsMap, column.Fingerprint, "destination missing column with fingerprint %s", column.Fingerprint)
783-
}
784-
for _, column := range destColumns {
785-
require.Contains(t, srcColsMap, column.Fingerprint, "source missing column with fingerprint %s", column.Fingerprint)
786-
}
742+
assert_fingerprints_match_in_source_and_target(t, srcColumns, destColumns, "columns")
787743
}
788744

789745
func test_mysql_schema_reconciliation_column_values(

internal/integration-tests/worker/workflow/postgres_test.go

Lines changed: 24 additions & 85 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import (
1515
sqlmanager_postgres "github.com/nucleuscloud/neosync/backend/pkg/sqlmanager/postgres"
1616
sqlmanager_shared "github.com/nucleuscloud/neosync/backend/pkg/sqlmanager/shared"
1717
"github.com/nucleuscloud/neosync/internal/gotypeutil"
18+
schemamanager_shared "github.com/nucleuscloud/neosync/internal/schema-manager/shared"
1819
tcpostgres "github.com/nucleuscloud/neosync/internal/testutil/testcontainers/postgres"
1920
tcredis "github.com/nucleuscloud/neosync/internal/testutil/testcontainers/redis"
2021
testutil_testdata "github.com/nucleuscloud/neosync/internal/testutil/testdata"
@@ -1525,30 +1526,7 @@ func verify_postgres_schemas(
15251526
require.NoError(t, err, "failed to get source columns")
15261527
destColumns, err := destManager.GetColumnsByTables(ctx, schematables)
15271528
require.NoError(t, err, "failed to get destination columns")
1528-
1529-
srcColumnsMap := make(map[string]*sqlmanager_shared.TableColumn)
1530-
for _, column := range srcColumns {
1531-
key := fmt.Sprintf("%s.%s.%s", column.Schema, column.Table, column.Name)
1532-
srcColumnsMap[key] = column
1533-
}
1534-
1535-
destColumnsMap := make(map[string]*sqlmanager_shared.TableColumn)
1536-
for _, column := range destColumns {
1537-
key := fmt.Sprintf("%s.%s.%s", column.Schema, column.Table, column.Name)
1538-
destColumnsMap[key] = column
1539-
}
1540-
1541-
for _, column := range srcColumns {
1542-
srcKey := fmt.Sprintf("%s.%s.%s", column.Schema, column.Table, column.Name)
1543-
destColumn, exists := destColumnsMap[srcKey]
1544-
require.True(t, exists, "source column %s not found in destination", srcKey)
1545-
verify_postgres_column_spec(t, column, destColumn)
1546-
}
1547-
for _, column := range destColumns {
1548-
destKey := fmt.Sprintf("%s.%s.%s", column.Schema, column.Table, column.Name)
1549-
_, exists := srcColumnsMap[destKey]
1550-
require.True(t, exists, "destination column %s not found in source", destKey)
1551-
}
1529+
assert_fingerprints_match_in_source_and_target(t, srcColumns, destColumns, "columns")
15521530

15531531
t.Logf("checking table constraints are the same in source and destination")
15541532
srcConstraints, err := srcManager.GetTableConstraintsByTables(ctx, schema, tables)
@@ -1565,88 +1543,49 @@ func verify_postgres_schemas(
15651543
require.Equal(t, srcfk, destfk, "foreign key constraints do not match for table %s", table)
15661544
require.Equal(t, srcNonFk, destNonFk, "non-foreign key constraints do not match for table %s", table)
15671545

1568-
for _, fk := range srcfk {
1569-
require.Contains(t, destfk, fk, "destination missing foreign key constraint in table %s", table)
1570-
}
1571-
for _, fk := range destfk {
1572-
require.Contains(t, srcfk, fk, "source missing foreign key constraint in table %s", table)
1573-
}
1574-
1575-
for _, nonFk := range srcNonFk {
1576-
require.Contains(t, destNonFk, nonFk, "destination missing non-foreign key constraint in table %s", table)
1577-
}
1578-
for _, nonFk := range destNonFk {
1579-
require.Contains(t, srcNonFk, nonFk, "source missing non-foreign key constraint in table %s", table)
1580-
}
1546+
assert_fingerprints_match_in_source_and_target(t, srcfk, destfk, "foreign key constraints")
1547+
assert_fingerprints_match_in_source_and_target(t, srcNonFk, destNonFk, "non-foreign key constraints")
15811548
}
15821549

15831550
t.Logf("checking triggers are the same in source and destination")
15841551
srcTriggers, err := srcManager.GetSchemaTableTriggers(ctx, schematables)
15851552
require.NoError(t, err, "failed to get source triggers")
15861553
destTriggers, err := destManager.GetSchemaTableTriggers(ctx, schematables)
15871554
require.NoError(t, err, "failed to get destination triggers")
1588-
1589-
destTriggersMap := make(map[string]*sqlmanager_shared.TableTrigger)
1590-
for _, trigger := range destTriggers {
1591-
destTriggersMap[trigger.Fingerprint] = trigger
1592-
}
1593-
for _, trigger := range srcTriggers {
1594-
destTrigger, ok := destTriggersMap[trigger.Fingerprint]
1595-
require.True(t, ok, "destination missing trigger with fingerprint %s", trigger.Fingerprint)
1596-
require.Equal(t, trigger.Definition, destTrigger.Definition, "trigger definitions do not match for fingerprint %s", trigger.Fingerprint)
1597-
}
1555+
assert_fingerprints_match_in_source_and_target(t, srcTriggers, destTriggers, "triggers")
15981556

15991557
srcDatatypes, err := srcManager.GetDataTypesByTables(ctx, schematables)
16001558
require.NoError(t, err, "failed to get source datatypes")
16011559
destDatatypes, err := destManager.GetDataTypesByTables(ctx, schematables)
16021560
require.NoError(t, err, "failed to get destination datatypes")
16031561

16041562
t.Logf("checking functions are the same in source and destination")
1605-
for _, function := range srcDatatypes.Functions {
1606-
assert.Contains(t, destDatatypes.Functions, function, "destination missing function with fingerprint %s", function.Fingerprint)
1607-
}
1608-
for _, function := range destDatatypes.Functions {
1609-
assert.Contains(t, srcDatatypes.Functions, function, "source missing function with fingerprint %s", function.Fingerprint)
1610-
}
1563+
assert_fingerprints_match_in_source_and_target(t, srcDatatypes.Functions, destDatatypes.Functions, "functions")
16111564

16121565
t.Logf("checking enum are the same in source and destination")
1613-
for _, enum := range srcDatatypes.Enums {
1614-
assert.Contains(t, destDatatypes.Enums, enum, "destination missing enum with fingerprint %s", enum.Fingerprint)
1615-
}
1616-
for _, enum := range destDatatypes.Enums {
1617-
assert.Contains(t, srcDatatypes.Enums, enum, "source missing enum with fingerprint %s", enum.Fingerprint)
1618-
}
1566+
assert_fingerprints_match_in_source_and_target(t, srcDatatypes.Enums, destDatatypes.Enums, "enums")
16191567

16201568
t.Logf("checking composite types are the same in source and destination")
1621-
for _, composite := range srcDatatypes.Composites {
1622-
assert.Contains(t, destDatatypes.Composites, composite, "destination missing composite with fingerprint %s", composite.Fingerprint)
1623-
}
1624-
for _, composite := range destDatatypes.Composites {
1625-
assert.Contains(t, srcDatatypes.Composites, composite, "source missing composite with fingerprint %s", composite.Fingerprint)
1626-
}
1569+
assert_fingerprints_match_in_source_and_target(t, srcDatatypes.Composites, destDatatypes.Composites, "composites")
16271570

16281571
t.Logf("checking domains are the same in source and destination")
1629-
for _, domain := range srcDatatypes.Domains {
1630-
assert.Contains(t, destDatatypes.Domains, domain, "destination missing domain with fingerprint %s", domain.Fingerprint)
1572+
assert_fingerprints_match_in_source_and_target(t, srcDatatypes.Domains, destDatatypes.Domains, "domains")
1573+
}
1574+
1575+
func assert_fingerprints_match_in_source_and_target[T schemamanager_shared.FingerprintedType](t *testing.T, source, target []T, label string) {
1576+
sourceMap := map[string]T{}
1577+
for _, item := range source {
1578+
sourceMap[item.GetFingerprint()] = item
16311579
}
1632-
for _, domain := range destDatatypes.Domains {
1633-
assert.Contains(t, srcDatatypes.Domains, domain, "source missing domain with fingerprint %s", domain.Fingerprint)
1580+
targetMap := map[string]T{}
1581+
for _, item := range target {
1582+
targetMap[item.GetFingerprint()] = item
16341583
}
1635-
}
16361584

1637-
func verify_postgres_column_spec(
1638-
t *testing.T,
1639-
source, target *sqlmanager_shared.TableColumn,
1640-
) {
1641-
columnName := fmt.Sprintf("%s.%s.%s", source.Schema, source.Table, source.Name)
1642-
assert.Equal(t, source.Name, target.Name, fmt.Sprintf("column names do not match for column %s", columnName))
1643-
assert.Equal(t, source.Comment, target.Comment, fmt.Sprintf("column comments do not match for column %s", columnName))
1644-
assert.Equal(t, source.DataType, target.DataType, fmt.Sprintf("column data types do not match for column %s", columnName))
1645-
assert.Equal(t, source.IsNullable, target.IsNullable, fmt.Sprintf("column nullability does not match for column %s", columnName))
1646-
assert.Equal(t, source.IdentityGeneration, target.IdentityGeneration, fmt.Sprintf("column identity generation does not match for column %s", columnName))
1647-
assert.Equal(t, source.GeneratedType, target.GeneratedType, fmt.Sprintf("column generated types do not match for column %s", columnName))
1648-
assert.Equal(t, source.GeneratedExpression, target.GeneratedExpression, fmt.Sprintf("column generated expressions do not match for column %s", columnName))
1649-
assert.Equal(t, source.ColumnDefaultType, target.ColumnDefaultType, fmt.Sprintf("column default types do not match for column %s", columnName))
1650-
assert.Equal(t, source.SequenceDefinition, target.SequenceDefinition, fmt.Sprintf("column sequence definitions do not match for column %s", columnName))
1651-
assert.Equal(t, source.ColumnDefault, target.ColumnDefault, fmt.Sprintf("column default values do not match for column %s", columnName))
1585+
for fingerprint, item := range sourceMap {
1586+
assert.Contains(t, targetMap, fingerprint, "%s fingerprint %s not found in target: %v", label, fingerprint, item)
1587+
}
1588+
for fingerprint, item := range targetMap {
1589+
assert.Contains(t, sourceMap, fingerprint, "%s fingerprint %s not found in source: %v", label, fingerprint, item)
1590+
}
16521591
}

internal/schema-manager/shared/schema-diff.go

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
package schemamanager_shared
22

33
import (
4+
"sort"
5+
46
sqlmanager_shared "github.com/nucleuscloud/neosync/backend/pkg/sqlmanager/shared"
57
)
68

@@ -216,6 +218,19 @@ func (b *SchemaDifferencesBuilder) buildTableColumnDifferences() {
216218
}
217219
}
218220
}
221+
// important to order columns by ordinal position to ensure columns are created in the correct order in destination
222+
b.diff.OrderColumnsByOrdinalPosition()
223+
}
224+
225+
// OrderColumnsByOrdinalPosition orders the columns in ExistsInSource by their ordinal position in ascending order
226+
func (d *SchemaDifferences) OrderColumnsByOrdinalPosition() {
227+
if d.ExistsInSource == nil || len(d.ExistsInSource.Columns) == 0 {
228+
return
229+
}
230+
231+
sort.SliceStable(d.ExistsInSource.Columns, func(i, j int) bool {
232+
return d.ExistsInSource.Columns[i].OrdinalPosition < d.ExistsInSource.Columns[j].OrdinalPosition
233+
})
219234
}
220235

221236
func buildColumnDiff(srcColumn, destColumn *sqlmanager_shared.TableColumn) *ColumnDiff {

0 commit comments

Comments
 (0)