public function testLastInsertIDFromStatement() { Zotero_DB::query("CREATE TABLE test (foo INTEGER PRIMARY KEY NOT NULL AUTO_INCREMENT, foo2 INTEGER NOT NULL)"); $sql = "INSERT INTO test VALUES (NULL, ?)"; $stmt = Zotero_DB::getStatement($sql, true); $insertID = Zotero_DB::queryFromStatement($stmt, array(1)); $this->assertEquals($insertID, 1); $insertID = Zotero_DB::queryFromStatement($stmt, array(2)); $this->assertEquals($insertID, 2); }
private static function loadItems($libraryID, $itemIDs = array()) { $shardID = Zotero_Shards::getByLibraryID($libraryID); $sql = self::getPrimaryDataSQL() . "1"; // TODO: optimize if ($itemIDs) { foreach ($itemIDs as $itemID) { if (!is_int($itemID)) { throw new Exception("Invalid itemID {$itemID}"); } } $sql .= ' AND itemID IN (' . implode(',', array_fill(0, sizeOf($itemIDs), '?')) . ')'; } $stmt = Zotero_DB::getStatement($sql, "loadItems_" . sizeOf($itemIDs), $shardID); $itemRows = Zotero_DB::queryFromStatement($stmt, $itemIDs); $loadedItemIDs = array(); if ($itemRows) { foreach ($itemRows as $row) { if ($row['libraryID'] != $libraryID) { throw new Exception("Item {$itemID} isn't in library {$libraryID}", Z_ERROR_OBJECT_LIBRARY_MISMATCH); } $itemID = $row['id']; $loadedItemIDs[] = $itemID; // Item isn't loaded -- create new object and stuff in array if (!isset(self::$objectCache[$itemID])) { $item = new Zotero_Item(); $item->loadFromRow($row, true); self::$objectCache[$itemID] = $item; } else { self::$objectCache[$itemID]->loadFromRow($row, true); } } } if (!$itemIDs) { // If loading all items, remove old items that no longer exist $ids = array_keys(self::$objectCache); foreach ($ids as $id) { if (!in_array($id, $loadedItemIDs)) { throw new Exception("Unimplemented"); //$this->unload($id); } } } }
protected function loadCreators($reload = false) { if ($this->loaded['creators'] && !$reload) return; if (!$this->id) { trigger_error('Item ID not set for item before attempting to load creators', E_USER_ERROR); } if (!is_numeric($this->id)) { trigger_error("Invalid itemID '$this->id'", E_USER_ERROR); } if ($this->cacheEnabled) { $cacheVersion = 1; $cacheKey = $this->getCacheKey("itemCreators", $cacheVersion); $creators = Z_Core::$MC->get($cacheKey); } else { $creators = false; } if ($creators === false) { $sql = "SELECT creatorID, creatorTypeID, orderIndex FROM itemCreators WHERE itemID=? ORDER BY orderIndex"; $stmt = Zotero_DB::getStatement($sql, true, Zotero_Shards::getByLibraryID($this->libraryID)); $creators = Zotero_DB::queryFromStatement($stmt, $this->id); if ($this->cacheEnabled) { Z_Core::$MC->set($cacheKey, $creators ? $creators : array()); } } $this->creators = []; $this->loaded['creators'] = true; $this->clearChanged('creators'); if (!$creators) { return; } foreach ($creators as $creator) { $creatorObj = Zotero_Creators::get($this->libraryID, $creator['creatorID'], true); if (!$creatorObj) { Z_Core::$MC->delete($cacheKey); throw new Exception("Creator {$creator['creatorID']} not found"); } $this->creators[$creator['orderIndex']] = array( 'creatorTypeID' => $creator['creatorTypeID'], 'ref' => $creatorObj ); } }
private function loadCreators() { if (!$this->id) { trigger_error('Item ID not set for item before attempting to load creators', E_USER_ERROR); } if (!is_numeric($this->id)) { trigger_error("Invalid itemID '{$this->id}'", E_USER_ERROR); } $cacheKey = $this->getCacheKey("itemCreators"); $creators = Z_Core::$MC->get($cacheKey); //var_dump($creators); if ($creators === false) { $sql = "SELECT creatorID, creatorTypeID, orderIndex FROM itemCreators\n\t\t\t\t\tWHERE itemID=? ORDER BY orderIndex"; $stmt = Zotero_DB::getStatement($sql, true, Zotero_Shards::getByLibraryID($this->libraryID)); $creators = Zotero_DB::queryFromStatement($stmt, $this->id); Z_Core::$MC->set($cacheKey, $creators ? $creators : array()); } $this->creators = array(); $this->loaded['creators'] = true; if (!$creators) { return; } foreach ($creators as $creator) { $creatorObj = Zotero_Creators::get($this->libraryID, $creator['creatorID'], true); if (!$creatorObj) { Z_Core::$MC->delete($cacheKey); throw new Exception("Creator {$creator['creatorID']} not found"); } $this->creators[$creator['orderIndex']] = array('creatorTypeID' => $creator['creatorTypeID'], 'ref' => $creatorObj); } }
public function save($full = false) { if (!$this->libraryID) { trigger_error("Library ID must be set before saving", E_USER_ERROR); } Zotero_Tags::editCheck($this); if (!$this->changed) { Z_Core::debug("Tag {$this->id} has not changed"); return false; } $shardID = Zotero_Shards::getByLibraryID($this->libraryID); Zotero_DB::beginTransaction(); try { $tagID = $this->id ? $this->id : Zotero_ID::get('tags'); $isNew = !$this->id; Z_Core::debug("Saving tag {$tagID}"); $key = $this->key ? $this->key : $this->generateKey(); $timestamp = Zotero_DB::getTransactionTimestamp(); $dateAdded = $this->dateAdded ? $this->dateAdded : $timestamp; $dateModified = $this->dateModified ? $this->dateModified : $timestamp; $fields = "name=?, `type`=?, dateAdded=?, dateModified=?,\n\t\t\t\tlibraryID=?, `key`=?, serverDateModified=?"; $params = array($this->name, $this->type ? $this->type : 0, $dateAdded, $dateModified, $this->libraryID, $key, $timestamp); try { if ($isNew) { $sql = "INSERT INTO tags SET tagID=?, {$fields}"; $stmt = Zotero_DB::getStatement($sql, true, $shardID); Zotero_DB::queryFromStatement($stmt, array_merge(array($tagID), $params)); // Remove from delete log if it's there $sql = "DELETE FROM syncDeleteLogKeys WHERE libraryID=? AND objectType='tag' AND `key`=?"; Zotero_DB::query($sql, array($this->libraryID, $key), $shardID); } else { $sql = "UPDATE tags SET {$fields} WHERE tagID=?"; $stmt = Zotero_DB::getStatement($sql, true, Zotero_Shards::getByLibraryID($this->libraryID)); Zotero_DB::queryFromStatement($stmt, array_merge($params, array($tagID))); } } catch (Exception $e) { // If an incoming tag is the same as an existing tag, but with a different key, // then delete the old tag and add its linked items to the new tag if (preg_match("/Duplicate entry .+ for key 'uniqueTags'/", $e->getMessage())) { // GET existing tag $existing = Zotero_Tags::getIDs($this->libraryID, $this->name); if (!$existing) { throw new Exception("Existing tag not found"); } foreach ($existing as $id) { $tag = Zotero_Tags::get($this->libraryID, $id, true); if ($tag->__get('type') == $this->type) { $linked = $tag->getLinkedItems(true); Zotero_Tags::delete($this->libraryID, $tag->key); break; } } // Save again if ($isNew) { $sql = "INSERT INTO tags SET tagID=?, {$fields}"; $stmt = Zotero_DB::getStatement($sql, true, $shardID); Zotero_DB::queryFromStatement($stmt, array_merge(array($tagID), $params)); // Remove from delete log if it's there $sql = "DELETE FROM syncDeleteLogKeys WHERE libraryID=? AND objectType='tag' AND `key`=?"; Zotero_DB::query($sql, array($this->libraryID, $key), $shardID); } else { $sql = "UPDATE tags SET {$fields} WHERE tagID=?"; $stmt = Zotero_DB::getStatement($sql, true, Zotero_Shards::getByLibraryID($this->libraryID)); Zotero_DB::queryFromStatement($stmt, array_merge($params, array($tagID))); } $new = array_unique(array_merge($linked, $this->getLinkedItems(true))); $this->setLinkedItems($new); } else { throw $e; } } // Linked items if ($full || !empty($this->changed['linkedItems'])) { $removed = array(); $newids = array(); $currentIDs = $this->getLinkedItems(true); if (!$currentIDs) { $currentIDs = array(); } if ($full) { $sql = "SELECT itemID FROM itemTags WHERE tagID=?"; $stmt = Zotero_DB::getStatement($sql, true, $shardID); $dbItemIDs = Zotero_DB::columnQueryFromStatement($stmt, $tagID); if ($dbItemIDs) { $removed = array_diff($dbItemIDs, $currentIDs); $newids = array_diff($currentIDs, $dbItemIDs); } else { $newids = $currentIDs; } } else { if ($this->previousData['linkedItems']) { $removed = array_diff($this->previousData['linkedItems'], $currentIDs); $newids = array_diff($currentIDs, $this->previousData['linkedItems']); } else { $newids = $currentIDs; } } if ($removed) { $sql = "DELETE FROM itemTags WHERE tagID=? AND itemID IN ("; $q = array_fill(0, sizeOf($removed), '?'); $sql .= implode(', ', $q) . ")"; Zotero_DB::query($sql, array_merge(array($this->id), $removed), $shardID); } if ($newids) { $newids = array_values($newids); $sql = "INSERT INTO itemTags (tagID, itemID) VALUES "; $maxInsertGroups = 50; Zotero_DB::bulkInsert($sql, $newids, $maxInsertGroups, $tagID, $shardID); } //Zotero.Notifier.trigger('add', 'collection-item', $this->id . '-' . $itemID); } Zotero_DB::commit(); Zotero_Tags::cachePrimaryData(array('id' => $tagID, 'libraryID' => $this->libraryID, 'key' => $key, 'name' => $this->name, 'type' => $this->type ? $this->type : 0, 'dateAdded' => $dateAdded, 'dateModified' => $dateModified)); } catch (Exception $e) { Zotero_DB::rollback(); throw $e; } // If successful, set values in object if (!$this->id) { $this->id = $tagID; } if (!$this->key) { $this->key = $key; } $this->init(); if ($isNew) { Zotero_Tags::cache($this); Zotero_Tags::cacheLibraryKeyID($this->libraryID, $key, $tagID); } return $this->id; }
public function save($fixGaps = false) { if (!$this->libraryID) { throw new Exception("Library ID must be set before saving"); } Zotero_Searches::editCheck($this); if (!$this->changed) { Z_Core::debug("Search {$this->id} has not changed"); return false; } if (!isset($this->name) || $this->name === '') { throw new Exception("Name not provided for saved search"); } $shardID = Zotero_Shards::getByLibraryID($this->libraryID); Zotero_DB::beginTransaction(); $isNew = !$this->id || !$this->exists(); try { $searchID = $this->id ? $this->id : Zotero_ID::get('savedSearches'); Z_Core::debug("Saving search {$this->id}"); if (!$isNew) { $sql = "DELETE FROM savedSearchConditions WHERE searchID=?"; Zotero_DB::query($sql, $searchID, $shardID); } $key = $this->key ? $this->key : $this->generateKey(); $fields = "searchName=?, libraryID=?, `key`=?, dateAdded=?, dateModified=?,\n\t\t\t\t\t\tserverDateModified=?"; $timestamp = Zotero_DB::getTransactionTimestamp(); $params = array($this->name, $this->libraryID, $key, $this->dateAdded ? $this->dateAdded : $timestamp, $this->dateModified ? $this->dateModified : $timestamp, $timestamp); $shardID = Zotero_Shards::getByLibraryID($this->libraryID); if ($isNew) { $sql = "INSERT INTO savedSearches SET searchID=?, {$fields}"; $stmt = Zotero_DB::getStatement($sql, true, $shardID); Zotero_DB::queryFromStatement($stmt, array_merge(array($searchID), $params)); Zotero_Searches::cacheLibraryKeyID($this->libraryID, $key, $searchID); // Remove from delete log if it's there $sql = "DELETE FROM syncDeleteLogKeys WHERE libraryID=? AND objectType='search' AND `key`=?"; Zotero_DB::query($sql, array($this->libraryID, $key), $shardID); } else { $sql = "UPDATE savedSearches SET {$fields} WHERE searchID=?"; $stmt = Zotero_DB::getStatement($sql, true, $shardID); Zotero_DB::queryFromStatement($stmt, array_merge($params, array($searchID))); } // Close gaps in savedSearchIDs $saveConditions = array(); $i = 1; foreach ($this->conditions as $id => $condition) { if (!$fixGaps && $id != $i) { trigger_error('searchConditionIDs not contiguous and |fixGaps| not set in save() of saved search ' . $this->id, E_USER_ERROR); } $saveConditions[$i] = $condition; $i++; } $this->conditions = $saveConditions; // TODO: use proper bound parameters once DB class is updated foreach ($this->conditions as $searchConditionID => $condition) { $sql = "INSERT INTO savedSearchConditions (searchID,\n\t\t\t\t\t\tsearchConditionID, `condition`, mode, operator,\n\t\t\t\t\t\tvalue, required) VALUES (?,?,?,?,?,?,?)"; $sqlParams = array($searchID, $searchConditionID, $condition['condition'], $condition['mode'] ? $condition['mode'] : '', $condition['operator'] ? $condition['operator'] : '', $condition['value'] ? $condition['value'] : '', $condition['required'] ? 1 : 0); try { Zotero_DB::query($sql, $sqlParams, $shardID); } catch (Exception $e) { $msg = $e->getMessage(); if (strpos($msg, "Data too long for column 'value'") !== false) { throw new Exception("=Value '" . mb_substr($condition['value'], 0, 75) . "…' too long in saved search '" . $this->name . "'"); } throw $e; } } Zotero_DB::commit(); } catch (Exception $e) { Zotero_DB::rollback(); throw $e; } // If successful, set values in object if (!$this->id) { $this->id = $searchID; } if (!$this->key) { $this->key = $key; } return $this->id; }
public function save($userID = false) { if (!$this->libraryID) { throw new Exception("Library ID must be set before saving"); } Zotero_Searches::editCheck($this, $userID); if (!$this->changed) { Z_Core::debug("Search {$this->id} has not changed"); return false; } if (!isset($this->name) || $this->name === '') { throw new Exception("Name not provided for saved search"); } $shardID = Zotero_Shards::getByLibraryID($this->libraryID); Zotero_DB::beginTransaction(); $isNew = !$this->id || !$this->exists(); try { $searchID = $this->id ? $this->id : Zotero_ID::get('savedSearches'); Z_Core::debug("Saving search {$this->id}"); if (!$isNew) { $sql = "DELETE FROM savedSearchConditions WHERE searchID=?"; Zotero_DB::query($sql, $searchID, $shardID); } $key = $this->key ? $this->key : Zotero_ID::getKey(); $fields = "searchName=?, libraryID=?, `key`=?, dateAdded=?, dateModified=?,\n\t\t\t\t\t\tserverDateModified=?, version=?"; $timestamp = Zotero_DB::getTransactionTimestamp(); $params = array($this->name, $this->libraryID, $key, $this->dateAdded ? $this->dateAdded : $timestamp, $this->dateModified ? $this->dateModified : $timestamp, $timestamp, Zotero_Libraries::getUpdatedVersion($this->libraryID)); $shardID = Zotero_Shards::getByLibraryID($this->libraryID); if ($isNew) { $sql = "INSERT INTO savedSearches SET searchID=?, {$fields}"; $stmt = Zotero_DB::getStatement($sql, true, $shardID); Zotero_DB::queryFromStatement($stmt, array_merge(array($searchID), $params)); // Remove from delete log if it's there $sql = "DELETE FROM syncDeleteLogKeys WHERE libraryID=? AND objectType='search' AND `key`=?"; Zotero_DB::query($sql, array($this->libraryID, $key), $shardID); } else { $sql = "UPDATE savedSearches SET {$fields} WHERE searchID=?"; $stmt = Zotero_DB::getStatement($sql, true, $shardID); Zotero_DB::queryFromStatement($stmt, array_merge($params, array($searchID))); } foreach ($this->conditions as $searchConditionID => $condition) { $sql = "INSERT INTO savedSearchConditions (searchID,\n\t\t\t\t\t\tsearchConditionID, `condition`, mode, operator,\n\t\t\t\t\t\tvalue, required) VALUES (?,?,?,?,?,?,?)"; $sqlParams = array($searchID, $searchConditionID + 1, $condition['condition'], $condition['mode'] ? $condition['mode'] : '', $condition['operator'] ? $condition['operator'] : '', $condition['value'] ? $condition['value'] : '', !empty($condition['required']) ? 1 : 0); try { Zotero_DB::query($sql, $sqlParams, $shardID); } catch (Exception $e) { $msg = $e->getMessage(); if (strpos($msg, "Data too long for column 'value'") !== false) { throw new Exception("=Value '" . mb_substr($condition['value'], 0, 75) . "…' too long in saved search '" . $this->name . "'"); } throw $e; } } Zotero_DB::commit(); } catch (Exception $e) { Zotero_DB::rollback(); throw $e; } if (!$this->id) { $this->id = $searchID; } if (!$this->key) { $this->key = $key; } return $this->id; }
public function save() { if (!$this->libraryID) { trigger_error("Library ID must be set before saving", E_USER_ERROR); } Zotero_Creators::editCheck($this); // If empty, move on if ($this->firstName === '' && $this->lastName === '') { throw new Exception('First and last name are empty'); } if ($this->fieldMode == 1 && $this->firstName !== '') { throw new Exception('First name must be empty in single-field mode'); } if (!$this->hasChanged()) { Z_Core::debug("Creator {$this->id} has not changed"); return false; } Zotero_DB::beginTransaction(); try { $creatorID = $this->id ? $this->id : Zotero_ID::get('creators'); $isNew = !$this->id; Z_Core::debug("Saving creator {$this->id}"); $key = $this->key ? $this->key : $this->generateKey(); $timestamp = Zotero_DB::getTransactionTimestamp(); $dateAdded = $this->dateAdded ? $this->dateAdded : $timestamp; $dateModified = $this->changed['dateModified'] ? $this->dateModified : $timestamp; $fields = "firstName=?, lastName=?, fieldMode=?,\n\t\t\t\t\t\tlibraryID=?, `key`=?, dateAdded=?, dateModified=?, serverDateModified=?"; $params = array($this->firstName, $this->lastName, $this->fieldMode, $this->libraryID, $key, $dateAdded, $dateModified, $timestamp); $shardID = Zotero_Shards::getByLibraryID($this->libraryID); try { if ($isNew) { $sql = "INSERT INTO creators SET creatorID=?, {$fields}"; $stmt = Zotero_DB::getStatement($sql, true, $shardID); Zotero_DB::queryFromStatement($stmt, array_merge(array($creatorID), $params)); // Remove from delete log if it's there $sql = "DELETE FROM syncDeleteLogKeys WHERE libraryID=? AND objectType='creator' AND `key`=?"; Zotero_DB::query($sql, array($this->libraryID, $key), $shardID); } else { $sql = "UPDATE creators SET {$fields} WHERE creatorID=?"; $stmt = Zotero_DB::getStatement($sql, true, $shardID); Zotero_DB::queryFromStatement($stmt, array_merge($params, array($creatorID))); } } catch (Exception $e) { if (strpos($e->getMessage(), " too long") !== false) { if (strlen($this->firstName) > 255) { throw new Exception("=First name '" . mb_substr($this->firstName, 0, 50) . "…' too long"); } if (strlen($this->lastName) > 255) { if ($this->fieldMode == 1) { throw new Exception("=Last name '" . mb_substr($this->lastName, 0, 50) . "…' too long"); } else { throw new Exception("=Name '" . mb_substr($this->lastName, 0, 50) . "…' too long"); } } } throw $e; } // The client updates the mod time of associated items here, but // we don't, because either A) this is from syncing, where appropriate // mod times come from the client or B) the change is made through // $item->setCreator(), which updates the mod time. // // If the server started to make other independent creator changes, // linked items would need to be updated. Zotero_DB::commit(); Zotero_Creators::cachePrimaryData(array('id' => $creatorID, 'libraryID' => $this->libraryID, 'key' => $key, 'dateAdded' => $dateAdded, 'dateModified' => $dateModified, 'firstName' => $this->firstName, 'lastName' => $this->lastName, 'fieldMode' => $this->fieldMode)); } catch (Exception $e) { Zotero_DB::rollback(); throw $e; } // If successful, set values in object if (!$this->id) { $this->id = $creatorID; } if (!$this->key) { $this->key = $key; } $this->init(); if ($isNew) { Zotero_Creators::cache($this); Zotero_Creators::cacheLibraryKeyID($this->libraryID, $key, $creatorID); } // TODO: invalidate memcache? return $this->id; }
private static function loadItems($libraryID, $itemIDs = array()) { $shardID = Zotero_Shards::getByLibraryID($libraryID); $sql = 'SELECT I.*, (SELECT COUNT(*) FROM itemNotes INo WHERE sourceItemID=I.itemID AND INo.itemID NOT IN (SELECT itemID FROM deletedItems)) AS numNotes, (SELECT COUNT(*) FROM itemAttachments IA WHERE sourceItemID=I.itemID AND IA.itemID NOT IN (SELECT itemID FROM deletedItems)) AS numAttachments FROM items I WHERE 1'; // TODO: optimize if ($itemIDs) { foreach ($itemIDs as $itemID) { if (!is_int($itemID)) { throw new Exception("Invalid itemID {$itemID}"); } } $sql .= ' AND I.itemID IN (' . implode(',', array_fill(0, sizeOf($itemIDs), '?')) . ')'; } $stmt = Zotero_DB::getStatement($sql, "loadItems_" . sizeOf($itemIDs), $shardID); $itemRows = Zotero_DB::queryFromStatement($stmt, $itemIDs); $loadedItemIDs = array(); if ($itemRows) { foreach ($itemRows as $row) { if ($row['libraryID'] != $libraryID) { throw new Exception("Item {$itemID} isn't in library {$libraryID}", Z_ERROR_OBJECT_LIBRARY_MISMATCH); } $itemID = $row['itemID']; $loadedItemIDs[] = $itemID; // Item isn't loaded -- create new object and stuff in array if (!isset(self::$itemsByID[$itemID])) { $item = new Zotero_Item(); $item->loadFromRow($row, true); self::$itemsByID[$itemID] = $item; } else { self::$itemsByID[$itemID]->loadFromRow($row, true); } } } if (!$itemIDs) { // If loading all items, remove old items that no longer exist $ids = array_keys(self::$itemsByID); foreach ($ids as $id) { if (!in_array($id, $loadedItemIDs)) { throw new Exception("Unimplemented"); //$this->unload($id); } } /* _cachedFields = ['itemID', 'itemTypeID', 'dateAdded', 'dateModified', 'numNotes', 'numAttachments', 'numChildren']; */ //this._reloadCache = false; } }