DeveelDB  20151217
complete SQL database system, primarly developed for .NET/Mono frameworks
QueryExpressionFrom.cs
Go to the documentation of this file.
1 //
2 // Copyright 2010-2015 Deveel
3 //
4 // Licensed under the Apache License, Version 2.0 (the "License");
5 // you may not use this file except in compliance with the License.
6 // You may obtain a copy of the License at
7 //
8 // http://www.apache.org/licenses/LICENSE-2.0
9 //
10 // Unless required by applicable law or agreed to in writing, software
11 // distributed under the License is distributed on an "AS IS" BASIS,
12 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 // See the License for the specific language governing permissions and
14 // limitations under the License.
15 //
16 
17 using System;
18 using System.Collections.Generic;
19 using System.Linq;
20 using System.Reflection;
21 
22 using Deveel.Data;
24 using Deveel.Data.Sql.Tables;
25 
26 namespace Deveel.Data.Sql.Query {
27  public sealed class QueryExpressionFrom {
28  private readonly List<IFromTableSource> tableSources;
29  private readonly List<ExpressionReference> expressionReferences;
30  private readonly List<ObjectName> exposedColumns;
31 
33  : this(true) {
34  }
35 
36  public QueryExpressionFrom(bool ignoreCase) {
37  IgnoreCase = ignoreCase;
38 
39  tableSources = new List<IFromTableSource>();
40  expressionReferences = new List<ExpressionReference>();
41  exposedColumns = new List<ObjectName>();
42 
43  ExpressionPreparer = new FromExpressionPreparer(this);
44  }
45 
46  public bool IgnoreCase { get; private set; }
47 
48  public QueryExpressionFrom Parent { get; set; }
49 
50  public IExpressionPreparer ExpressionPreparer { get; private set; }
51 
52  public int SourceCount {
53  get { return tableSources.Count; }
54  }
55 
56  public bool CompareStrings(string str1, string str2) {
57  var compareType = IgnoreCase ? StringComparison.OrdinalIgnoreCase : StringComparison.Ordinal;
58  return String.Equals(str1, str2, compareType);
59  }
60 
61  public void AddTable(IFromTableSource tableSource) {
62  tableSources.Add(tableSource);
63  }
64 
65  public void AddExpression(ExpressionReference expressionReference) {
66  expressionReferences.Add(expressionReference);
67  }
68 
69  public void ExposeColumn(ObjectName columName) {
70  exposedColumns.Add(columName);
71  }
72 
73  public void ExposeColumns(IFromTableSource tableSource) {
74  foreach (var column in tableSource.ColumnNames) {
75  exposedColumns.Add(column);
76  }
77  }
78 
79  public void ExposeColumns(ObjectName tableName) {
80  var schema = tableName.Parent != null ? tableName.Parent.Name : null;
81  IFromTableSource table = FindTable(schema, tableName.Name);
82  if (table == null)
83  throw new InvalidOperationException("Table name found: " + tableName);
84 
85  ExposeColumns(table);
86  }
87 
88  public void ExposeAllColumns() {
89  foreach (var source in tableSources) {
90  ExposeColumns(source);
91  }
92  }
93 
94  public IFromTableSource GetTableSource(int offset) {
95  return tableSources[offset];
96  }
97 
98  public IFromTableSource FindTable(string schema, string name) {
99  return tableSources.FirstOrDefault(x => x.MatchesReference(null, schema, name));
100  }
101 
103  var columnNames = new ObjectName[exposedColumns.Count];
104  exposedColumns.CopyTo(columnNames, 0);
105  return columnNames;
106  }
107 
109  if (alias == null)
110  throw new ArgumentNullException("alias");
111 
112  if (alias.Parent != null)
113  return null;
114 
115  var aliasName = alias.Name;
116 
117  int matchCount = 0;
118  SqlExpression expToRetun = null;
119  foreach (var reference in expressionReferences) {
120  if (matchCount > 1)
121  throw new AmbiguousMatchException(String.Format("Alias '{0}' resolves to multiple expressions in the plan", alias));
122 
123  if (CompareStrings(reference.Alias, aliasName)) {
124  expToRetun = reference.Expression;
125  matchCount++;
126  }
127  }
128 
129  return expToRetun;
130  }
131 
133  var parent = columnName.Parent;
134  var name = columnName.Name;
135  string schemaName = null;
136  string tableName = null;
137  if (parent != null) {
138  tableName = parent.Name;
139  parent = parent.Parent;
140  if (parent != null)
141  schemaName = parent.Name;
142  }
143 
144  ObjectName matchedColumn = null;
145 
146  foreach (var source in tableSources) {
147  int matchCount = source.ResolveColumnCount(null, schemaName, tableName, name);
148  if (matchCount > 1)
149  throw new AmbiguousMatchException();
150 
151  if (matchCount == 1)
152  matchedColumn = source.ResolveColumn(null, schemaName, tableName, name);
153  }
154 
155  return matchedColumn;
156  }
157 
159  if (alias.Parent != null)
160  return null;
161 
162  var aliasName = alias.Name;
163 
164  int matchCount = 0;
165  ObjectName matched = null;
166  foreach (var reference in expressionReferences) {
167  if (matchCount > 1)
168  throw new AmbiguousMatchException();
169 
170  if (CompareStrings(aliasName, reference.Alias)) {
171  matched = new ObjectName(reference.Alias);
172  matchCount++;
173  }
174  }
175 
176  return matched;
177  }
178 
180  var refList = new List<ObjectName>();
181 
182  var resolved = ResolveAliasReference(refName);
183  if (resolved != null)
184  refList.Add(resolved);
185 
186  resolved = ResolveColumnReference(refName);
187  if (resolved != null)
188  refList.Add(resolved);
189 
190  if (refList.Count > 1)
191  throw new AmbiguousMatchException();
192 
193  if (refList.Count == 0)
194  return null;
195 
196  return refList[0];
197  }
198 
200  ObjectName resolvedName = ResolveReference(name);
201  if (resolvedName == null && Parent != null)
202  // If we need to descend to the parent, increment the level.
203  return Parent.GlobalResolveReference(level + 1, name);
204 
205  if (resolvedName != null)
206  return new QueryReference(resolvedName, level);
207 
208  return null;
209  }
210 
211  private object QualifyReference(ObjectName name) {
212  var referenceName = ResolveReference(name);
213  if (referenceName == null) {
214  if (Parent == null)
215  throw new InvalidOperationException(String.Format("Reference {0} was not found in context.", name));
216 
217  var queryRef = GlobalResolveReference(1, name);
218  if (queryRef == null)
219  throw new InvalidOperationException(String.Format("Reference {0} was not found in context.", name));
220 
221  return queryRef;
222  }
223 
224  return referenceName;
225  }
226 
227  public static QueryExpressionFrom Create(IRequest context, SqlQueryExpression expression) {
228  // Get the 'from_clause' from the table expression
229  var fromClause = expression.FromClause;
230  var ignoreCase = context.Query.IgnoreIdentifiersCase();
231 
232  var queryFrom = new QueryExpressionFrom(ignoreCase);
233  foreach (var fromTable in fromClause.AllTables) {
234  var uniqueKey = fromTable.UniqueKey;
235  var alias = fromTable.Alias;
236 
237  if (fromTable.IsSubQuery) {
238  // eg. FROM ( SELECT id FROM Part )
239  var subQuery = fromTable.SubQuery;
240  var subQueryFrom = Create(context, subQuery);
241 
242  // The aliased name of the table
243  ObjectName aliasTableName = null;
244  if (alias != null)
245  aliasTableName = new ObjectName(alias);
246 
247  // Add to list of sub-query tables to add to command,
248  queryFrom.AddTable(new FromTableSubQuerySource(ignoreCase, uniqueKey, subQuery, subQueryFrom, aliasTableName));
249  } else {
250  // Else must be a standard command table,
251  string name = fromTable.Name;
252 
253  // Resolve to full table name
254  var tableName = context.Query.ResolveTableName(name);
255 
256  if (!context.Query.TableExists(tableName))
257  throw new InvalidOperationException(String.Format("Table '{0}' was not found.", tableName));
258 
259  ObjectName givenName = null;
260  if (alias != null)
261  givenName = new ObjectName(alias);
262 
263  // Get the ITableQueryInfo object for this table name (aliased).
264  ITableQueryInfo tableQueryInfo = context.Query.GetTableQueryInfo(tableName, givenName);
265 
266  queryFrom.AddTable(new FromTableDirectSource(ignoreCase, tableQueryInfo, uniqueKey, givenName, tableName));
267  }
268  }
269 
270  // Set up functions, aliases and exposed variables for this from set,
271 
272  foreach (var selectColumn in expression.SelectColumns) {
273  // Is this a glob? (eg. Part.* )
274  if (selectColumn.IsGlob) {
275  // Find the columns globbed and add to the 'selectedColumns' result.
276  if (selectColumn.IsAll) {
277  queryFrom.ExposeAllColumns();
278  } else {
279  // Otherwise the glob must be of the form '[table name].*'
280  queryFrom.ExposeColumns(selectColumn.TableName);
281  }
282  } else {
283  // Otherwise must be a standard column reference. Note that at this
284  // time we aren't sure if a column expression is correlated and is
285  // referencing an outer source. This means we can't verify if the
286  // column expression is valid or not at this point.
287 
288  // If this column is aliased, add it as a function reference to the
289  // select expression
290 
291  string alias = selectColumn.Alias;
292  var v = selectColumn.Expression.AsReferenceName();
293  bool aliasMatchV = (v != null && alias != null &&
294  queryFrom.CompareStrings(v.Name, alias));
295  if (alias != null && !aliasMatchV) {
296  queryFrom.AddExpression(new ExpressionReference(selectColumn.Expression, alias));
297  queryFrom.ExposeColumn(new ObjectName(alias));
298  } else if (v != null) {
299  var resolved = queryFrom.ResolveReference(v);
300  queryFrom.ExposeColumn(resolved ?? v);
301  } else {
302  string funName = selectColumn.Expression.ToString();
303  queryFrom.AddExpression(new ExpressionReference(selectColumn.Expression, funName));
304  queryFrom.ExposeColumn(new ObjectName(funName));
305  }
306  }
307  }
308 
309  return queryFrom;
310  }
311 
312  #region FromExpressionPreparer
313 
315  private readonly QueryExpressionFrom fromSet;
316 
318  this.fromSet = fromSet;
319  }
320 
321  public bool CanPrepare(SqlExpression expression) {
322  return expression is SqlReferenceExpression;
323  }
324 
325  public SqlExpression Prepare(SqlExpression expression) {
326  var refName = ((SqlReferenceExpression) expression).ReferenceName;
327 
328  var reference = fromSet.QualifyReference(refName);
329  if (reference is ObjectName)
330  return SqlExpression.Reference((ObjectName) reference);
331  if (reference is QueryReference)
332  return new QueryReferenceExpression((QueryReference) reference);
333 
334  throw new InvalidOperationException();
335  }
336  }
337 
338  #endregion
339  }
340 }
bool CompareStrings(string str1, string str2)
readonly List< ExpressionReference > expressionReferences
An implementation of IFromTableSource that wraps around a SqlQueryExpression as a sub-query source...
SqlExpression FindExpression(ObjectName alias)
An expression that references an object within a context.
readonly List< ObjectName > exposedColumns
readonly List< IFromTableSource > tableSources
QueryReference GlobalResolveReference(int level, ObjectName name)
IFromTableSource GetTableSource(int offset)
A single table resource item in a query which handles the behaviour of resolving references to column...
ObjectName ResolveReference(ObjectName refName)
void ExposeColumns(IFromTableSource tableSource)
Describes the name of an object within a database.
Definition: ObjectName.cs:44
void AddTable(IFromTableSource tableSource)
ObjectName ResolveColumnReference(ObjectName columnName)
static QueryExpressionFrom Create(IRequest context, SqlQueryExpression expression)
bool CanPrepare(SqlExpression expression)
Verifies whether the instance of the interface can prepare the given expression.
An interface used to prepare a SqlExpression object.
SqlExpression Prepare(SqlExpression expression)
Returns the new translated object to be mutated from the given expression.
ObjectName Parent
Gets the parent reference of the current one, if any or null if none.
Definition: ObjectName.cs:99
string Name
Gets the name of the object being referenced.
Definition: ObjectName.cs:108
static SqlReferenceExpression Reference(ObjectName objectName)
void AddExpression(ExpressionReference expressionReference)
Defines the base class for instances that represent SQL expression tree nodes.
ObjectName ResolveAliasReference(ObjectName alias)
An implementation of IFromTableSource that wraps around a ObjectName/ITable object.
IFromTableSource FindTable(string schema, string name)