@@ -9,78 +9,49 @@ import Amplify
9
9
import Foundation
10
10
import SQLite
11
11
12
- /// Represents a `select` SQL statement associated with a `Model` instance and
13
- /// optionally composed by a `ConditionStatement`.
14
- struct SelectStatement : SQLStatement {
15
-
16
- let modelType : Model . Type
17
- let conditionStatement : ConditionStatement ?
18
- let paginationInput : QueryPaginationInput ?
19
-
20
- // TODO remove this once sorting support is added to DataStore
21
- // Used by plugin to order and limit results for system table queries
22
- let additionalStatements : String ?
23
- let namespace = " root "
24
-
25
- init ( from modelType: Model . Type ,
26
- predicate: QueryPredicate ? = nil ,
27
- paginationInput: QueryPaginationInput ? = nil ,
28
- additionalStatements: String ? = nil ) {
29
- self . modelType = modelType
30
-
31
- var conditionStatement : ConditionStatement ?
32
- if let predicate = predicate {
33
- let statement = ConditionStatement ( modelType: modelType,
34
- predicate: predicate,
35
- namespace: namespace [ ... ] )
36
- conditionStatement = statement
37
- }
38
- self . conditionStatement = conditionStatement
39
- self . paginationInput = paginationInput
40
- self . additionalStatements = additionalStatements
41
- }
42
-
43
- var stringValue : String {
12
+ /// Support data structure used to hold information about `SelectStatement` than
13
+ /// can be used later to parse the results.
14
+ struct SelectStatementMetadata {
15
+
16
+ typealias ColumnMapping = [ String : ( ModelSchema , ModelField ) ]
17
+
18
+ let statement : String
19
+ let columnMapping : ColumnMapping
20
+ let bindings : [ Binding ? ]
21
+
22
+ // TODO remove additionalStatements once sorting support is added to DataStore
23
+ static func metadata( from modelType: Model . Type ,
24
+ predicate: QueryPredicate ? = nil ,
25
+ paginationInput: QueryPaginationInput ? = nil ,
26
+ additionalStatements: String ? = nil ) -> SelectStatementMetadata {
27
+ let rootNamespace = " root "
44
28
let schema = modelType. schema
45
29
let fields = schema. columns
46
30
let tableName = schema. name
31
+ var columnMapping : ColumnMapping = [ : ]
47
32
var columns = fields. map { field -> String in
48
- return field. columnName ( forNamespace: namespace) + " " + field. columnAlias ( )
33
+ columnMapping. updateValue ( ( schema, field) , forKey: field. name)
34
+ return field. columnName ( forNamespace: rootNamespace) + " as " + field. columnAlias ( )
49
35
}
50
36
51
37
// eager load many-to-one/one-to-one relationships
52
- var joinStatements : [ String ] = [ ]
53
- for foreignKey in schema. foreignKeys {
54
- let associatedModelType = foreignKey. requiredAssociatedModel
55
- let associatedSchema = associatedModelType. schema
56
- let associatedTableName = associatedModelType. schema. name
57
-
58
- // columns
59
- let alias = foreignKey. name
60
- let associatedColumn = associatedSchema. primaryKey. columnName ( forNamespace: alias)
61
- let foreignKeyName = foreignKey. columnName ( forNamespace: " root " )
62
-
63
- // append columns from relationships
64
- columns += associatedSchema. columns. map { field -> String in
65
- return field. columnName ( forNamespace: alias) + " " + field. columnAlias ( forNamespace: alias)
66
- }
67
-
68
- let joinType = foreignKey. isRequired ? " inner " : " left outer "
69
-
70
- joinStatements. append ( """
71
- \( joinType) join \( associatedTableName) as \( alias)
72
- on \( associatedColumn) = \( foreignKeyName)
73
- """ )
74
- }
38
+ let joinStatements = joins ( from: schema)
39
+ columns += joinStatements. columns
40
+ columnMapping. merge ( joinStatements. columnMapping) { _, new in new }
75
41
76
42
var sql = """
77
43
select
78
44
\( joinedAsSelectedColumns ( columns) )
79
- from \( tableName) as root
80
- \( joinStatements. joined ( separator: " \n " ) )
45
+ from \( tableName) as " \( rootNamespace ) "
46
+ \( joinStatements. statements . joined ( separator: " \n " ) )
81
47
""" . trimmingCharacters ( in: . whitespacesAndNewlines)
82
48
83
- if let conditionStatement = conditionStatement {
49
+ var bindings : [ Binding ? ] = [ ]
50
+ if let predicate = predicate {
51
+ let conditionStatement = ConditionStatement ( modelType: modelType,
52
+ predicate: predicate,
53
+ namespace: rootNamespace [ ... ] )
54
+ bindings. append ( contentsOf: conditionStatement. variables)
84
55
sql = """
85
56
\( sql)
86
57
where 1 = 1
@@ -101,12 +72,86 @@ struct SelectStatement: SQLStatement {
101
72
\( paginationInput. sqlStatement)
102
73
"""
103
74
}
75
+ return SelectStatementMetadata ( statement: sql,
76
+ columnMapping: columnMapping,
77
+ bindings: bindings)
78
+ }
104
79
105
- return sql
80
+ struct JoinStatement {
81
+ let columns : [ String ]
82
+ let statements : [ String ]
83
+ let columnMapping : ColumnMapping
84
+ }
85
+
86
+ /// Walk through the associations recursively to generate join statements.
87
+ ///
88
+ /// Implementation note: this should be revisited once we define support
89
+ /// for explicit `eager` vs `lazy` associations.
90
+ private static func joins( from schema: ModelSchema ) -> JoinStatement {
91
+ var columns : [ String ] = [ ]
92
+ var joinStatements : [ String ] = [ ]
93
+ var columnMapping : ColumnMapping = [ : ]
94
+
95
+ func visitAssociations( node: ModelSchema , namespace: String = " root " ) {
96
+ for foreignKey in node. foreignKeys {
97
+ let associatedModelType = foreignKey. requiredAssociatedModel
98
+ let associatedSchema = associatedModelType. schema
99
+ let associatedTableName = associatedModelType. schema. name
100
+
101
+ // columns
102
+ let alias = namespace == " root " ? foreignKey. name : " \( namespace) . \( foreignKey. name) "
103
+ let associatedColumn = associatedSchema. primaryKey. columnName ( forNamespace: alias)
104
+ let foreignKeyName = foreignKey. columnName ( forNamespace: namespace)
105
+
106
+ // append columns from relationships
107
+ columns += associatedSchema. columns. map { field -> String in
108
+ columnMapping. updateValue ( ( associatedSchema, field) , forKey: " \( alias) . \( field. name) " )
109
+ return field. columnName ( forNamespace: alias) + " as " + field. columnAlias ( forNamespace: alias)
110
+ }
111
+
112
+ let joinType = foreignKey. isRequired ? " inner " : " left outer "
113
+
114
+ joinStatements. append ( """
115
+ \( joinType) join \( associatedTableName) as " \( alias) "
116
+ on \( associatedColumn) = \( foreignKeyName)
117
+ """ )
118
+ visitAssociations ( node: associatedModelType. schema,
119
+ namespace: alias)
120
+ }
121
+ }
122
+ visitAssociations ( node: schema)
123
+
124
+ return JoinStatement ( columns: columns,
125
+ statements: joinStatements,
126
+ columnMapping: columnMapping)
127
+ }
128
+
129
+ }
130
+
131
+ /// Represents a `select` SQL statement associated with a `Model` instance and
132
+ /// optionally composed by a `ConditionStatement`.
133
+ struct SelectStatement : SQLStatement {
134
+
135
+ let modelType : Model . Type
136
+ let metadata : SelectStatementMetadata
137
+
138
+ init ( from modelType: Model . Type ,
139
+ predicate: QueryPredicate ? = nil ,
140
+ paginationInput: QueryPaginationInput ? = nil ,
141
+ additionalStatements: String ? = nil ) {
142
+ self . modelType = modelType
143
+ self . metadata = . metadata( from: modelType,
144
+ predicate: predicate,
145
+ paginationInput: paginationInput,
146
+ additionalStatements: additionalStatements)
147
+ }
148
+
149
+ var stringValue : String {
150
+ metadata. statement
106
151
}
107
152
108
153
var variables : [ Binding ? ] {
109
- return conditionStatement ? . variables ?? [ ]
154
+ metadata . bindings
110
155
}
111
156
112
157
}
0 commit comments