@@ -32,41 +32,76 @@ struct SQLForeignKeyRequest {
32
32
}
33
33
34
34
// Incomplete information: let's look for schema foreign keys
35
- let foreignKeys = try db. foreignKeys ( on: originTable) . filter { foreignKey in
36
- if destinationTable. lowercased ( ) != foreignKey. destinationTable. lowercased ( ) {
37
- return false
35
+ //
36
+ // But maybe the tables are views. In this case, don't throw
37
+ // "no such table" error, because this is confusing for the user,
38
+ // as discovered in <https://github.com/groue/GRDB.swift/discussions/1481>.
39
+ // Instead, we'll crash with a clear message.
40
+
41
+ guard let originType = try tableType ( db, for: originTable) else {
42
+ throw DatabaseError . noSuchTable ( originTable)
43
+ }
44
+
45
+ if originType. isView {
46
+ if originColumns == nil {
47
+ fatalError ( """
48
+ Could not infer foreign key from ' \( originTable) ' \
49
+ to ' \( destinationTable) '. To fix this error, provide an \
50
+ explicit `ForeignKey` in the association definition.
51
+ """ )
38
52
}
39
- if let originColumns {
40
- let originColumns = Set ( originColumns. lazy. map { $0. lowercased ( ) } )
41
- let foreignKeyColumns = Set ( foreignKey. mapping. lazy. map { $0. origin. lowercased ( ) } )
42
- if originColumns != foreignKeyColumns {
53
+ } else {
54
+ let foreignKeys = try db. foreignKeys ( on: originTable) . filter { foreignKey in
55
+ if destinationTable. lowercased ( ) != foreignKey. destinationTable. lowercased ( ) {
43
56
return false
44
57
}
45
- }
46
- if let destinationColumns {
47
- // TODO: test
48
- let destinationColumns = Set ( destinationColumns. lazy. map { $0. lowercased ( ) } )
49
- let foreignKeyColumns = Set ( foreignKey. mapping. lazy. map { $0. destination. lowercased ( ) } )
50
- if destinationColumns != foreignKeyColumns {
51
- return false
58
+ if let originColumns {
59
+ let originColumns = Set ( originColumns. lazy. map { $0. lowercased ( ) } )
60
+ let foreignKeyColumns = Set ( foreignKey. mapping. lazy. map { $0. origin. lowercased ( ) } )
61
+ if originColumns != foreignKeyColumns {
62
+ return false
63
+ }
64
+ }
65
+ if let destinationColumns {
66
+ // TODO: test
67
+ let destinationColumns = Set ( destinationColumns. lazy. map { $0. lowercased ( ) } )
68
+ let foreignKeyColumns = Set ( foreignKey. mapping. lazy. map { $0. destination. lowercased ( ) } )
69
+ if destinationColumns != foreignKeyColumns {
70
+ return false
71
+ }
52
72
}
73
+ return true
53
74
}
54
- return true
55
- }
56
-
57
- // Matching foreign key(s) found
58
- if let foreignKey = foreignKeys. first {
59
- if foreignKeys. count == 1 {
60
- // Non-ambiguous
61
- return foreignKey. mapping
62
- } else {
63
- // Ambiguous: can't choose
64
- fatalError ( " Ambiguous foreign key from \( originTable) to \( destinationTable) " )
75
+
76
+ // Matching foreign key(s) found
77
+ if let foreignKey = foreignKeys. first {
78
+ if foreignKeys. count == 1 {
79
+ // Non-ambiguous
80
+ return foreignKey. mapping
81
+ } else {
82
+ // Ambiguous: can't choose
83
+ fatalError ( """
84
+ Ambiguous foreign key from ' \( originTable) ' to \
85
+ ' \( destinationTable) '. To fix this error, provide an \
86
+ explicit `ForeignKey` in the association definition.
87
+ """ )
88
+ }
65
89
}
66
90
}
67
91
68
92
// No matching foreign key found: use the destination primary key
69
93
if let originColumns {
94
+ guard let destinationType = try tableType ( db, for: destinationTable) else {
95
+ throw DatabaseError . noSuchTable ( destinationTable)
96
+ }
97
+ if destinationType. isView {
98
+ fatalError ( """
99
+ Could not infer foreign key from ' \( originTable) ' \
100
+ to ' \( destinationTable) '. To fix this error, provide an \
101
+ explicit `ForeignKey` in the association definition, \
102
+ with both origin and destination columns.
103
+ """ )
104
+ }
70
105
let destinationColumns = try db. primaryKey ( destinationTable) . columns
71
106
if originColumns. count == destinationColumns. count {
72
107
let mapping = zip ( originColumns, destinationColumns) . map {
@@ -76,7 +111,28 @@ struct SQLForeignKeyRequest {
76
111
}
77
112
}
78
113
79
- fatalError ( " Could not infer foreign key from \( originTable) to \( destinationTable) " )
114
+ fatalError ( """
115
+ Could not infer foreign key from ' \( originTable) ' to \
116
+ ' \( destinationTable) '. To fix this error, provide an \
117
+ explicit `ForeignKey` in the association definition.
118
+ """ )
119
+ }
120
+
121
+ private struct TableType {
122
+ var isView : Bool
123
+ }
124
+
125
+ private func tableType( _ db: Database , for name: String ) throws -> TableType ? {
126
+ for schemaID in try db. schemaIdentifiers ( ) {
127
+ if try db. schema ( schemaID) . containsObjectNamed ( name, ofType: . table) {
128
+ return TableType ( isView: false )
129
+ }
130
+ if try db. schema ( schemaID) . containsObjectNamed ( name, ofType: . view) {
131
+ return TableType ( isView: true )
132
+ }
133
+ }
134
+
135
+ return nil
80
136
}
81
137
}
82
138
0 commit comments