protected function registerRecordImpl(array &$records = NULL, $record) {
        parent::registerRecordImpl($records, $record);

        $recordKey = NULL;
        foreach ($this->keyColumnNames as $keyColumnName) {
            $recordKey[] = isset($record[$keyColumnName]) ? $record[$keyColumnName] : NULL;
        }

        $key = ArrayHelper::prepareCompositeKey($recordKey);
        if (isset($records[$key])) {
            if ($this->isColumnValueUnique) {
                throw new IllegalArgumentException(t(
                	'Found several records for the key: %key',
                    array('%key' => ArrayHelper::serialize($recordKey, ', ', TRUE, TRUE))));
            }

            $records[$key][] = $record;
        }
        else {
            if ($this->isColumnValueUnique) {
                $records[$key] = $record;
            }
            else {
                $records[$key][] = $record;
            }
        }

        return TRUE;
    }
    public function replaceColumnNames(ParserCallback $callback, &$callerSession) {
        $columnName = $callback->marker;

        $column = $this->columnReferenceFactory->getColumn($columnName);
        $this->approveColumn4ParticipationInExpression($column);

        if ($column->persistence == FormulaMetaData::PERSISTENCE__CALCULATED) {
            // checking if the column is already in the stack
            if (in_array($column->name, $this->columnAssemblingStack)) {
                $columnPublicNames = NULL;
                // adding columns from stack
                foreach ($this->columnAssemblingStack as $stackColumnName) {
                    $stackColumn = $this->dataset->getColumn($stackColumnName);
                    $columnPublicNames[] = $stackColumn->publicName;
                }
                // adding current processed column
                $columnPublicNames[] = $column->publicName;

                throw new IllegalStateException(t(
                    'Circular reference in formula expression is not allowed: %stack',
                    array('%stack' => ArrayHelper::serialize($columnPublicNames, ', ', TRUE, FALSE))));
            }

            array_push($this->columnAssemblingStack, $column->name);

            $language = isset($column->expressionLanguage) ? $column->expressionLanguage : NULL;
            $parser = new FormulaExpressionParser($language);

            $expression = $parser->expressionLanguageHandler->clean($column->source);

            $callback->marker = $parser->parse($expression, array($this, 'replaceColumnNames'));

            array_pop($this->columnAssemblingStack);

            $callback->removeDelimiters = TRUE;
        }
    }
    protected function loadColumnsProperties(SystemTableMetaModelLoaderCallContext $callcontext, DataSourceMetaData $datasource) {
        $tableNames = NULL;
        foreach ($callcontext->datasets as $dataset) {
            $tableNames[] = $dataset->sourse;
        }
        if (!isset($tableNames)) {
            return NULL;
        }

        $sql =
            'SELECT c.relname AS ' . self::CN_TABLE_NAME . ",\n" .
            '       a.attname AS ' . self::CN_COLUMN_NAME . ",\n" .
            '       a.attnum AS ' . self::CN_COLUMN_INDEX . ",\n" .
            '       t.typname AS ' . self::CN_COLUMN_TYPE .
            "  FROM pg_class c INNER JOIN pg_namespace ns ON ns.oid = c.relnamespace\n" .
            "       INNER JOIN pg_attribute a ON a.attrelid = c.oid\n" .
            "       INNER JOIN pg_type t ON t.oid = a.atttypid\n" .
            " WHERE c.relkind IN ('r','v')\n" .
            "   AND ns.nspname = '$datasource->schema'\n" .
            "   AND a.attnum > 0\n" .
            '   AND c.relname IN (' .  ArrayHelper::serialize($tableNames) . ')';

        return $this->executeQuery($datasource, 'table.column', $sql);
    }
    protected function loadConstraintColumnsProperties(SystemTableMetaModelLoaderCallContext $callcontext, DataSourceMetaData $datasource, array $constraintTypes) {
        $eligibleOwners = $this->getEligibleOwners(
            $callcontext,
            (isset($datasource->schema) ? $datasource->schema : NULL));
        $ineligibleOwners = $this->getIneligibleOwners($callcontext);

        $types = $this->convertConstraintTypes($constraintTypes);

        $sql =
            'SELECT c.constraint_schema AS ' . self::CN_TABLE_OWNER . ",\n" .
            '       cu.constraint_name AS ' . self::CN_OBJECT_NAME . ",\n" .
            '       cu.table_name AS ' . self::CN_TABLE_NAME . ",\n" .
            '       cu.column_name AS ' . self::CN_COLUMN_NAME . ",\n" .
            '       (cu.ordinal_position - 1) AS ' . self::CN_COLUMN_INDEX . "\n" .
            "  FROM information_schema.key_column_usage cu JOIN information_schema.table_constraints c ON cu.constraint_name = c.constraint_name AND cu.constraint_catalog = c.constraint_catalog\n" .
            " WHERE cu.table_catalog = '{$datasource->database}'\n" .
            "   AND cu.constraint_catalog = cu.table_catalog\n" .
            '   AND c.constraint_type IN (' . ArrayHelper::serialize($types) . ')';
        $this->appendOwnerStatementCondition($sql, TRUE, 'c.constraint_schema', $eligibleOwners, $ineligibleOwners);

        return $this->executeQuery($datasource, 'constraint.column', $sql);
    }
    public function findNestedLinkByDatasetNameAndParentColumnNames($datasetName, $parentColumnNames) {
        $selectedNestedLink = NULL;

        if (isset($this->nestedLinks)) {
            $parentColumnCount = count($parentColumnNames);
            foreach ($this->nestedLinks as $nestedLink) {
                if ($nestedLink->dataset->name !== $datasetName) {
                    continue;
                }

                $nestedLinkParentColumnCount = count($nestedLink->parentColumnNames);
                if ($nestedLinkParentColumnCount != $parentColumnCount) {
                    continue;
                }

                foreach ($nestedLink->parentColumnNames as $parentColumnIndex => $nestedLinkParentColumnName) {
                    if (!isset($parentColumnNames[$parentColumnIndex])) {
                        continue 2;
                    }

                    if ($nestedLinkParentColumnName != $parentColumnNames[$parentColumnIndex]) {
                        continue 2;
                    }
                }

                if (isset($selectedNestedLink)) {
                    throw new UnsupportedOperationException(t(
                        'Found several nested reference links for %datasetName dataset by the parent columns: %parentColumns',
                        array(
                            '%datasetName' => $datasetName,
                            '%parentColumns' => ArrayHelper::serialize($parentColumnNames, ', ', TRUE, FALSE))));
                }

                $selectedNestedLink = $nestedLink;
            }
        }

        return $selectedNestedLink;
    }
    protected function loadConstraintColumnsProperties(SystemTableMetaModelLoaderCallContext $callcontext, DataSourceMetaData $datasource, array $constraintTypes) {
        $eligibleOwners = $this->getEligibleOwners($callcontext, $datasource->username);
        $ineligibleOwners = $this->getIneligibleOwners($callcontext);

        $types = $this->convertConstraintTypes($constraintTypes);

        $sql =
            'SELECT c.owner AS ' . self::CN_TABLE_OWNER . ",\n" .
            '       cc.constraint_name AS ' . self::CN_OBJECT_NAME . ",\n" .
            '       cc.table_name AS ' . self::CN_TABLE_NAME . ",\n" .
            '       cc.column_name AS ' . self::CN_COLUMN_NAME . ",\n" .
            '       (cc.position - 1) AS ' . self::CN_COLUMN_INDEX . "\n" .
            "  FROM all_cons_columns cc JOIN all_constraints c ON cc.constraint_name = c.constraint_name AND cc.owner = c.owner\n" .
            ' WHERE c.constraint_type IN (' . ArrayHelper::serialize($types) . ')';
        $this->appendOwnerStatementCondition($sql, TRUE, 'c.owner', $eligibleOwners, $ineligibleOwners);

        return $this->executeQuery($datasource, 'constraint.column', $sql);
    }
    protected function prepareCacheEntryName() {
        $entryName = NULL;

        $filters = $this->getMetaModelFilters();
        if (isset($filters)) {
            $suffix = NULL;

            ksort($filters);
            foreach ($filters as $className => $properties) {
                if (isset($suffix)) {
                    $suffix .= ',';
                }
                $suffix .= $className . '{';

                $propertySuffix = NULL;

                ksort($properties);
                foreach ($properties as $propertyName => $filterValues) {
                    sort($filterValues);

                    if (isset($propertySuffix)) {
                        $propertySuffix .= ',';
                    }
                    $propertySuffix .= $propertyName . '=' . ArrayHelper::serialize($filterValues, ',', TRUE, FALSE);
                }

                $suffix .= $propertySuffix . '}';
            }

            $entryName = $suffix;
        }

        return $entryName;
    }
    public function getValues(array $names, array $options = NULL) {
        $timeStart = microtime(TRUE);

        $values = parent::getValues($names, $options);

        $nameCount = count($names);
        $loadedValueCount = count($values);

        LogHelper::log_debug(t(
            '[@cacheType] Requested entries: @entryNames',
            array(
                '@cacheType' => $this->getCacheType(),
                '@entryNames' => ArrayHelper::serialize(array_values($names), ', ', TRUE, FALSE))));
        LogHelper::log_debug(
            t('[@cacheType] Retrieved entries: @entryNames',
                array(
                    '@cacheType' => $this->getCacheType(),
                    '@entryNames' => (($nameCount == $loadedValueCount)
                        ? 'ALL'
                        : (isset($values) ? ArrayHelper::serialize(array_keys($values), ', ', TRUE, FALSE) : t('NONE'))))));
        LogHelper::log_info(t(
            '[@cacheType] Execution time for retrieving @entryCount entry(-ies) is !executionTime@successFlag',
            array(
                '@cacheType' => $this->getCacheType(),
                '@entryCount' => $nameCount,
                '!executionTime' => LogHelper::formatExecutionTime($timeStart),
                '@successFlag' => (
                    isset($values)
                        ? (($nameCount == $loadedValueCount)
                            ? ''
                            : t(" (cache hit for ONLY @loadedValueCount entry(-ies) out of @nameCount)", array('@loadedValueCount' => $loadedValueCount, '@nameCount' => $nameCount)))
                        : (' (' . t('cache was NOT hit') . ')')))));

        return $values;
    }
    protected function selectDataType(array $datatypes, $selectCompatible = TRUE) {
        // eliminating null records from the array
        if (isset($datatypes)) {
            foreach ($datatypes as $index => $datatype) {
                if (!isset($datatype)) {
                    unset($datatypes[$index]);
                }
            }
            if (count($datatypes) == 0) {
                $datatypes = NULL;
            }
        }
        if (!isset($datatypes)) {
            return NULL;
        }

        // checking if all elements are equal
        $selectedDataType = NULL;
        foreach ($datatypes as $datatype) {
            if (isset($selectedDataType)) {
                if ($selectedDataType != $datatype) {
                    $selectedDataType = NULL;
                    break;
                }
            }
            else {
                $selectedDataType = $datatype;
            }
        }

        if (isset($selectedDataType)) {
            return $selectedDataType;
        }

        // checking if we need to return compatible type
        if (!$selectCompatible) {
            // if some types are not compatible we need to return the lowest compatible type
            if (!$this->areCompatible($datatypes)) {
                $selectCompatible = TRUE;
            }
        }

        $selectedDataTypes = $datatypes;
        if ($selectCompatible) {
            do {
                $initialDataTypeCount = count($selectedDataTypes);
                $selectedDataTypes = $this->selectCompatible($selectedDataTypes);
                $selectedDataTypeCount = count($selectedDataTypes);
                if (($initialDataTypeCount > 1) && ($initialDataTypeCount == $selectedDataTypeCount)) {
                    throw new IncompatibleDataTypeException(t(
                    	'Data types are inter-compatible. Single type could not be selected: %datatypes',
                        array('%datatypes' => ArrayHelper::serialize($datatypes, ',', TRUE, FALSE))));
                }
            }
            while ($selectedDataTypeCount > 1);
        }
        else {
            // it is expected that we have only 2 types here
            while (($datatypeCount = count($selectedDataTypes)) >= 2) {
                $datatypeA = $selectedDataTypes[0];
                $datatypeB = $selectedDataTypes[1];
                $selectedByDataTypeA = $this->getHandler($datatypeA)->selectCompatible($datatypeB);
                $selectedByDataTypeB = $this->getHandler($datatypeB)->selectCompatible($datatypeA);

                $selectedDataType = NULL;
                if (isset($selectedByDataTypeA)) {
                    if (isset($selectedByDataTypeB)) {
                        throw new IncompatibleDataTypeException(t(
                        	'%datatypeA and %datatypeB data types are inter-compatible. Single type could not be selected',
                            array('%datatypeA' => $datatypeA, '%datatypeB' => $datatypeB)));
                    }
                    else {
                        $selectedDataType = $selectedByDataTypeA;
                    }
                }
                elseif (isset($selectedByDataTypeB)) {
                    $selectedDataType = $selectedByDataTypeB;
                }
                else {
                    throw new UnsupportedOperationException();
                }

                $selectedDataTypes = array_slice($selectedDataTypes, 2);
                // selecting opposite data type. We do not need the lowest compatible type
                $selectedDataTypes[] = ($selectedDataType == $datatypeA) ? $datatypeB : $datatypeA;
            }
        }

        if (count($selectedDataTypes) == 1) {
            return $selectedDataTypes[0];
        }
        else {
            throw new IncompatibleDataTypeException(t(
            	'Incompatible data types: %datatypes',
                array('%datatypes' => ArrayHelper::serialize($datatypes, ',', TRUE, FALSE))));
        }
    }
    protected function loadIdentifiers($lookupDatasetName, array $uniqueSetColumns, array &$lookupValues) {
        $dataQueryController = data_controller_get_instance();
        $metamodel = data_controller_get_metamodel();

        $lookupDataset = $metamodel->getDataset($lookupDatasetName);
        $identifierColumnName = $lookupDataset->getKeyColumn()->name;

        $lookupCacheKey = $this->prepareLookupCacheKey($lookupDataset->name);

        $isCompositeUniqueSet = count($uniqueSetColumns) > 1;

        // preparing parameters for the query
        $queryParameters = NULL;
        foreach ($lookupValues as $lookupKey => $lookupValue) {
            if (isset($lookupValue->identifier)) {
                continue;
            }

            if (isset($this->cachedIdentifiers[$lookupCacheKey][$lookupKey])) {
                $lookupValues[$lookupKey]->identifier = $this->cachedIdentifiers[$lookupCacheKey][$lookupKey];
                continue;
            }

            if ($isCompositeUniqueSet) {
                $keyColumnValues = NULL;
                foreach ($uniqueSetColumns as $column) {
                    $columnName = $column->name;
                    $keyColumnValues[$columnName] = $lookupValue->$columnName;
                }

                $queryParameters[] = $keyColumnValues;
            }
            else {
                $columnName = $uniqueSetColumns[0]->name;
                $queryParameters[$columnName][] = $lookupValue->$columnName;
            }
        }
        if (!isset($queryParameters)) {
            return;
        }

        // preparing columns for the query
        $queryColumns = array($identifierColumnName);
        foreach ($uniqueSetColumns as $column) {
            ArrayHelper::addUniqueValue($queryColumns, $column->name);
        }

        // loading data from database for 'missing' records
        $loadedLookupProperties = $dataQueryController->queryDataset($lookupDataset->name, $queryColumns, $queryParameters);

        // processing found records
        if (isset($loadedLookupProperties)) {
            $foundUnmatchedIdentifiers = FALSE;
            foreach ($loadedLookupProperties as $lookupProperties) {
                $identifier = $lookupProperties[$identifierColumnName];

                // preparing lookup key
                $keyItems = NULL;
                foreach ($uniqueSetColumns as $column) {
                    $keyItems[] = $lookupProperties[$column->name];
                }
                $lookupKey = self::prepareLookupKey($keyItems);

                if (!isset($lookupValues[$lookupKey])) {
                    if (count($lookupValues) == 1) {
                        // 04/23/2014 if only one record requested and one record received, but the received key does not match the request
                        // it means that character encoding functionality is more sophisticated on server and we actually have a match

                        // storing the value into cache for further usage
                        $this->cachedIdentifiers[$lookupCacheKey][$lookupKey] = $identifier;

                        reset($lookupValues);
                        $alternativeLookupKey = key($lookupValues);
                        $lookupKey = $alternativeLookupKey;
                    }
                    else {
                        $foundUnmatchedIdentifiers = TRUE;
                        continue;
                    }
                }
                if (isset($lookupValues[$lookupKey]->identifier)) {
                    $searchCriteria = array();
                    foreach ($uniqueSetColumns as $column) {
                        $searchCriteria[$column->name] = $lookupProperties[$column->name];
                    }
                    LogHelper::log_error(t(
                        'Key: @searchCriteria. Loaded identifiers: @identifiers',
                        array(
                            '@searchCriteria' => ArrayHelper::serialize($searchCriteria, ', ', TRUE, FALSE),
                            '@identifiers' => ArrayHelper::serialize(array($lookupValues[$lookupKey]->identifier, $identifier), ', ', TRUE, FALSE))));
                    throw new IllegalArgumentException(t(
                        'Several records in %datasetName dataset match search criteria',
                        array('%datasetName' => $lookupDataset->publicName)));
                }
                $lookupValues[$lookupKey]->identifier = $identifier;

                // storing the value into cache for further usage
                $this->cachedIdentifiers[$lookupCacheKey][$lookupKey] = $identifier;
            }

            // found unmatched values. Processing unprocessed lookups one by one
            if ($foundUnmatchedIdentifiers) {
                foreach ($lookupValues as $lookupKey => $lookupValue) {
                    if (!isset($lookupValue->identifier)) {
                        $singleLookupValue = array($lookupKey => $lookupValue);
                        $this->loadIdentifiers($lookupDatasetName, $uniqueSetColumns, $singleLookupValue);
                    }
                }
            }

            $this->freeSpaceInIdentifierCache($lookupDataset->name);
        }
    }
    public function permitDatasetStorageTruncation(DataControllerCallContext $callcontext, DatasetMetaData $logicalDataset) {
        parent::permitDatasetStorageTruncation($callcontext, $logicalDataset);

        if (!self::isLookupDataset($logicalDataset)) {
            return;
        }

        $dataQueryController = data_controller_get_instance();
        $metamodel = data_controller_get_metamodel();

        // checking of the reference datasets have any NOT NULL data in reference columns
        $populatedReferenceDatasetPublicNames = NULL;
        foreach ($metamodel->datasets as $dataset) {
            foreach ($dataset->getColumns(FALSE, TRUE) as $column) {
                if ($column->persistence != ColumnMetaData::PERSISTENCE__STORAGE_CREATED) {
                    continue;
                }
                if ($column->type->getReferencedDatasetName() == $logicalDataset->name) {
                    $recordCount = $dataQueryController->countDatasetRecords(
                        $dataset->name,
                        array($column->name => OperatorFactory::getInstance()->initiateHandler(NotEmptyOperatorHandler::OPERATOR__NAME)));
                    if ($recordCount > 0) {
                        $populatedReferenceDatasetPublicNames[] = $dataset->publicName;
                        break;
                    }
                }
            }
        }

        // we should not allow to truncate the dataset if there is any data in any reference datasets
        if (isset($populatedReferenceDatasetPublicNames)) {
            throw new IllegalArgumentException(t(
                "%datasetName dataset is referenced by other datasets. The dataset truncation is not permitted unless records in %referenceDatasetNames datasets are deleted first",
                array(
                    '%datasetName' => $logicalDataset->publicName,
                    '%referenceDatasetNames' => ArrayHelper::serialize($populatedReferenceDatasetPublicNames))));
        }
    }
    public function getValues(array $names, array $options = NULL) {
        $this->checkAccessibility(TRUE);

        $cacheEntryNameMap = NULL;
        foreach ($names as $name) {
            $cacheEntryName = $this->assembleCacheEntryName($name);
            $cacheEntryNameMap[$cacheEntryName] = $name;
        }
        if (!isset($cacheEntryNameMap)) {
            return NULL;
        }

        $result = NULL;
        try {
            $cacheEntries = $this->loadValues(array_keys($cacheEntryNameMap), $options);

            foreach ($cacheEntryNameMap as $cacheEntryName => $name) {
                if (!isset($cacheEntries[$cacheEntryName])) {
                    continue;
                }

                $value = $cacheEntries[$cacheEntryName];

                $result[$name] = $value;

                unset($cacheEntries[$cacheEntryName]);
            }

            // checking for unknown returned entries which are still in the list (were not processed)
            if (isset($cacheEntries)) {
                $unrequestedCacheEntryNames = array_keys($cacheEntries);
                if (count($unrequestedCacheEntryNames) > 0) {
                    throw new IllegalStateException(t(
                        'Received unrequested data for the cache entry names: %cacheEntryName',
                        array('%cacheEntryName' => ArrayHelper::serialize($unrequestedCacheEntryNames, ', ', TRUE, FALSE))));
                }
            }
        }
        catch (Exception $e) {
            LogHelper::log_error($e);
        }

        return $result;
    }