/**
  * @param string $direction
  * @param object $object
  * @param integer $pageLimit
  * @return array
  */
 protected function makePagingLink($direction, $object, $pageLimit)
 {
     $return = array('offset-dir' => $direction, 'limit' => $pageLimit, $this->offsetKey => $this->storage->serializeOffset($object, $this->sort));
     if (isset($this->options['sortby'])) {
         $return['sortby'] = $this->options['sortby'];
     }
     return $return;
 }
 public function merge(array $query, array $result)
 {
     $query = ObjectManager::splitFromRow($query, $this->queryKeys);
     if ($query === null) {
         // the queryKeys are either unset or null, and not indexable
         // TODO: what should happen here?
         return;
     }
     $this->result[$query] = $result;
 }
 public function canAnswer(array $keys, array $options)
 {
     if (!parent::canAnswer($keys, $options)) {
         return false;
     }
     if (isset($options['sort'], $options['order'])) {
         return ObjectManager::makeArray($options['sort']) === $this->options['sort'] && strtoupper($options['order']) === $this->options['order'];
     }
     return true;
 }
 public function fromStorageRow(array $row, $object = null)
 {
     $pk = ObjectManager::splitFromRow($row, $this->primaryKey);
     if ($pk === null) {
         throw new \InvalidArgumentException('Storage row has no pk');
     } elseif (!isset($this->loaded[$pk])) {
         // unserialize the object
         return $this->loaded[$pk] = call_user_func($this->fromStorageRow, $row, $object);
     } elseif ($object === null) {
         // provide previously loaded object
         return $this->loaded[$pk];
     } elseif ($object !== $this->loaded[$pk]) {
         // loaded object of this id is not same object
         $class = get_class($object);
         $id = json_encode($pk);
         throw new \InvalidArgumentException("Duplicate '{$class}' objects for id {$id}");
     } else {
         // object was provided, load $row into $object
         // we already know $this->loaded[$pk] === $object
         return call_user_func($this->fromStorageRow, $row, $object);
     }
 }
 /**
  * Calculates the DB updates to be performed to update data from $old to
  * $new.
  *
  * @param array $old
  * @param array $new
  * @return array
  * @throws DataModelException
  */
 public function calcUpdates(array $old, array $new)
 {
     $changeSet = ObjectManager::calcUpdatesWithoutValidation($old, $new);
     foreach ($this->obsoleteUpdateColumns as $val) {
         // Need to use array_key_exists to check null value
         if (array_key_exists($val, $changeSet)) {
             unset($changeSet[$val]);
         }
     }
     if (is_array($this->allowedUpdateColumns)) {
         $extra = array_diff(array_keys($changeSet), $this->allowedUpdateColumns);
         if ($extra) {
             throw new DataModelException('Update not allowed on: ' . implode(', ', $extra), 'process-data');
         }
     }
     return $changeSet;
 }
 /**
  * {@inheritDoc}
  */
 public function onAfterRemove($object, array $old, array $metadata)
 {
     $indexed = ObjectManager::splitFromRow($old, $this->indexed);
     if (!$indexed) {
         throw new DataModelException('Unindexable row: ' . FormatJson::encode($old), 'process-data');
     }
     $this->removeFromIndex($indexed, $old);
 }
 /**
  * Fetch a node and all its descendants. Children are returned in the
  * same order they were inserted.
  *
  * @param UUID|UUID[] $roots
  * @return array Multi-dimensional tree. The top level is a map from the uuid of a node
  *  to attributes about that node.  The top level contains not just the parents, but all nodes
  *  within this tree. Within each node there is a 'children' key that contains a map from
  *  the child uuid's to references back to the top level of this identity map. As such this
  *  result can be read either as a list or a tree.
  * @throws DataModelException When invalid data is received from self::fetchSubtreeNodeList
  */
 public function fetchSubtreeIdentityMap($roots)
 {
     $roots = ObjectManager::makeArray($roots);
     if (!$roots) {
         return array();
     }
     $nodes = $this->fetchSubtreeNodeList($roots);
     if (!$nodes) {
         throw new DataModelException('subtree node list should have at least returned root: ' . $root, 'process-data');
     } elseif (count($nodes) === 1) {
         $parentMap = $this->fetchParentMap(reset($nodes));
     } else {
         $parentMap = $this->fetchParentMap(call_user_func_array('array_merge', $nodes));
     }
     $identityMap = array();
     foreach ($parentMap as $child => $parent) {
         if (!array_key_exists($child, $identityMap)) {
             $identityMap[$child] = array('children' => array());
         }
         // Root nodes have no parent
         if ($parent !== null) {
             $identityMap[$parent->getAlphadecimal()]['children'][$child] =& $identityMap[$child];
         }
     }
     foreach (array_keys($identityMap) as $parent) {
         ksort($identityMap[$parent]['children']);
     }
     return $identityMap;
 }
 /**
  * Takes an array of rows going to/from the database/cache.  Converts uuid and
  * things that look like uuids into the requested format.
  *
  * @param array $array
  * @param string $format
  * @return string[]|UUIDBlob[] Typically an array of strings.  If required by the database when
  *  $format === 'binary' uuid values will be represented as Blob objects.
  */
 public static function convertUUIDs($array, $format = 'binary')
 {
     $array = ObjectManager::makeArray($array);
     foreach ($array as $key => $value) {
         if ($value instanceof UUIDBlob) {
             // database encoded binary value
             if ($format === 'alphadecimal') {
                 $array[$key] = UUID::create($value->fetch())->getAlphadecimal();
             }
         } elseif ($value instanceof UUID) {
             if ($format === 'binary') {
                 $array[$key] = $value->getBinary();
             } elseif ($format === 'alphadecimal') {
                 $array[$key] = $value->getAlphadecimal();
             }
         } elseif (is_string($value) && substr($key, -3) === '_id') {
             // things that look like uuids
             $len = strlen($value);
             if ($format === 'alphadecimal' && $len === self::BIN_LEN) {
                 $array[$key] = UUID::create($value)->getAlphadecimal();
             } elseif ($format === 'binary' && ($len >= self::MIN_ALNUM_LEN && $len <= self::ALNUM_LEN || $len === self::HEX_LEN)) {
                 // Note that if a value is a binary string, but needs to be encoded
                 // for the database, that is unhandled here.  A patch is under
                 // consideration to allow binary data to always be wrapped in a Blob
                 // to clear up this inconsistency.
                 $array[$key] = UUID::create($value)->getBinary();
             }
         }
     }
     return $array;
 }
 public function findMulti(array $queries, array $options = array())
 {
     $keys = array_keys(reset($queries));
     $pks = $this->getPrimaryKeyColumns();
     if (count($keys) !== count($pks) || array_diff($keys, $pks)) {
         return $this->fallbackFindMulti($queries, $options);
     }
     $conds = array();
     $dbr = $this->dbFactory->getDB(DB_SLAVE);
     foreach ($queries as $query) {
         $conds[] = $dbr->makeList($this->preprocessSqlArray($query), LIST_AND);
     }
     unset($query);
     $conds = $dbr->makeList($conds, LIST_OR);
     $result = array();
     // options can be ignored for primary key search
     $res = $this->find(array(new RawSql($conds)));
     if (!$res) {
         return $result;
     }
     // create temp array with pk value (usually uuid) as key and full db row
     // as value
     $temp = new MultiDimArray();
     foreach ($res as $val) {
         $val = UUID::convertUUIDs($val, 'alphadecimal');
         $temp[ObjectManager::splitFromRow($val, $this->primaryKey)] = $val;
     }
     // build return value by mapping the database rows to the matching array
     // index in $queries
     foreach ($queries as $i => $val) {
         $val = UUID::convertUUIDs($val, 'alphadecimal');
         $pk = ObjectManager::splitFromRow($val, $this->primaryKey);
         $result[$i][] = isset($temp[$pk]) ? $temp[$pk] : null;
     }
     return $result;
 }
 /**
  * Gets the required updates.  Any changes to External Store will be reflected in
  * the returned array.
  *
  * @param array $old Associative array mapping prior columns to old values
  * @param array $new Associative array mapping updated columns to new values
  *
  * @return array Validated change set as associative array, mapping columns to
  *   change to their new values
  */
 public function calcUpdates(array $old, array $new)
 {
     // First, see if there are any changes to content at all.
     // If not, processExternalStore will know not to insert a useless row for
     // unchanged content (if updating content is allowed).
     $unvalidatedChangeset = ObjectManager::calcUpdatesWithoutValidation($old, $new);
     // We check here so if it's not allowed, we don't insert a wasted External
     // Store entry, then throw an exception in the parent calcUpdates.
     if ($this->isUpdatingExistingRevisionContentAllowed()) {
         $unvalidatedChangeset = $this->processExternalStore($unvalidatedChangeset);
     }
     // The parent calcUpdates does the validation that we're not changing a non-allowed
     // field, regardless of whether explicitly passed in, or done by processExternalStore.
     $validatedChangeset = parent::calcUpdates(array(), $unvalidatedChangeset);
     return $validatedChangeset;
 }
 /**
  * Returns a boolean true/false if the getMulti()-operation for the given
  * attributes has already been resolves and doesn't need to query any
  * outside cache/database.
  * Determining if a find() has not yet been resolved may be useful so that
  * additional data may be loaded at once.
  *
  * @param array $objectIds Ids to getMulti()
  * @return bool
  */
 public function gotMulti(array $objectIds)
 {
     if (!$objectIds) {
         return true;
     }
     $primaryKey = $this->storage->getPrimaryKeyColumns();
     $queries = array();
     foreach ($objectIds as $id) {
         $query = array_combine($primaryKey, ObjectManager::makeArray($id));
         $query = UUID::convertUUIDs($query, 'alphadecimal');
         if (!$this->mapper->get($query)) {
             $queries[] = $query;
         }
     }
     if ($queries && $this->mapper instanceof Mapper\CachingObjectMapper) {
         return false;
     }
     return $this->foundMulti($queries);
 }