DeveelDB  20151217
complete SQL database system, primarly developed for .NET/Mono frameworks
FunctionTable.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 
21 using Deveel.Data.Caching;
22 using Deveel.Data.Index;
24 using Deveel.Data.Types;
25 
26 namespace Deveel.Data.Sql.Tables {
28  private readonly IRequest context;
30  private readonly TableInfo funTableInfo;
31  private bool wholeTableIsSimpleEnum;
32  private readonly int uniqueId;
33  private readonly byte[] expInfo;
34  private readonly SqlExpression[] expList;
35  private readonly int rowCount;
36 
37  private IList<int> groupLinks;
38  private IList<int> groupLookup;
40  private bool wholeTableAsGroup;
41  private IList<int> wholeTableGroup;
42  private int wholeTableGroupSize;
43 
44  private static readonly ObjectName FunctionTableName = new ObjectName(null, "FUNCTIONTABLE");
45 
46  private static int uniqueKeySeq = 0;
47 
48  public FunctionTable(SqlExpression[] functionList, string[] columnNames, IRequest queryContext)
49  : this(queryContext.Query.Session.Transaction.Database.SingleRowTable, functionList, columnNames, queryContext) {
50  }
51 
52  public FunctionTable(ITable table, SqlExpression[] functionList, string[] columnNames, IRequest queryContext)
53  : base(queryContext.Context) {
54  // Make sure we are synchronized over the class.
55  lock (typeof(FunctionTable)) {
56  uniqueId = uniqueKeySeq;
57  ++uniqueKeySeq;
58  }
59 
60  uniqueId = (uniqueId & 0x0FFFFFFF) | 0x010000000;
61 
62  context = queryContext;
63 
64  ReferenceTable = table;
65  varResolver = table.GetVariableResolver();
66  varResolver = varResolver.ForRow(0);
67 
68  // Create a DataTableInfo object for this function table.
69  funTableInfo = new TableInfo(FunctionTableName);
70 
71  expList = new SqlExpression[functionList.Length];
72  expInfo = new byte[functionList.Length];
73 
74  // Create a new DataColumnInfo for each expression, and work out if the
75  // expression is simple or not.
76  for (int i = 0; i < functionList.Length; ++i) {
77  var expr = functionList[i];
78  // Examine the expression and determine if it is simple or not
79  if (expr.IsConstant() && !expr.HasAggregate(context)) {
80  // If expression is a constant, solve it
81  var result = expr.Evaluate(context, null);
82  if (result.ExpressionType != SqlExpressionType.Constant)
83  throw new InvalidOperationException();
84 
85  expr = result;
86  expList[i] = expr;
87  expInfo[i] = 1;
88  } else {
89  // Otherwise must be dynamic
90  expList[i] = expr;
91  expInfo[i] = 0;
92  }
93 
94  // Make the column info
95  funTableInfo.AddColumn(columnNames[i], expr.ReturnType(context, varResolver));
96  }
97 
98  // Make sure the table info isn't changed from this point on.
99  funTableInfo = funTableInfo.AsReadOnly();
100 
101  // routine tables are the size of the referring table.
102  rowCount = table.RowCount;
103 
104  // Set schemes to 'blind search'.
105  SetupIndexes(DefaultIndexTypes.BlindSearch);
106  }
107 
108  public override IEnumerator<Row> GetEnumerator() {
109  return new SimpleRowEnumerator(this);
110  }
111 
112  public ITable ReferenceTable { get; private set; }
113 
114  public override TableInfo TableInfo {
115  get { return funTableInfo; }
116  }
117 
118  public override int RowCount {
119  get { return rowCount; }
120  }
121 
122  public override void Lock() {
123  // We Lock the reference table.
124  // NOTE: This cause the reference table to Lock twice when we use the
125  // 'MergeWithReference' method. While this isn't perfect behaviour, it
126  // means if 'MergeWithReference' isn't used, we still maintain a safe
127  // level of locking.
129  }
130 
131  public override void Release() {
132  // We unlock the reference table.
133  // NOTE: This cause the reference table to unlock twice when we use the
134  // 'MergeWithReference' method. While this isn't perfect behaviour, it
135  // means if 'MergeWithReference' isn't used, we still maintain a safe
136  // level of locking.
138  }
139 
141  return Context.ResolveService<ITableCellCache>();
142  }
143 
144  public override DataObject GetValue(long rowNumber, int columnOffset) {
145  // [ FUNCTION TABLE CACHING NOW USES THE GLOBAL CELL CACHING MECHANISM ]
146  // Check if in the cache,
147  var cache = TableCellCache();
148 
149  // Is the column worth caching, and is caching enabled?
150  if (expInfo[columnOffset] == 0 && cache != null) {
151  DataObject cell;
152  if (cache.TryGetValue(context.Query.Session.Transaction.Database.Name, uniqueId, (int)rowNumber, columnOffset, out cell))
153  // In the cache so return the cell.
154  return cell;
155 
156  // Not in the cache so calculate the value and write it in the cache.
157  return CalcValue((int)rowNumber, columnOffset, cache);
158  }
159 
160  // Caching is not enabled
161  return CalcValue((int)rowNumber, columnOffset, null);
162 
163  }
164 
165  private DataObject CalcValue(int row, int column, ITableCellCache cache) {
166  var resolver = varResolver.ForRow(row);
167 
168  if (groupResolver != null) {
169  groupResolver.SetUpGroupForRow(row);
170  }
171 
172  var expr = expList[column];
173  var exp = expr.Evaluate(context, resolver, groupResolver);
174  if (exp.ExpressionType != SqlExpressionType.Constant)
175  throw new ArgumentException();
176 
177  var value = ((SqlConstantExpression) exp).Value;
178  if (cache != null)
179  cache.Set(context.Query.Session.Transaction.Database.Name, uniqueId, row, column, value);
180 
181  return value;
182  }
183 
184  private int GetRowGroup(int rowIndex) {
185  return groupLookup[rowIndex];
186  }
187 
188  private int GetGroupSize(int groupNumber) {
189  int groupSize = 1;
190  int i = groupLinks[groupNumber];
191  while ((i & 0x040000000) == 0) {
192  ++groupSize;
193  ++groupNumber;
194  i = groupLinks[groupNumber];
195  }
196  return groupSize;
197  }
198 
199  private IList<int> GetGroupRows(int groupNumber) {
200  var rows = new List<int>();
201  var row = groupLinks[groupNumber];
202 
203  while ((row & 0x040000000) == 0) {
204  rows.Add(row);
205  ++groupNumber;
206  row = groupLinks[groupNumber];
207  }
208 
209  rows.Add(row & 0x03FFFFFFF);
210  return rows;
211  }
212 
213  private IList<int> GetTopRowsFromEachGroup() {
214  var extractRows = new List<int>();
215  var size = groupLinks.Count;
216  var take = true;
217 
218  for (int i = 0; i < size; ++i) {
219  int r = groupLinks[i];
220  if (take)
221  extractRows.Add(r & 0x03FFFFFFF);
222 
223  take = (r & 0x040000000) != 0;
224  }
225 
226  return extractRows;
227  }
228 
229  private IList<int> GetMaxFromEachGroup(int colNum) {
230  var refTab = ReferenceTable;
231 
232  var extractRows = new List<int>();
233  var size = groupLinks.Count;
234 
235  int toTakeInGroup = -1;
236  DataObject max = null;
237 
238  for (int i = 0; i < size; ++i) {
239  int row = groupLinks[i];
240 
241  int actRIndex = row & 0x03FFFFFFF;
242  var cell = refTab.GetValue(actRIndex, colNum);
243 
244  if (max == null || cell.CompareTo(max) > 0) {
245  max = cell;
246  toTakeInGroup = actRIndex;
247  }
248 
249  if ((row & 0x040000000) != 0) {
250  extractRows.Add(toTakeInGroup);
251  max = null;
252  }
253  }
254 
255  return extractRows;
256  }
257 
258  public ITable MergeWith(ObjectName maxColumn) {
259  var table = ReferenceTable;
260 
261  IList<int> rowList;
262 
263  if (wholeTableAsGroup) {
264  // Whole table is group, so take top entry of table.
265 
266  rowList = new List<int>(1);
267  var rowEnum = table.GetEnumerator();
268  if (rowEnum.MoveNext()) {
269  rowList.Add(rowEnum.Current.RowId.RowNumber);
270  } else {
271  // MAJOR HACK: If the referencing table has no elements then we choose
272  // an arbitary index from the reference table to merge so we have
273  // at least one element in the table.
274  // This is to fix the 'SELECT COUNT(*) FROM empty_table' bug.
275  rowList.Add(Int32.MaxValue - 1);
276  }
277  } else if (table.RowCount == 0) {
278  rowList = new List<int>(0);
279  } else if (groupLinks != null) {
280  // If we are grouping, reduce down to only include one row from each
281  // group.
282  if (maxColumn == null) {
283  rowList = GetTopRowsFromEachGroup();
284  } else {
285  var colNum = ReferenceTable.FindColumn(maxColumn);
286  rowList = GetMaxFromEachGroup(colNum);
287  }
288  } else {
289  // OPTIMIZATION: This should be optimized. It should be fairly trivial
290  // to generate a Table implementation that efficiently merges this
291  // function table with the reference table.
292 
293  // This means there is no grouping, so merge with entire table,
294  int rowCount = table.RowCount;
295  rowList = new List<int>(rowCount);
296  var en = table.GetEnumerator();
297  while (en.MoveNext()) {
298  rowList.Add(en.Current.RowId.RowNumber);
299  }
300  }
301 
302  // Create a virtual table that's the new group table merged with the
303  // functions in this...
304 
305  var tabs = new [] { table, this };
306  var rowSets = new[] { rowList, rowList };
307 
308  return new VirtualTable(tabs, rowSets);
309  }
310 
312  // TODO: create a new table ...
313  wholeTableAsGroup = true;
314 
315  wholeTableGroupSize = ReferenceTable.RowCount;
316 
317  // Set up 'whole_table_group' to the list of all rows in the reference
318  // table.
319  var en = ReferenceTable.GetEnumerator();
320  wholeTableIsSimpleEnum = en is SimpleRowEnumerator;
321  if (!wholeTableIsSimpleEnum) {
322  wholeTableGroup = new List<int>(ReferenceTable.RowCount);
323  while (en.MoveNext()) {
324  wholeTableGroup.Add(en.Current.RowId.RowNumber);
325  }
326  }
327 
328  // Set up a group resolver for this method.
329  groupResolver = new TableGroupResolver(this);
330  return this;
331  }
332 
334  // If we have zero rows, then don't bother creating the matrix.
335  if (RowCount <= 0 || columns.Length <= 0)
336  return this;
337 
338  var rootTable = ReferenceTable;
339  int rowCount = rootTable.RowCount;
340  int[] colLookup = new int[columns.Length];
341  for (int i = columns.Length - 1; i >= 0; --i) {
342  colLookup[i] = rootTable.IndexOfColumn(columns[i]);
343  }
344 
345  var rowList = rootTable.OrderRowsByColumns(colLookup).ToList();
346 
347  // 'row_list' now contains rows in this table sorted by the columns to
348  // group by.
349 
350  // This algorithm will generate two lists. The group_lookup list maps
351  // from rows in this table to the group number the row belongs in. The
352  // group number can be used as an index to the 'group_links' list that
353  // contains consequtive links to each row in the group until -1 is reached
354  // indicating the end of the group;
355 
356  groupLookup = new List<int>(rowCount);
357  groupLinks = new List<int>(rowCount);
358  int currentGroup = 0;
359  int previousRow = -1;
360  for (int i = 0; i < rowCount; i++) {
361  var rowIndex = rowList[i];
362 
363  if (previousRow != -1) {
364  bool equal = true;
365  // Compare cell in column in this row with previous row.
366  for (int n = 0; n < colLookup.Length && equal; ++n) {
367  var c1 = rootTable.GetValue(rowIndex, colLookup[n]);
368  var c2 = rootTable.GetValue(previousRow, colLookup[n]);
369  equal = (c1.CompareTo(c2) == 0);
370  }
371 
372  if (!equal) {
373  // If end of group, set bit 15
374  groupLinks.Add(previousRow | 0x040000000);
375  currentGroup = groupLinks.Count;
376  } else {
377  groupLinks.Add(previousRow);
378  }
379  }
380 
381  // groupLookup.Insert(row_index, current_group);
382  PlaceAt(groupLookup, rowIndex, currentGroup);
383 
384  previousRow = rowIndex;
385  }
386 
387  // Add the final row.
388  groupLinks.Add(previousRow | 0x040000000);
389 
390  // Set up a group resolver for this method.
391  groupResolver = new TableGroupResolver(this);
392 
393  return this;
394  }
395 
396  private static void PlaceAt(IList<int> list, int index, int value) {
397  while (index > list.Count) {
398  list.Add(0);
399  }
400 
401  list.Insert(index, value);
402  }
403 
404  public static ITable ResultTable(IRequest context, SqlExpression expression) {
405  var exp = new [] { expression };
406  var names = new[] { "result" };
407  var table = new FunctionTable(exp, names, context);
408 
409  return new SubsetColumnTable(table, new[]{0}, new []{new ObjectName("result") });
410  }
411 
412  public static ITable ResultTable(IRequest context, DataObject value) {
413  return ResultTable(context, SqlExpression.Constant(value));
414  }
415 
416  public static ITable ResultTable(IRequest context, int value) {
417  return ResultTable(context, DataObject.Integer(value));
418  }
419 
420  #region TableGroupResolver
421 
423  private IList<int> group;
425 
427  Table = table;
428  GroupId = -1;
429  }
430 
431  public FunctionTable Table { get; private set; }
432 
433  public int GroupId { get; private set; }
434 
435  public int Count {
436  get {
437  if (GroupId == -2)
438  return Table.wholeTableGroupSize;
439  if (group != null)
440  return group.Count;
441 
442  return Table.GetGroupSize(GroupId);
443  }
444  }
445 
446  private void EnsureGroup() {
447  if (group == null) {
448  if (GroupId == -2) {
449  group = Table.wholeTableGroup;
450  } else {
451  group = Table.GetGroupRows(GroupId);
452  }
453  }
454  }
455 
456  public DataObject Resolve(ObjectName variable, int setIndex) {
457  int colIndex = Table.ReferenceTable.FindColumn(variable);
458  if (colIndex == -1)
459  throw new InvalidOperationException(String.Format("Column {0} not found in table {1}.", variable, Table.TableName));
460 
461  EnsureGroup();
462 
463  int rowIndex = setIndex;
464  if (group != null)
465  rowIndex = group[setIndex];
466 
467  return Table.ReferenceTable.GetValue(rowIndex, colIndex);
468  }
469 
471  if (groupVarResolver == null)
472  groupVarResolver = new GroupVariableResolver(this);
473 
474  return groupVarResolver;
475  }
476 
477  public IVariableResolver GetVariableResolver(int setIndex) {
478  var resolver = CreateVariableResolver();
479  resolver = resolver.ForRow(setIndex);
480  return resolver;
481  }
482 
483  public void SetUpGroupForRow(int rowIndex) {
484  if (Table.wholeTableAsGroup) {
485  if (GroupId != -2) {
486  GroupId = -2;
487  group = null;
488  }
489  } else {
490  int g = Table.GetRowGroup(rowIndex);
491  if (g != GroupId) {
492  GroupId = g;
493  group = null;
494  }
495  }
496  }
497 
498  #region GroupVariableResolver
499 
502  private readonly int rowIndex;
503 
505  : this(groupResolver, -1) {
506  }
507 
508  public GroupVariableResolver(TableGroupResolver groupResolver, int rowIndex) {
509  this.groupResolver = groupResolver;
510  this.rowIndex = rowIndex;
511  }
512 
513  public DataObject Resolve(ObjectName variable) {
514  if (rowIndex < 0)
515  throw new InvalidOperationException();
516 
517  return groupResolver.Resolve(variable, rowIndex);
518  }
519 
520  public SqlType ReturnType(ObjectName variable) {
521  var columnOffset = groupResolver.Table.FindColumn(variable);
522  if (columnOffset < 0)
523  throw new InvalidOperationException(String.Format("Cannot find column {0} in table {1}", variable,
524  groupResolver.Table.TableName));
525 
526  return groupResolver.Table.TableInfo[columnOffset].ColumnType;
527  }
528 
529  public ITableVariableResolver ForRow(int rowNum) {
530  return new GroupVariableResolver(groupResolver, rowNum);
531  }
532  }
533 
534  #endregion
535  }
536 
537  #endregion
538  }
539 }
abstract DataObject GetValue(long rowNumber, int columnOffset)
Gets a single cell within the table that is located at the given column offset and row...
override IEnumerator< Row > GetEnumerator()
Definition: FilterTable.cs:32
static DataObject Integer(int value)
Definition: DataObject.cs:576
Defines the contract to access the data contained into a table of a database.
Definition: ITable.cs:40
static ITable ResultTable(IRequest context, DataObject value)
ITable MergeWith(ObjectName maxColumn)
FunctionTable(SqlExpression[] functionList, string[] columnNames, IRequest queryContext)
A long string in the system.
FunctionTable CreateGroupMatrix(ObjectName[] columns)
SqlType ReturnType(ObjectName variable)
Returns the SqlType of object the given variable is.
static void PlaceAt(IList< int > list, int index, int value)
static ITable ResultTable(IRequest context, SqlExpression expression)
int FindColumn(ObjectName columnName)
Definition: Table.cs:143
IVariableResolver GetVariableResolver(int setIndex)
Returns a IVariableResolver that can be used to resolve variable in the get set of the group...
The default implementation of a database in a system.
Definition: Database.cs:38
Describes the name of an object within a database.
Definition: ObjectName.cs:44
DataObject Resolve(ObjectName variable)
Returns the value of a given variable.
DataObject CalcValue(int row, int column, ITableCellCache cache)
DataObject Resolve(ObjectName variable, int setIndex)
Returns the value of a variable of a group.
override DataObject GetValue(long rowNumber, int columnOffset)
Gets a single cell within the table that is located at the given column offset and row...
IList< int > GetMaxFromEachGroup(int colNum)
SqlExpressionType
All the possible type of SqlExpression supported
This is a session that is constructed around a given user and a transaction, to the given database...
Definition: Session.cs:32
GroupVariableResolver(TableGroupResolver groupResolver, int rowIndex)
virtual SqlExpression Evaluate(EvaluateContext context)
When overridden by a derived class, this method evaluates the expression within the provided context...
int CompareTo(DataObject other)
Definition: DataObject.cs:131
Defines a contract used by grouping functions to find information about the current group being evalu...
Represents a dynamic object that encapsulates a defined SqlType and a compatible constant ISqlObject ...
Definition: DataObject.cs:35
IList< int > GetGroupRows(int groupNumber)
readonly ITableVariableResolver varResolver
Defines the properties of a specific SQL Type and handles the values compatible.
Definition: SqlType.cs:33
int RowCount
Gets the total number of rows in the table.
Definition: ITable.cs:52
An interface to resolve a variable name to a constant object.
An expression that holds a constant value.
Defines the base class for instances that represent SQL expression tree nodes.
static SqlConstantExpression Constant(object value)
static ITable ResultTable(IRequest context, int value)
Defines the metadata properties of a table existing within a database.
Definition: TableInfo.cs:41
DataObject Add(DataObject other)
Adds the given value to this object value.
Definition: DataObject.cs:383
override IEnumerator< Row > GetEnumerator()
readonly SqlExpression[] expList
FunctionTable(ITable table, SqlExpression[] functionList, string[] columnNames, IRequest queryContext)