} // constructor

        public DataSet executeQuery(Query query)
        {
            List <SelectItem>  selectItems        = query.getSelectClause().getItems();
            List <FromItem>    fromItems          = query.getFromClause().getItems();
            List <FilterItem>  whereItems         = query.getWhereClause().getItems();
            List <SelectItem>  whereSelectItems   = query.getWhereClause().getEvaluatedSelectItems();
            List <GroupByItem> groupByItems       = query.getGroupByClause().getItems();
            List <SelectItem>  groupBySelectItems = query.getGroupByClause().getEvaluatedSelectItems();
            List <SelectItem>  havingSelectItems  = query.getHavingClause().getEvaluatedSelectItems();
            List <SelectItem>  orderBySelectItems = query.getOrderByClause().getEvaluatedSelectItems();

            List <FilterItem>  havingItems  = query.getHavingClause().getItems();
            List <OrderByItem> orderByItems = query.getOrderByClause().getItems();

            int firstRow = (query.getFirstRow() == null ? 1  : query.getFirstRow());
            int maxRows  = (query.getMaxRows() == null ? -1 : query.getMaxRows());

            if (maxRows == 0)
            {
                // no rows requested - no reason to do anything
                return(new EmptyDataSet(selectItems));
            }

            // check certain common query types that can often be optimized by
            // subclasses
            bool singleFromItem = fromItems.Count == 1;
            bool noGrouping     = groupByItems.IsEmpty() && havingItems.IsEmpty();

            if (singleFromItem && noGrouping)
            {
                FromItem fromItem = query.getFromClause().getItem(0);
                Table    table    = fromItem.getTable();
                if (table != null)
                {
                    // check for SELECT COUNT(*) queries
                    if (selectItems.Count == 1)
                    {
                        SelectItem selectItem = query.getSelectClause().getItem(0);
                        if (SelectItem.isCountAllItem(selectItem))
                        {
                            bool functionApproximationAllowed = selectItem.isFunctionApproximationAllowed();
                            if (isMainSchemaTable(table))
                            {
                                logger.debug("Query is a COUNT query with {} where items. Trying executeCountQuery(...)",
                                             whereItems.Count);
                                NNumber count = executeCountQuery(table, whereItems, functionApproximationAllowed);
                                if (count == null)
                                {
                                    logger.debug(
                                        "DataContext did not return any count query results. Proceeding with manual counting.");
                                }
                                else
                                {
                                    List <Row>    data   = new List <Row>(1);
                                    DataSetHeader header = new SimpleDataSetHeader(new SelectItem[] { selectItem });
                                    data.Add(new DefaultRow(header, new Object[] { count }));
                                    return(new InMemoryDataSet(header, data));
                                }
                            }
                        }
                    }

                    bool is_simple_select = isSimpleSelect(query.getSelectClause());
                    if (is_simple_select)
                    {
                        // check for lookup query by primary key
                        if (whereItems.Count == 1)
                        {
                            FilterItem whereItem  = whereItems[0];
                            SelectItem selectItem = whereItem.getSelectItem();
                            if (!whereItem.isCompoundFilter() && selectItem != null && selectItem.getColumn() != null)
                            {
                                Column column = selectItem.getColumn();
                                if (column.isPrimaryKey() && OperatorType.EQUALS_TO.Equals(whereItem.getOperator()))
                                {
                                    logger.debug(
                                        "Query is a primary key lookup query. Trying executePrimaryKeyLookupQuery(...)");
                                    if (table != null)
                                    {
                                        if (isMainSchemaTable(table))
                                        {
                                            Object operand = whereItem.getOperand();
                                            Row    row     = executePrimaryKeyLookupQuery(table, selectItems, column, operand);
                                            if (row == null)
                                            {
                                                logger.debug(
                                                    "DataContext did not return any GET query results. Proceeding with manual lookup.");
                                            }
                                            else
                                            {
                                                DataSetHeader header = new SimpleDataSetHeader(selectItems);
                                                return(new InMemoryDataSet(header, row));
                                            }
                                        }
                                    }
                                }
                            }
                        }

                        // check for simple queries with or without simple criteria
                        if (orderByItems.IsEmpty())
                        {
                            DataSet ds = null;

                            // no WHERE criteria set
                            if (whereItems.IsEmpty())
                            {
                                ds = materializeTable(table, selectItems, firstRow, maxRows);
                                return(ds);
                            }

                            ds = materializeTable(table, selectItems, whereItems, firstRow, maxRows);
                            return(ds);
                        }
                    }
                }
            }

            // Creates a list for all select items that are needed to execute query
            // (some may only be used as part of a filter, but not shown in result)
            List <SelectItem> workSelectItems = CollectionUtils.concat(true, selectItems, whereSelectItems,
                                                                       groupBySelectItems, havingSelectItems, orderBySelectItems);

            // Materialize the tables in the from clause
            DataSet[] fromDataSets = new DataSet[fromItems.Count];
            for (int i = 0; i < fromDataSets.Length; i++)
            {
                FromItem fromItem = fromItems[i];
                fromDataSets[i] = materializeFromItem(fromItem, workSelectItems);
            }

            // Execute the query using the raw data
            DataSet dataSet = null; // MetaModelHelper.getCarthesianProduct(fromDataSets, whereItems);

            // we can now exclude the select items imposed by the WHERE clause (and
            // should, to make the aggregation process faster)
            workSelectItems = CollectionUtils.concat(true, selectItems, groupBySelectItems, havingSelectItems,
                                                     orderBySelectItems);

            if (groupByItems.Count > 0)
            {
                dataSet = MetaModelHelper.getGrouped(workSelectItems, dataSet, groupByItems);
            }
            else
            {
                dataSet = MetaModelHelper.getAggregated(workSelectItems, dataSet);
            }
            dataSet = MetaModelHelper.getFiltered(dataSet, havingItems);

            if (query.getSelectClause().isDistinct())
            {
                dataSet = MetaModelHelper.getSelection(selectItems, dataSet);
                dataSet = MetaModelHelper.getDistinct(dataSet);
                dataSet = MetaModelHelper.getOrdered(dataSet, orderByItems);
            }
            else
            {
                dataSet = MetaModelHelper.getOrdered(dataSet, orderByItems);
                dataSet = MetaModelHelper.getSelection(selectItems, dataSet);
            }

            dataSet = MetaModelHelper.getPaged(dataSet, firstRow, maxRows);
            return(dataSet);
        } // executeQuery()
        } // getInformationSchema()

        public virtual DataSet materializeInformationSchemaTable(Table table, List <SelectItem> selectItems)
        {
            String tableName = table.getName();

            SelectItem[]        columnSelectItems = MetaModelHelper.createSelectItems(table.getColumns());
            SimpleDataSetHeader header            = new SimpleDataSetHeader(columnSelectItems);

            Table[]    tables = getDefaultSchema().getTables(false);
            List <Row> data   = new List <Row>();

            if ("tables".Equals(tableName))
            {
                // "tables" columns: name, type, num_columns, remarks
                foreach (Table t in tables)
                {
                    String typeString = null;
                    if (t.GetType() != null)
                    {
                        typeString = t.getType().ToString();
                    }
                    data.Add(new DefaultRow(header,
                                            new Object[] { t.getName(), typeString, t.getColumnCount(), t.getRemarks() }));
                }
            }
            else if ("columns".Equals(tableName))
            {
                // "columns" columns: name, type, native_type, size, nullable,
                // indexed, table, remarks
                foreach (Table t in tables)
                {
                    foreach (Column c in t.getColumns())
                    {
                        String typeString = null;
                        if (t.GetType() != null)
                        {
                            typeString = c.getType().ToString();
                        }
                        data.Add(new DefaultRow(header, new Object[] { c.getName(), typeString, c.getNativeType(),
                                                                       c.getColumnSize(), c.isNullable(), c.isIndexed(), t.getName(), c.getRemarks() }));
                    }
                }
            }
            else if ("relationships".Equals(tableName))
            {
                // "relationships" columns: primary_table, primary_column,
                // foreign_table, foreign_column
                foreach (Relationship r in getDefaultSchema().getRelationships())
                {
                    Column[] primaryColumns = r.getPrimaryColumns();
                    Column[] foreignColumns = r.getForeignColumns();
                    Table    pTable         = r.getPrimaryTable();
                    Table    fTable         = r.getForeignTable();
                    for (int i = 0; i < primaryColumns.Length; i++)
                    {
                        Column pColumn = primaryColumns[i];
                        Column fColumn = foreignColumns[i];
                        data.Add(new DefaultRow(header,
                                                new Object[] { pTable.getName(), pColumn.getName(), fTable.getName(), fColumn.getName() }));
                    }
                }
            }
            else
            {
                throw new ArgumentException("Cannot materialize non information_schema table: " + table);
            }

            DataSet dataSet;

            if (data.IsEmpty())
            {
                dataSet = new EmptyDataSet(selectItems);
            }
            else
            {
                dataSet = new InMemoryDataSet(header, data);
            }

            // Handle column subset
            DataSet selectionDataSet = MetaModelHelper.getSelection(selectItems, dataSet);

            dataSet = selectionDataSet;

            return(dataSet);
        } // materializeInformationSchemaTable()
        }     // getGrouped()

        /**
         * Applies aggregate values to a dataset. This method is to be invoked AFTER
         * any filters have been applied.
         *
         * @param workSelectItems
         *            all select items included in the processing of the query
         *            (including those originating from other clauses than the
         *            SELECT clause).
         * @param dataSet
         * @return
         */
        public static DataSet getAggregated(List <SelectItem> workSelectItems, DataSet dataSet)
        {
            List <SelectItem> functionItems = getAggregateFunctionSelectItems(workSelectItems);

            if (functionItems.IsEmpty())
            {
                return(dataSet);
            }

            AggregateBuilder <Object> t;
            Dictionary <SelectItem, AggregateBuilder <Object> > aggregateBuilders = new Dictionary <SelectItem, AggregateBuilder <Object> >();

            foreach (SelectItem item in functionItems)
            {
                aggregateBuilders.Add(item, item.getAggregateFunction().createAggregateBuilder <object>());
            }

            DataSetHeader header;
            bool          onlyAggregates;

            if (functionItems.Count != workSelectItems.Count)
            {
                onlyAggregates = false;
                header         = new CachingDataSetHeader(workSelectItems);
            }
            else
            {
                onlyAggregates = true;
                header         = new SimpleDataSetHeader(workSelectItems);
            }

            List <Row> resultRows = new List <Row>();

            while (dataSet.next())
            {
                Row inputRow = dataSet.getRow();
                foreach (SelectItem item in functionItems)
                {
                    AggregateBuilder <object> aggregateBuilder = aggregateBuilders[item];
                    Column column = item.getColumn();
                    if (column != null)
                    {
                        Object value = inputRow.getValue(new SelectItem(column));
                        aggregateBuilder.add(value);
                    }
                    else if (SelectItem.isCountAllItem(item))
                    {
                        // Just use the empty string, since COUNT(*) don't
                        // evaluate values (but null values should be prevented)
                        aggregateBuilder.add("");
                    }
                    else
                    {
                        throw new ArgumentException("Expression function not supported: " + item);
                    }
                }

                // If the result should also contain non-aggregated values, we
                // will keep those in the rows list
                if (!onlyAggregates)
                {
                    Object[] values = new Object[header.size()];
                    for (int i = 0; i < header.size(); i++)
                    {
                        Object value = inputRow.getValue(header.getSelectItem(i));
                        if (value != null)
                        {
                            values[i] = value;
                        }
                    }
                    resultRows.Add(new DefaultRow(header, values));
                }
            }
            dataSet.close();

            // Collect the aggregates
            Dictionary <SelectItem, Object> functionResult = new Dictionary <SelectItem, Object>();

            foreach (SelectItem item in functionItems)
            {
                AggregateBuilder <object> aggregateBuilder = aggregateBuilders[item];
                Object result = aggregateBuilder.getAggregate();
                functionResult.Add(item, result);
            }

            // if there are no result rows (no matching records at all), we still
            // need to return a record with the aggregates
            bool noResultRows = resultRows.IsEmpty();

            if (onlyAggregates || noResultRows)
            {
                // We will only create a single row with all the aggregates
                Object[] values = new Object[header.size()];
                for (int i = 0; i < header.size(); i++)
                {
                    values[i] = functionResult[header.getSelectItem(i)];
                }
                Row row = new DefaultRow(header, values);
                resultRows.Add(row);
            }
            else
            {
                // We will create the aggregates as well as regular values
                for (int i = 0; i < resultRows.Count; i++)
                {
                    Row      row    = resultRows[i];
                    Object[] values = row.getValues();
                    foreach (KeyValuePair <SelectItem, Object> entry in functionResult)
                    {
                        SelectItem item      = entry.Key;
                        int        itemIndex = row.indexOf(item);
                        if (itemIndex != -1)
                        {
                            Object value = entry.Value;
                            values[itemIndex] = value;
                        }
                    }
                    resultRows[i] = new DefaultRow(header, values);
                }
            }
            return(new InMemoryDataSet(header, resultRows));
        }     // getAggregated()