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 |