@@ -11,10 +11,8 @@ import (
11
11
)
12
12
13
13
type RowMissingDependency struct {
14
- Row Row
15
- ToTable string
16
- ToColumn string
17
- UniqueValue string
14
+ Row Row
15
+ ConstraintMapKey [3 ]string
18
16
}
19
17
20
18
// Exports all rows in the database to a ripoff file.
@@ -35,25 +33,12 @@ func ExportToRipoff(ctx context.Context, tx pgx.Tx) (RipoffFile, error) {
35
33
}
36
34
// A map from [table,column] -> ForeignKey for single column foreign keys.
37
35
singleColumnFkeyMap := map [[2 ]string ]* ForeignKey {}
38
- // A map from [table,column] -> a map of column values to row keys (ex: users:literal(1)) of the given table.
39
- uniqueConstraintMap := map [[2 ]string ]map [string ]string {}
40
- // A map from table to a list of columns that need mapped in uniqueConstraintMap.
41
- hasUniqueConstraintMap := map [string ][]string {}
36
+ // A map from [table,constraintName,values] -> rowKey.
37
+ constraintMap := map [[3 ]string ]string {}
42
38
for table , tableInfo := range foreignKeyResult {
43
39
for _ , foreignKey := range tableInfo .ForeignKeys {
44
- // We could possibly maintain a uniqueConstraintMap map for these as well, but tabling for now.
45
- if len (foreignKey .ColumnConditions ) != 1 {
46
- continue
47
- }
48
- singleColumnFkeyMap [[2 ]string {table , foreignKey .ColumnConditions [0 ][0 ]}] = foreignKey
49
- // This is a foreign key to a unique index, not a primary key.
50
- if len (primaryKeyResult [foreignKey .ToTable ]) == 1 && primaryKeyResult [foreignKey .ToTable ][0 ] != foreignKey .ColumnConditions [0 ][1 ] {
51
- _ , ok := hasUniqueConstraintMap [foreignKey .ToTable ]
52
- if ! ok {
53
- hasUniqueConstraintMap [foreignKey .ToTable ] = []string {}
54
- }
55
- uniqueConstraintMap [[2 ]string {foreignKey .ToTable , foreignKey .ColumnConditions [0 ][1 ]}] = map [string ]string {}
56
- hasUniqueConstraintMap [foreignKey .ToTable ] = append (hasUniqueConstraintMap [foreignKey .ToTable ], foreignKey .ColumnConditions [0 ][1 ])
40
+ if len (foreignKey .ColumnConditions ) == 1 {
41
+ singleColumnFkeyMap [[2 ]string {table , foreignKey .ColumnConditions [0 ][0 ]}] = foreignKey
57
42
}
58
43
}
59
44
}
@@ -89,6 +74,8 @@ func ExportToRipoff(ctx context.Context, tx pgx.Tx) (RipoffFile, error) {
89
74
}
90
75
}
91
76
ripoffRow := Row {}
77
+ // A map of fieldName -> tableName to convert values to literal:(...)
78
+ literalFields := map [string ]string {}
92
79
ids := []string {}
93
80
for i , field := range fields {
94
81
// Null columns are still exported since we don't know if there is a default or not (at least not at time of writing).
@@ -114,51 +101,72 @@ func ExportToRipoff(ctx context.Context, tx pgx.Tx) (RipoffFile, error) {
114
101
}
115
102
continue
116
103
}
117
- // If this is a foreign key, should ensure it uses the table:valueFunc() format.
118
- if isFkey && columnVal != "" {
119
- // Does the referenced table have more than one primary key, or does the constraint not point to a primary key?
120
- // Then is a foreign key to a non-primary key, we need to fill this info in later.
121
- if len (primaryKeyResult [foreignKey .ToTable ]) != 1 || primaryKeyResult [foreignKey .ToTable ][0 ] != foreignKey .ColumnConditions [0 ][1 ] {
122
- missingDependencies = append (missingDependencies , RowMissingDependency {
123
- Row : ripoffRow ,
124
- UniqueValue : columnVal ,
125
- ToTable : foreignKey .ToTable ,
126
- ToColumn : foreignKey .ColumnConditions [0 ][1 ],
127
- })
128
- } else {
129
- ripoffRow [field .Name ] = fmt .Sprintf ("%s:literal(%s)" , foreignKey .ToTable , columnVal )
130
- continue
131
- }
104
+ // If this is a foreign key to a single-column primary key, we can use literal() instead of ~dependencies.
105
+ if isFkey && columnVal != "" && len (primaryKeyResult [foreignKey .ToTable ]) == 1 && primaryKeyResult [foreignKey .ToTable ][0 ] == foreignKey .ColumnConditions [0 ][1 ] {
106
+ literalFields [field .Name ] = foreignKey .ToTable
132
107
}
133
108
// Normal column.
134
109
ripoffRow [field .Name ] = columnVal
135
110
}
136
111
rowKey := fmt .Sprintf ("%s:literal(%s)" , table , strings .Join (ids , "." ))
137
- // For foreign keys to non-unique fields, we need to maintain our own map of unique values to rowKeys.
138
- columnsThatNeepMapped , needsMapped := hasUniqueConstraintMap [table ]
139
- if needsMapped {
140
- for i , field := range fields {
141
- if columns [i ] == nil {
112
+ // Hash values of this row for dependency lookups in the future.
113
+ for _ , fkeys := range foreignKeyResult {
114
+ for constraintName , fkey := range fkeys .ForeignKeys {
115
+ if fkey .ToTable != table {
142
116
continue
143
117
}
144
- columnVal := * columns [i ]
145
- if slices .Contains (columnsThatNeepMapped , field .Name ) {
146
- uniqueConstraintMap [[2 ]string {table , field .Name }][columnVal ] = rowKey
118
+ values := []string {}
119
+ abort := false
120
+ for _ , conditions := range fkey .ColumnConditions {
121
+ toColumnValue , hasToColumn := ripoffRow [conditions [1 ]]
122
+ if hasToColumn && toColumnValue .(string ) != "" {
123
+ values = append (values , toColumnValue .(string ))
124
+ } else {
125
+ abort = true
126
+ break
127
+ }
128
+ }
129
+ if abort {
130
+ continue
147
131
}
132
+ constraintMap [[3 ]string {table , constraintName , strings .Join (values , "," )}] = rowKey
148
133
}
149
134
}
135
+ // Now register missing dependencies for all our foreign keys.
136
+ for constraintName , fkey := range foreignKeyResult [table ].ForeignKeys {
137
+ values := []string {}
138
+ allLiteral := true
139
+ for _ , condition := range fkey .ColumnConditions {
140
+ fieldValue , hasField := ripoffRow [condition [0 ]]
141
+ fieldValueStr , isString := fieldValue .(string )
142
+ if hasField && isString && fieldValue != "" {
143
+ _ , isLiteral := literalFields [condition [0 ]]
144
+ if ! isLiteral {
145
+ allLiteral = false
146
+ }
147
+ values = append (values , fieldValueStr )
148
+ }
149
+ }
150
+ // We have enough values to satisfy the column conditions.
151
+ if ! allLiteral && len (values ) == len (fkey .ColumnConditions ) {
152
+ missingDependencies = append (missingDependencies , RowMissingDependency {
153
+ Row : ripoffRow ,
154
+ ConstraintMapKey : [3 ]string {fkey .ToTable , constraintName , strings .Join (values , "," )},
155
+ })
156
+ }
157
+ }
158
+ // Finally convert some fields to use literal() for UX reasons.
159
+ for fieldName , toTable := range literalFields {
160
+ ripoffRow [fieldName ] = fmt .Sprintf ("%s:literal(%s)" , toTable , ripoffRow [fieldName ])
161
+ }
150
162
ripoffFile .Rows [rowKey ] = ripoffRow
151
163
}
152
164
}
153
165
// Resolve missing dependencies now that all rows are in memory.
154
166
for _ , missingDependency := range missingDependencies {
155
- valueMap , ok := uniqueConstraintMap [[2 ]string {missingDependency .ToTable , missingDependency .ToColumn }]
156
- if ! ok {
157
- return ripoffFile , fmt .Errorf ("row has dependency on column %s.%s which is not mapped" , missingDependency .ToTable , missingDependency .ToColumn )
158
- }
159
- rowKey , ok := valueMap [missingDependency .UniqueValue ]
167
+ rowKey , ok := constraintMap [missingDependency .ConstraintMapKey ]
160
168
if ! ok {
161
- return ripoffFile , fmt .Errorf ("row has dependency on column %s.%s which does not contain unqiue value %s" , missingDependency .ToTable , missingDependency . ToColumn , missingDependency . UniqueValue )
169
+ return ripoffFile , fmt .Errorf ("row has missing dependency on constraint map key %s" , missingDependency .ConstraintMapKey )
162
170
}
163
171
dependencies , ok := missingDependency .Row ["~dependencies" ].([]string )
164
172
if ! ok {
0 commit comments