in sources/spanner/infoschema.go [306:380]
func (isi InfoSchemaImpl) GetForeignKeys(conv *internal.Conv, table common.SchemaAndName) (foreignKeys []schema.ForeignKey, err error) {
q := `SELECT k.constraint_name, k.column_name, c.table_name, c.column_name
FROM information_schema.key_column_usage AS k
JOIN information_schema.constraint_column_usage AS c ON k.constraint_name = c.constraint_name
JOIN information_schema.table_constraints AS t ON k.constraint_name = t.constraint_name
WHERE t.constraint_type='FOREIGN KEY' AND t.table_schema = '' AND t.table_name = @p1
ORDER BY k.constraint_name, k.ordinal_position;`
if isi.SpDialect == constants.DIALECT_POSTGRESQL {
q = `SELECT k.constraint_name, k.column_name, c.table_name, c.column_name
FROM information_schema.key_column_usage AS k
JOIN information_schema.constraint_column_usage AS c ON k.constraint_name = c.constraint_name
JOIN information_schema.table_constraints AS t ON k.constraint_name = t.constraint_name
WHERE t.constraint_type='FOREIGN KEY' AND t.table_schema = 'public' AND t.table_name = $1
ORDER BY k.constraint_name, k.ordinal_position;`
}
stmt := spanner.Statement{
SQL: q,
Params: map[string]interface{}{
"p1": table.Name,
},
}
var iter spannerclient.RowIterator
if isi.SpannerClient != nil {
iter = isi.SpannerClient.Single().Query(isi.Ctx, stmt)
} else {
iter = isi.Client.Single().Query(isi.Ctx, stmt)
}
defer iter.Stop()
var col, refCol, fKeyName, refTable string
fKeys := make(map[string]common.FkConstraint)
var keyNames []string
for {
row, err := iter.Next()
if err == iterator.Done {
break
}
if err != nil {
return nil, fmt.Errorf("couldn't get row while fetching foreign keys: %w", err)
}
err = row.Columns(&fKeyName, &col, &refTable, &refCol)
if err != nil {
return nil, err
}
if _, found := fKeys[fKeyName]; found {
fk := fKeys[fKeyName]
fk.Cols = append(fk.Cols, col)
fk.Refcols = append(fk.Refcols, refCol)
fKeys[fKeyName] = fk
continue
}
fKeys[fKeyName] = common.FkConstraint{Name: fKeyName, Table: isi.GetTableName(table.Schema, refTable), Refcols: []string{refCol}, Cols: []string{col}}
keyNames = append(keyNames, fKeyName)
}
sort.Strings(keyNames)
for _, k := range keyNames {
// The query returns a crypted result for multi-col FKs. Currently for a FK from (a,b,c) -> (x,y,z),
// the returned rows like (a,x), (a,y), (a,z), (b,x), (b,y), (b,z), (c,x), (c,y), (c,z).
// Need to reduce it to (a,x), (b,y), (c,z). The logic below does that.
n := int(math.Sqrt(float64(len(fKeys[k].Cols))))
cols, refcols := []string{}, []string{}
for i := 0; i < n; i++ {
cols = append(cols, fKeys[k].Cols[i*n])
refcols = append(refcols, fKeys[k].Refcols[i])
}
foreignKeys = append(foreignKeys,
schema.ForeignKey{
Id: internal.GenerateForeignkeyId(),
Name: fKeys[k].Name,
ColumnNames: cols,
ReferTableName: fKeys[k].Table,
ReferColumnNames: refcols})
}
return foreignKeys, nil
}