@@ -1033,22 +1033,29 @@ func (og *operationGenerator) violatesFkConstraints(
1033
1033
tableName * tree.TableName ,
1034
1034
nonGeneratedColNames []tree.Name ,
1035
1035
rows [][]string ,
1036
- ) (bool , error ) {
1036
+ ) (expectedViolation , potentialViolation bool , err error ) {
1037
1037
// TODO(annie): readd the join on active constraints once #120702 is resolved.
1038
1038
//
1039
1039
// N.B. We add random noise to column names that makes it hard to just directly call on these names. This is
1040
1040
// not the case with table/schema names; thus, only column names are quote_ident'ed to ensure that they get
1041
1041
// referenced properly.
1042
1042
fkConstraints , err := og .scanStringArrayRows (ctx , tx , fmt .Sprintf (`
1043
- SELECT array[parent.table_schema, parent.table_name, parent.column_name, child.column_name]
1043
+ SELECT array[
1044
+ parent.table_schema,
1045
+ parent.table_name,
1046
+ parent.column_name,
1047
+ parent.is_generated,
1048
+ child.column_name,
1049
+ child.is_generated
1050
+ ]
1044
1051
FROM (
1045
1052
SELECT conname, conkey, confkey, conrelid, confrelid
1046
1053
FROM pg_constraint
1047
1054
WHERE contype = 'f'
1048
1055
AND conrelid = '%s'::REGCLASS::INT8
1049
1056
) AS con
1050
1057
JOIN (
1051
- SELECT column_name, ordinal_position, column_default
1058
+ SELECT column_name, ordinal_position, column_default, is_generated
1052
1059
FROM information_schema.columns
1053
1060
WHERE table_schema = '%s'
1054
1061
AND table_name = '%s'
@@ -1058,7 +1065,8 @@ func (og *operationGenerator) violatesFkConstraints(
1058
1065
cols.table_schema,
1059
1066
cols.table_name,
1060
1067
cols.column_name,
1061
- cols.ordinal_position
1068
+ cols.ordinal_position,
1069
+ cols.is_generated
1062
1070
FROM pg_class AS pc
1063
1071
JOIN pg_namespace AS pn ON pc.relnamespace = pn.oid
1064
1072
JOIN information_schema.columns AS cols ON (pc.relname = cols.table_name AND pn.nspname = cols.table_schema)
@@ -1069,59 +1077,71 @@ func (og *operationGenerator) violatesFkConstraints(
1069
1077
WHERE child.column_name != 'rowid';
1070
1078
` , tableName .String (), tableName .Schema (), tableName .Object ()))
1071
1079
if err != nil {
1072
- return false , og .checkAndAdjustForUnknownSchemaErrors (err )
1080
+ return false , false , og .checkAndAdjustForUnknownSchemaErrors (err )
1073
1081
}
1074
1082
1075
- // Maps a column name to its index. This way, the value of a column in a row can be looked up
1076
- // using row[colToIndexMap["columnName"]] = "valueForColumn"
1077
- columnNameToIndexMap := map [tree.Name ]int {}
1083
+ // Maps a non-generated column name to its index. This way, the value of a
1084
+ // column in a row can be looked up using row[colToIndexMap["columnName"]] = "valueForColumn"
1085
+ nonGeneratedColumnNameToIndexMap := map [tree.Name ]int {}
1078
1086
1079
1087
for i , name := range nonGeneratedColNames {
1080
- columnNameToIndexMap [name ] = i
1088
+ nonGeneratedColumnNameToIndexMap [name ] = i
1081
1089
}
1082
1090
1083
1091
for _ , row := range rows {
1084
1092
for _ , constraint := range fkConstraints {
1085
1093
parentTableSchema := tree .Name (constraint [0 ])
1086
1094
parentTableName := tree .Name (constraint [1 ])
1087
1095
parentColumnName := tree .Name (constraint [2 ])
1088
- childColumnName := tree .Name (constraint [3 ])
1096
+ parentIsGenerated := constraint [3 ] == "ALWAYS"
1097
+ childColumnName := tree .Name (constraint [4 ])
1098
+ childIsGenerated := constraint [5 ] == "ALWAYS"
1089
1099
1090
1100
// If self referential, there cannot be a violation.
1091
1101
parentAndChildAreSame := parentTableSchema == tableName .SchemaName && parentTableName == tableName .ObjectName
1092
1102
if parentAndChildAreSame && parentColumnName == childColumnName {
1093
1103
continue
1094
1104
}
1095
1105
1106
+ // If the foreign key involves any generated columns, skip detailed FK checking
1107
+ // and conservatively assume a violation may occur. This avoids the complexity
1108
+ // of reasoning about values that are automatically computed by the database.
1109
+ if parentIsGenerated || childIsGenerated {
1110
+ return false , true , nil
1111
+ }
1112
+
1096
1113
violation , err := og .violatesFkConstraintsHelper (
1097
- ctx , tx , columnNameToIndexMap , parentTableSchema , parentTableName , parentColumnName , childColumnName , tableName , parentAndChildAreSame , row , rows ,
1114
+ ctx , tx , nonGeneratedColumnNameToIndexMap , parentTableSchema , parentTableName , parentColumnName , childColumnName , tableName , parentAndChildAreSame , row , rows ,
1098
1115
)
1099
1116
if err != nil {
1100
- return false , err
1117
+ return false , false , err
1101
1118
}
1102
1119
if violation {
1103
- return true , nil
1120
+ return true , false , nil
1104
1121
}
1105
1122
}
1106
1123
}
1107
1124
1108
- return false , nil
1125
+ return false , false , nil
1109
1126
}
1110
1127
1111
- // violatesFkConstraintsHelper checks if a single row will violate a foreign key constraint
1112
- // between the childColumn and parentColumn.
1128
+ // violatesFkConstraintsHelper checks if inserting a single row would violate a
1129
+ // foreign key constraint between the given child and parent columns.
1130
+ //
1131
+ // This function assumes that neither the parent nor child columns are generated.
1132
+ // The caller must ensure this; generated columns are not supported.
1113
1133
func (og * operationGenerator ) violatesFkConstraintsHelper (
1114
1134
ctx context.Context ,
1115
1135
tx pgx.Tx ,
1116
- columnNameToIndexMap map [tree.Name ]int ,
1136
+ nonGeneratedColumnNameToIndexMap map [tree.Name ]int ,
1117
1137
parentTableSchema , parentTableName , parentColumn , childColumn tree.Name ,
1118
1138
childTableName * tree.TableName ,
1119
1139
parentAndChildAreSameTable bool ,
1120
1140
rowToInsert []string ,
1121
1141
allRows [][]string ,
1122
1142
) (bool , error ) {
1123
1143
1124
- childIndex , ok := columnNameToIndexMap [childColumn ]
1144
+ childIndex , ok := nonGeneratedColumnNameToIndexMap [childColumn ]
1125
1145
if ! ok {
1126
1146
return false , errors .Newf ("child column %s does not exist in table %s" , childColumn , childTableName )
1127
1147
}
@@ -1134,41 +1154,12 @@ func (og *operationGenerator) violatesFkConstraintsHelper(
1134
1154
// insert may satisfy the same constraint.
1135
1155
var parentAndChildSameQueryColumns []string
1136
1156
if parentAndChildAreSameTable {
1137
- colsInfo , err := og .getTableColumns (ctx , tx , childTableName , false )
1138
- if err != nil {
1139
- return false , err
1140
- }
1141
-
1142
- var parentColInfo * column
1143
- for _ , colInfo := range colsInfo {
1144
- if colInfo .name == parentColumn {
1145
- parentColInfo = & colInfo
1146
- break
1147
- }
1148
- }
1149
- if parentColInfo == nil {
1150
- return false , errors .Newf ("column %s not found in columns for %s" , parentColumn , childTableName )
1151
- }
1152
-
1153
1157
for _ , otherRow := range allRows {
1154
- var parentValueInSameInsert string
1155
- if parentColInfo .generated {
1156
- // If the parent column is a computed column, spend time to generate the value.
1157
- columnsToValues := map [tree.Name ]string {}
1158
- for name , idx := range columnNameToIndexMap {
1159
- columnsToValues [name ] = rowToInsert [idx ]
1160
- }
1161
- parentValueInSameInsert , err = og .generateColumn (ctx , tx , * parentColInfo , columnsToValues )
1162
- if err != nil {
1163
- return false , err
1164
- }
1165
- } else {
1166
- parentIdx , ok := columnNameToIndexMap [parentColumn ]
1167
- if ! ok {
1168
- return false , errors .Newf ("parent column %s does not exist in table %s" , parentColumn , childTableName )
1169
- }
1170
- parentValueInSameInsert = otherRow [parentIdx ]
1158
+ parentIdx , ok := nonGeneratedColumnNameToIndexMap [parentColumn ]
1159
+ if ! ok {
1160
+ return false , errors .Newf ("parent column %s does not exist in table %s" , parentColumn , childTableName )
1171
1161
}
1162
+ parentValueInSameInsert := otherRow [parentIdx ]
1172
1163
1173
1164
// Skip over NULL values.
1174
1165
if parentValueInSameInsert == "NULL" {
0 commit comments