Datei: NDODLL/SqlPersistenceHandling/SqlQueryGenerator.cs
Last Commit (48a2b1a)
1 | using NDO.Mapping; |
2 | using NDO.Query; |
3 | using NDOql.Expressions; |
4 | using System; |
5 | using System.Collections.Generic; |
6 | using System.Linq; |
7 | using System.Text; |
8 | using System.Text.RegularExpressions; |
9 | using NDO.Configuration; |
10 | |
11 | namespace NDO.SqlPersistenceHandling |
12 | { |
13 | ····class SqlQueryGenerator : IQueryGenerator |
14 | ····{ |
15 | ········private readonly INDOContainer configContainer; |
16 | ········private List<QueryInfo> subQueries = new List<QueryInfo>(); |
17 | ········private Func<Dictionary<Relation, Class>, bool, Class, bool, object, string> selectPartCreator; |
18 | ········private object additionalSelectPartData = null; |
19 | ········private Mappings mappings; |
20 | |
21 | ········public SqlQueryGenerator(INDOContainer configContainer) |
22 | ········{ |
23 | ············this.configContainer = configContainer; |
24 | ············this.mappings = this.configContainer.Resolve<Mappings>(); |
25 | ········} |
26 | |
27 | ········/// <summary> |
28 | ········/// Creates a Sql query string for the complete query over all types. |
29 | ········/// </summary> |
30 | ········/// <param name="queryContextsList">List of all contexts which define possible mutations of concrete classes in results and relations.</param> |
31 | ········/// <param name="expressionTree">The syntax tree of the NDOql query expression.</param> |
32 | ········/// <param name="hollow">Determines, if the query results should be hollow objects.</param> |
33 | ········/// <param name="orderings">List of orderings for the resultset.</param> |
34 | ········/// <param name="skip">Determines how many records of the resultset should be skipped. The resultset must be ordered.</param> |
35 | ········/// <param name="take">Determines how many records of the resultset should be returned by the query. The resultset must be ordered.</param> |
36 | ········/// <param name="prefetch">Query for the given prefetch relation.</param> |
37 | ········/// <returns>A query string.</returns> |
38 | ········/// <remarks>The result can be used for debugging and display purposes or with handlers, which don't support distributed databases.</remarks> |
39 | ········public string GenerateQueryStringForAllTypes( |
40 | ············List<QueryContextsEntry> queryContextsList, |
41 | ············OqlExpression expressionTree, |
42 | ············bool hollow, |
43 | ············List<QueryOrder> orderings, |
44 | ············int skip, |
45 | ············int take, string prefetch = null ) |
46 | ········{ |
47 | ············this.selectPartCreator = CreateQuerySelectPart; |
48 | |
49 | ············CreateSubQueriesForAllTypes(queryContextsList, expressionTree, hollow, orderings, skip, take ); |
50 | ············StringBuilder generatedQuery = new StringBuilder(); |
51 | |
52 | ············foreach (QueryInfo qi in subQueries) |
53 | ············{ |
54 | ················generatedQuery.Append(qi.QueryString); |
55 | ················generatedQuery.Append(";\r\n"); |
56 | ············} |
57 | |
58 | ············generatedQuery.Length -= 3; // the trailing ;\r\n |
59 | |
60 | ············return generatedQuery.ToString(); |
61 | ········} |
62 | |
63 | ········/// <summary> |
64 | ········/// Creates a SQL query string, which can be passed to the IPersistenceHandler to fetch the results for a given concrete type. |
65 | ········/// </summary> |
66 | ········/// <param name="queryContextsEntry">All contexts which define possible mutations of concrete classes in results and relations.</param> |
67 | ········/// <param name="expressionTree">The syntax tree of the NDOql query expression.</param> |
68 | ········/// <param name="hollow">Determines, if the query results should be hollow objects.</param> |
69 | ········/// <param name="hasSubclassResultsets">Determines, if this query is part of a composed query which spans over several tables.</param> |
70 | ········/// <param name="orderings">List of orderings for the resultset.</param> |
71 | ········/// <param name="skip">Determines how many records of the resultset should be skipped. The resultset must be ordered.</param> |
72 | ········/// <param name="take">Determines how many records of the resultset should be returned by the query. The resultset must be ordered.</param> |
73 | ········/// <param name="prefetch">Query for the given prefetch relation.</param> |
74 | ········/// <returns>A Query string.</returns> |
75 | ········public string GenerateQueryString( |
76 | ············QueryContextsEntry queryContextsEntry, |
77 | ············OqlExpression expressionTree, |
78 | ············bool hollow, |
79 | ············bool hasSubclassResultsets, |
80 | ············List<QueryOrder> orderings, |
81 | ············int skip, |
82 | ············int take, |
83 | ············string prefetch ) |
84 | ········{ |
85 | ············this.selectPartCreator = CreateQuerySelectPart; |
86 | |
87 | ············return InnerGenerateQueryString( queryContextsEntry, expressionTree, hollow, hasSubclassResultsets, orderings, skip, take, prefetch ); |
88 | ········} |
89 | |
90 | ········private string InnerGenerateQueryString( QueryContextsEntry queryContextsEntry, OqlExpression expressionTree, bool hollow, bool hasSubclassResultsets, List<QueryOrder> orderings, int skip, int take, string prefetch ) |
91 | ········{ |
92 | ············CreateSubQueries( queryContextsEntry, expressionTree, hollow, hasSubclassResultsets, orderings, skip, take ); |
93 | ············StringBuilder generatedQuery = new StringBuilder(); |
94 | |
95 | ············foreach (QueryInfo qi in subQueries) |
96 | ············{ |
97 | ················generatedQuery.Append( qi.QueryString ); |
98 | ················generatedQuery.Append( ";\n" ); |
99 | ············} |
100 | |
101 | ············generatedQuery.Length--; // the trailing \n |
102 | |
103 | ············if (subQueries.Count == 1) |
104 | ················generatedQuery.Length--; // the semicolon |
105 | |
106 | ············return generatedQuery.ToString(); |
107 | ········} |
108 | |
109 | ········void CreateSubQueriesForAllTypes( |
110 | ············List<QueryContextsEntry> queryContextsList, |
111 | ············OqlExpression expressionTree, |
112 | ············bool hollow, |
113 | ············List<QueryOrder> orderings, |
114 | ············int skip, |
115 | ············int take, |
116 | ············string prefetch = null ) |
117 | ········{ |
118 | ············foreach (var item in queryContextsList) |
119 | ············{ |
120 | ················CreateSubQueries( item, expressionTree, hollow, queryContextsList.Count > 1, orderings, skip, take ); |
121 | ············} |
122 | ········} |
123 | |
124 | ········void CreateSubQueries( QueryContextsEntry queryContextsEntry, |
125 | ············OqlExpression expressionTree, |
126 | ············bool hollow, |
127 | ············bool hasSubclassResultsets, |
128 | ············List<QueryOrder> orderings, |
129 | ············int skip, |
130 | ············int take ) |
131 | ········{ |
132 | ············Type t = queryContextsEntry.Type; |
133 | ············var queryContexts = queryContextsEntry.QueryContexts; |
134 | |
135 | ············if (queryContexts.Count == 0) |
136 | ············{ |
137 | ················this.subQueries.Add( new QueryInfo( t, ConstructQueryString( t, new Dictionary<Relation, Class>(), expressionTree, hollow, hasSubclassResultsets, orderings, skip, take ) ) ); |
138 | ············} |
139 | ············else |
140 | ············{ |
141 | ················string queryString = string.Empty; |
142 | ················int added = 0; |
143 | ················foreach (var queryContext in queryContexts) |
144 | ················{ |
145 | ····················if (!queryContext.Any( kvp => kvp.Value.SystemType == t )) |
146 | ····················{ |
147 | ························if (added > 0) |
148 | ····························queryString += " UNION \r\n"; |
149 | ························queryString += ConstructQueryString( t, queryContext, expressionTree, hollow, hasSubclassResultsets, orderings, skip, take ); |
150 | ························added++; |
151 | ····················} |
152 | ················} |
153 | ················this.subQueries.Add( new QueryInfo( t, queryString ) ); |
154 | ············} |
155 | ········} |
156 | |
157 | ········SqlColumnListGenerator CreateColumnListGenerator( Class cls ) |
158 | ········{ |
159 | ············var key = $"{nameof(SqlColumnListGenerator)}-{cls.FullName}"; |
160 | ············return configContainer.ResolveOrRegisterType<SqlColumnListGenerator>( new ContainerControlledLifetimeManager(), key, new ParameterOverride( "cls", cls ) ); |
161 | ········} |
162 | |
163 | ········string ConstructQueryString( |
164 | ············Type resultType, |
165 | ············Dictionary<Relation, Class> relationContext, |
166 | ············OqlExpression expressionTree, |
167 | ············bool hollow, |
168 | ············bool hasSubclassResultsets, |
169 | ············List<QueryOrder> orderings, |
170 | ············int skip, |
171 | ············int take ) |
172 | ········{ |
173 | ············Class cls = this.mappings.FindClass( resultType ); |
174 | |
175 | ············StringBuilder sb = new StringBuilder( "SELECT " ); |
176 | |
177 | ············var from = new FromGenerator( cls, relationContext ).GenerateFromExpression( expressionTree ); |
178 | ············var qualifyWithTableName = from.IndexOf( "INNER JOIN" ) > -1; |
179 | |
180 | ············string columnList = this.selectPartCreator( relationContext, hollow, cls, qualifyWithTableName, this.additionalSelectPartData ); |
181 | ············sb.Append( columnList ); |
182 | |
183 | ············// If we need to sort a set of hollow results for different subclasses |
184 | ············// we need to fetch the ordering columns together with the oid columns |
185 | ············if (hollow && orderings.Count > 0 && hasSubclassResultsets) |
186 | ············{ |
187 | ················var orderCols = (from o in orderings select cls.FindField( o.FieldName ).Column.GetQualifiedName()).ToArray(); |
188 | ················columnList += ", " + String.Join( ", ", orderCols ); |
189 | ············} |
190 | |
191 | ············sb.Append( ' ' ); |
192 | ············sb.Append( from ); |
193 | ············string where = new WhereGenerator( cls, relationContext ).GenerateWhereClause( expressionTree ); |
194 | ············if (where != string.Empty) |
195 | ············{ |
196 | ················sb.Append( ' ' ); |
197 | ················sb.Append( where ); |
198 | ············} |
199 | |
200 | ············if (orderings.Count > 0) |
201 | ············{ |
202 | ················sb.Append( ' ' ); |
203 | ················sb.Append( new OrderGenerator( cls ).GenerateOrderClause( orderings, skip, take ) ); |
204 | ············} |
205 | |
206 | ············return sb.ToString(); |
207 | ········} |
208 | |
209 | ········private string CreateQuerySelectPart( Dictionary<Relation, Class> relationContext, bool hollow, Class cls, bool qualifyWithTableName, object additionalData ) |
210 | ········{ |
211 | ············var generator = CreateColumnListGenerator( cls ); |
212 | |
213 | ············// We have to hack around a special behavior of SQLite, generating |
214 | ············// new columns with fully specified column names, if the query |
215 | ············// is a UNION············ |
216 | ············var generateAliasNames = relationContext.Count > 0 && cls.Provider.GetType().FullName.IndexOf( "Sqlite" ) > -1; |
217 | |
218 | ············return generator.Result( hollow, generateAliasNames, qualifyWithTableName ); |
219 | ········} |
220 | |
221 | ········private string CreateAggregateSelectPart( Dictionary<Relation, Class> relationContext, bool hollow, Class cls, bool qualifyWithTableName, object additionalData ) |
222 | ········{ |
223 | ············var tuple = (Tuple<string, AggregateType>)additionalData; |
224 | ············string field = tuple.Item1; |
225 | ············AggregateType aggregateType = tuple.Item2; |
226 | ············bool isStar = field == "*"; |
227 | |
228 | ············Column column = null; |
229 | ············if (field.ToLower().StartsWith( "oid" )) |
230 | ············{ |
231 | ················Regex regex = new Regex( @"\(\s*(\d+)\s*\)" ); |
232 | ················Match match = regex.Match( field ); |
233 | ················int index = 0; |
234 | ················if (match.Success) |
235 | ····················index = int.Parse( match.Groups[1].Value ); |
236 | ················column = ((OidColumn)cls.Oid.OidColumns[index]); |
237 | ············} |
238 | ············else |
239 | ············{ |
240 | ················if (!isStar) |
241 | ····················column = cls.FindField( field ).Column; |
242 | ············} |
243 | |
244 | ············var provider = cls.Provider; |
245 | ············var colName = isStar ? "*" : column.GetQualifiedName(); |
246 | ············//var tableName = qualifyWithTableName ? cls.GetQualifiedTableName() + "." : String.Empty; |
247 | |
248 | return $"{ aggregateType. ToString( ) . ToUpper( ) } ( { colName} ) AS { provider. GetQuotedName( "AggrResult" ) } "; |
249 | ········} |
250 | |
251 | ········public string GenerateAggregateQueryString( string field, QueryContextsEntry queryContextsEntry, OqlExpression expressionTree, bool hasSubclassResultsets, AggregateType aggregateType ) |
252 | ········{ |
253 | ············this.selectPartCreator = CreateAggregateSelectPart; |
254 | ············this.additionalSelectPartData = new Tuple<string,AggregateType>( field, aggregateType ); |
255 | |
256 | ············var query = InnerGenerateQueryString( queryContextsEntry, expressionTree, true, hasSubclassResultsets, new List<QueryOrder>(), 0, 0, null ); |
257 | ············return query; |
258 | ········} |
259 | |
260 | ········public string GeneratePrefetchQuery( Type parentType, IEnumerable<IPersistenceCapable> parents, string prefetch ) |
261 | ········{ |
262 | ············Class parentCls = this.mappings.FindClass( parentType ); |
263 | |
264 | ············StringBuilder sb = new StringBuilder( "SELECT " ); |
265 | ············List<Relation> relations = new List<Relation>(); |
266 | |
267 | ············if (prefetch.IndexOf('.') == -1) |
268 | ············{ |
269 | ················var rel = parentCls.FindRelation( prefetch ); |
270 | ················if (rel != null) |
271 | ····················relations.Add( rel ); |
272 | ············} |
273 | |
274 | ············new RelationContextGenerator( this.mappings ).CreateContextForName( parentCls, prefetch, relations ); |
275 | |
276 | ············if (relations.Count == 0) |
277 | ················throw new NDOException( 76, $"Prefetch: Can't find relation mapping with name {prefetch} in class {parentCls.FullName}" ); |
278 | |
279 | ············Class cls = mappings.FindClass( relations[relations.Count - 1].ReferencedTypeName ); |
280 | ············var relationContext = new Dictionary<Relation, Class>(); |
281 | #warning Hier fehlt der INNER JOIN |
282 | ············string columnList = CreateQuerySelectPart( relationContext, false, cls, true, null ); |
283 | ············sb.Append( columnList ); |
284 | |
285 | ············sb.Append( ' ' ); |
286 | ············sb.Append( new FromGenerator( cls, relationContext ).GenerateFromExpression( null, relations ) ); |
287 | ············// We can be sure, that we have only one oid column here. |
288 | ············var oidList = String.Join( ",", from p in parents select p.NDOObjectId.Id.Values[0].ToString() ); |
289 | ············string where = $"WHERE {parentCls.Oid.OidColumns.First().GetQualifiedName()} IN ({oidList})"; |
290 | ············sb.Append( ' ' ); |
291 | ············sb.Append( where ); |
292 | |
293 | ············return sb.ToString(); |
294 | ········} |
295 | ····} |
296 | } |
297 |
New Commit (790647f)
1 | using NDO.Mapping; |
2 | using NDO.Query; |
3 | using NDOql.Expressions; |
4 | using System; |
5 | using System.Collections.Generic; |
6 | using System.Linq; |
7 | using System.Text; |
8 | using System.Text.RegularExpressions; |
9 | using NDO.Configuration; |
10 | |
11 | namespace NDO.SqlPersistenceHandling |
12 | { |
13 | ····class SqlQueryGenerator : IQueryGenerator |
14 | ····{ |
15 | ········private readonly INDOContainer configContainer; |
16 | ········private List<QueryInfo> subQueries = new List<QueryInfo>(); |
17 | ········private Func<Dictionary<Relation, Class>, bool, Class, bool, object, string> selectPartCreator; |
18 | ········private object additionalSelectPartData = null; |
19 | ········private Mappings mappings; |
20 | |
21 | ········public SqlQueryGenerator(INDOContainer configContainer) |
22 | ········{ |
23 | ············this.configContainer = configContainer; |
24 | ············this.mappings = this.configContainer.Resolve<Mappings>(); |
25 | ········} |
26 | |
27 | ········/// <summary> |
28 | ········/// Creates a Sql query string for the complete query over all types. |
29 | ········/// </summary> |
30 | ········/// <param name="queryContextsList">List of all contexts which define possible mutations of concrete classes in results and relations.</param> |
31 | ········/// <param name="expressionTree">The syntax tree of the NDOql query expression.</param> |
32 | ········/// <param name="hollow">Determines, if the query results should be hollow objects.</param> |
33 | ········/// <param name="orderings">List of orderings for the resultset.</param> |
34 | ········/// <param name="skip">Determines how many records of the resultset should be skipped. The resultset must be ordered.</param> |
35 | ········/// <param name="take">Determines how many records of the resultset should be returned by the query. The resultset must be ordered.</param> |
36 | ········/// <param name="prefetch">Query for the given prefetch relation.</param> |
37 | ········/// <returns>A query string.</returns> |
38 | ········/// <remarks>The result can be used for debugging and display purposes or with handlers, which don't support distributed databases.</remarks> |
39 | ········public string GenerateQueryStringForAllTypes( |
40 | ············List<QueryContextsEntry> queryContextsList, |
41 | ············OqlExpression expressionTree, |
42 | ············bool hollow, |
43 | ············List<QueryOrder> orderings, |
44 | ············int skip, |
45 | ············int take, string prefetch = null ) |
46 | ········{ |
47 | ············this.selectPartCreator = CreateQuerySelectPart; |
48 | |
49 | ············CreateSubQueriesForAllTypes(queryContextsList, expressionTree, hollow, orderings, skip, take ); |
50 | ············StringBuilder generatedQuery = new StringBuilder(); |
51 | |
52 | ············foreach (QueryInfo qi in subQueries) |
53 | ············{ |
54 | ················generatedQuery.Append(qi.QueryString); |
55 | ················generatedQuery.Append(";\r\n"); |
56 | ············} |
57 | |
58 | ············generatedQuery.Length -= 3; // the trailing ;\r\n |
59 | |
60 | ············return generatedQuery.ToString(); |
61 | ········} |
62 | |
63 | ········/// <summary> |
64 | ········/// Creates a SQL query string, which can be passed to the IPersistenceHandler to fetch the results for a given concrete type. |
65 | ········/// </summary> |
66 | ········/// <param name="queryContextsEntry">All contexts which define possible mutations of concrete classes in results and relations.</param> |
67 | ········/// <param name="expressionTree">The syntax tree of the NDOql query expression.</param> |
68 | ········/// <param name="hollow">Determines, if the query results should be hollow objects.</param> |
69 | ········/// <param name="hasSubclassResultsets">Determines, if this query is part of a composed query which spans over several tables.</param> |
70 | ········/// <param name="orderings">List of orderings for the resultset.</param> |
71 | ········/// <param name="skip">Determines how many records of the resultset should be skipped. The resultset must be ordered.</param> |
72 | ········/// <param name="take">Determines how many records of the resultset should be returned by the query. The resultset must be ordered.</param> |
73 | ········/// <param name="prefetch">Query for the given prefetch relation.</param> |
74 | ········/// <returns>A Query string.</returns> |
75 | ········public string GenerateQueryString( |
76 | ············QueryContextsEntry queryContextsEntry, |
77 | ············OqlExpression expressionTree, |
78 | ············bool hollow, |
79 | ············bool hasSubclassResultsets, |
80 | ············List<QueryOrder> orderings, |
81 | ············int skip, |
82 | ············int take, |
83 | ············string prefetch ) |
84 | ········{ |
85 | ············this.selectPartCreator = CreateQuerySelectPart; |
86 | |
87 | ············return InnerGenerateQueryString( queryContextsEntry, expressionTree, hollow, hasSubclassResultsets, orderings, skip, take, prefetch ); |
88 | ········} |
89 | |
90 | ········private string InnerGenerateQueryString( QueryContextsEntry queryContextsEntry, OqlExpression expressionTree, bool hollow, bool hasSubclassResultsets, List<QueryOrder> orderings, int skip, int take, string prefetch ) |
91 | ········{ |
92 | ············CreateSubQueries( queryContextsEntry, expressionTree, hollow, hasSubclassResultsets, orderings, skip, take ); |
93 | ············StringBuilder generatedQuery = new StringBuilder(); |
94 | |
95 | ············foreach (QueryInfo qi in subQueries) |
96 | ············{ |
97 | ················generatedQuery.Append( qi.QueryString ); |
98 | ················generatedQuery.Append( ";\n" ); |
99 | ············} |
100 | |
101 | ············generatedQuery.Length--; // the trailing \n |
102 | |
103 | ············if (subQueries.Count == 1) |
104 | ················generatedQuery.Length--; // the semicolon |
105 | |
106 | ············return generatedQuery.ToString(); |
107 | ········} |
108 | |
109 | ········void CreateSubQueriesForAllTypes( |
110 | ············List<QueryContextsEntry> queryContextsList, |
111 | ············OqlExpression expressionTree, |
112 | ············bool hollow, |
113 | ············List<QueryOrder> orderings, |
114 | ············int skip, |
115 | ············int take, |
116 | ············string prefetch = null ) |
117 | ········{ |
118 | ············foreach (var item in queryContextsList) |
119 | ············{ |
120 | ················CreateSubQueries( item, expressionTree, hollow, queryContextsList.Count > 1, orderings, skip, take ); |
121 | ············} |
122 | ········} |
123 | |
124 | ········void CreateSubQueries( QueryContextsEntry queryContextsEntry, |
125 | ············OqlExpression expressionTree, |
126 | ············bool hollow, |
127 | ············bool hasSubclassResultsets, |
128 | ············List<QueryOrder> orderings, |
129 | ············int skip, |
130 | ············int take ) |
131 | ········{ |
132 | ············Type t = queryContextsEntry.Type; |
133 | ············var queryContexts = queryContextsEntry.QueryContexts; |
134 | |
135 | ············if (queryContexts.Count == 0) |
136 | ············{ |
137 | ················this.subQueries.Add( new QueryInfo( t, ConstructQueryString( t, new Dictionary<Relation, Class>(), expressionTree, hollow, hasSubclassResultsets, orderings, skip, take ) ) ); |
138 | ············} |
139 | ············else |
140 | ············{ |
141 | ················string queryString = string.Empty; |
142 | ················int added = 0; |
143 | ················foreach (var queryContext in queryContexts) |
144 | ················{ |
145 | ····················if (!queryContext.Any( kvp => kvp.Value.SystemType == t )) |
146 | ····················{ |
147 | ························if (added > 0) |
148 | ····························queryString += " UNION \r\n"; |
149 | ························queryString += ConstructQueryString( t, queryContext, expressionTree, hollow, hasSubclassResultsets, orderings, skip, take ); |
150 | ························added++; |
151 | ····················} |
152 | ················} |
153 | ················this.subQueries.Add( new QueryInfo( t, queryString ) ); |
154 | ············} |
155 | ········} |
156 | |
157 | ········SqlColumnListGenerator CreateColumnListGenerator( Class cls ) |
158 | ········{ |
159 | ············var key = $"{nameof(SqlColumnListGenerator)}-{cls.FullName}"; |
160 | ············return configContainer.ResolveOrRegisterType<SqlColumnListGenerator>( new ContainerControlledLifetimeManager(), key, new ParameterOverride( "cls", cls ) ); |
161 | ········} |
162 | |
163 | ········string ConstructQueryString( |
164 | ············Type resultType, |
165 | ············Dictionary<Relation, Class> relationContext, |
166 | ············OqlExpression expressionTree, |
167 | ············bool hollow, |
168 | ············bool hasSubclassResultsets, |
169 | ············List<QueryOrder> orderings, |
170 | ············int skip, |
171 | ············int take ) |
172 | ········{ |
173 | ············Class cls = this.mappings.FindClass( resultType ); |
174 | |
175 | ············StringBuilder sb = new StringBuilder( "SELECT " ); |
176 | |
177 | ············var from = new FromGenerator( cls, relationContext ).GenerateFromExpression( expressionTree ); |
178 | ············var qualifyWithTableName = from.IndexOf( "INNER JOIN" ) > -1; |
179 | |
180 | ············string columnList = this.selectPartCreator( relationContext, hollow, cls, qualifyWithTableName, this.additionalSelectPartData ); |
181 | ············sb.Append( columnList ); |
182 | |
183 | ············// If we need to sort a set of hollow results for different subclasses |
184 | ············// we need to fetch the ordering columns together with the oid columns |
185 | ············if (hollow && orderings.Count > 0 && hasSubclassResultsets) |
186 | ············{ |
187 | ················var orderCols = (from o in orderings select cls.FindField( o.FieldName ).Column.GetQualifiedName()).ToArray(); |
188 | ················columnList += ", " + String.Join( ", ", orderCols ); |
189 | ············} |
190 | |
191 | ············sb.Append( ' ' ); |
192 | ············sb.Append( from ); |
193 | ············string where = new WhereGenerator( cls, relationContext ).GenerateWhereClause( expressionTree ); |
194 | ············if (where != string.Empty) |
195 | ············{ |
196 | ················sb.Append( ' ' ); |
197 | ················sb.Append( where ); |
198 | ············} |
199 | |
200 | ············if (orderings.Count > 0) |
201 | ············{ |
202 | ················sb.Append( ' ' ); |
203 | ················sb.Append( new OrderGenerator( cls ).GenerateOrderClause( orderings, skip, take ) ); |
204 | ············} |
205 | |
206 | ············return sb.ToString(); |
207 | ········} |
208 | |
209 | ········private string CreateQuerySelectPart( Dictionary<Relation, Class> relationContext, bool hollow, Class cls, bool qualifyWithTableName, object additionalData ) |
210 | ········{ |
211 | ············var generator = CreateColumnListGenerator( cls ); |
212 | |
213 | ············// We have to hack around a special behavior of SQLite, generating |
214 | ············// new columns with fully specified column names, if the query |
215 | ············// is a UNION············ |
216 | ············var generateAliasNames = relationContext.Count > 0 && cls.Provider.GetType().FullName.IndexOf( "Sqlite" ) > -1; |
217 | |
218 | ············return generator.Result( hollow, generateAliasNames, qualifyWithTableName ); |
219 | ········} |
220 | |
221 | ········private string CreateAggregateSelectPart( Dictionary<Relation, Class> relationContext, bool hollow, Class cls, bool qualifyWithTableName, object additionalData ) |
222 | ········{ |
223 | ············var tuple = (Tuple<string, AggregateType>)additionalData; |
224 | ············string field = tuple.Item1; |
225 | ············AggregateType aggregateType = tuple.Item2; |
226 | ············bool isStar = field == "*"; |
227 | |
228 | ············Column column = null; |
229 | ············if (field.ToLower().StartsWith( "oid" )) |
230 | ············{ |
231 | ················Regex regex = new Regex( @"\(\s*(\d+)\s*\)" ); |
232 | ················Match match = regex.Match( field ); |
233 | ················int index = 0; |
234 | ················if (match.Success) |
235 | ····················index = int.Parse( match.Groups[1].Value ); |
236 | ················column = ((OidColumn)cls.Oid.OidColumns[index]); |
237 | ············} |
238 | ············else |
239 | ············{ |
240 | ················if (!isStar) |
241 | ····················column = cls.FindField( field ).Column; |
242 | ············} |
243 | |
244 | ············var provider = cls.Provider; |
245 | ············var colName = isStar ? "*" : column.GetQualifiedName(); |
246 | |
247 | return $"{ aggregateType. ToString( ) . ToUpper( ) } ( { colName} ) AS { provider. GetQuotedName( "AggrResult" ) } "; |
248 | ········} |
249 | |
250 | ········public string GenerateAggregateQueryString( string field, QueryContextsEntry queryContextsEntry, OqlExpression expressionTree, bool hasSubclassResultsets, AggregateType aggregateType ) |
251 | ········{ |
252 | ············this.selectPartCreator = CreateAggregateSelectPart; |
253 | ············this.additionalSelectPartData = new Tuple<string,AggregateType>( field, aggregateType ); |
254 | |
255 | ············var query = InnerGenerateQueryString( queryContextsEntry, expressionTree, true, hasSubclassResultsets, new List<QueryOrder>(), 0, 0, null ); |
256 | ············return query; |
257 | ········} |
258 | |
259 | ········public string GeneratePrefetchQuery( Type parentType, IEnumerable<IPersistenceCapable> parents, string prefetch ) |
260 | ········{ |
261 | ············Class parentCls = this.mappings.FindClass( parentType ); |
262 | |
263 | ············StringBuilder sb = new StringBuilder( "SELECT " ); |
264 | ············List<Relation> relations = new List<Relation>(); |
265 | |
266 | ············if (prefetch.IndexOf('.') == -1) |
267 | ············{ |
268 | ················var rel = parentCls.FindRelation( prefetch ); |
269 | ················if (rel != null) |
270 | ····················relations.Add( rel ); |
271 | ············} |
272 | |
273 | ············new RelationContextGenerator( this.mappings ).CreateContextForName( parentCls, prefetch, relations ); |
274 | |
275 | ············if (relations.Count == 0) |
276 | ················throw new NDOException( 76, $"Prefetch: Can't find relation mapping with name {prefetch} in class {parentCls.FullName}" ); |
277 | |
278 | ············Class cls = mappings.FindClass( relations[relations.Count - 1].ReferencedTypeName ); |
279 | ············var relationContext = new Dictionary<Relation, Class>(); |
280 | #warning Hier fehlt der INNER JOIN |
281 | ············string columnList = CreateQuerySelectPart( relationContext, false, cls, true, null ); |
282 | ············sb.Append( columnList ); |
283 | |
284 | ············sb.Append( ' ' ); |
285 | ············sb.Append( new FromGenerator( cls, relationContext ).GenerateFromExpression( null, relations ) ); |
286 | ············// We can be sure, that we have only one oid column here. |
287 | ············var oidList = String.Join( ",", from p in parents select p.NDOObjectId.Id.Values[0].ToString() ); |
288 | ············string where = $"WHERE {parentCls.Oid.OidColumns.First().GetQualifiedName()} IN ({oidList})"; |
289 | ············sb.Append( ' ' ); |
290 | ············sb.Append( where ); |
291 | |
292 | ············return sb.ToString(); |
293 | ········} |
294 | ····} |
295 | } |
296 |