From c52ed6a4555fd79983f11f8dea515c94d489ddee Mon Sep 17 00:00:00 2001 From: Marcin Kurczewski Date: Sun, 19 Oct 2014 19:52:27 +0200 Subject: [PATCH] Abandoned FPDO Also, fixed tag search by categories --- composer.json | 3 +- src/Dao/AbstractDao.php | 25 ++- src/Dao/FavoritesDao.php | 2 +- src/Dao/PostDao.php | 48 +++-- src/Dao/ScoreDao.php | 2 +- src/Dao/SnapshotDao.php | 2 +- src/Dao/TagDao.php | 27 ++- src/Dao/TokenDao.php | 2 +- src/Dao/UserDao.php | 2 +- src/DatabaseConnection.php | 1 + src/PDOEx/BaseQuery.php | 174 ++++++++++++++++ src/PDOEx/DeleteQuery.php | 10 + src/PDOEx/InsertQuery.php | 41 ++++ src/{ => PDOEx}/PDOEx.php | 22 +- src/PDOEx/SelectQuery.php | 95 +++++++++ src/PDOEx/UpdateQuery.php | 34 ++++ .../RequirementCompositeValue.php | 6 + tests/Dao/PostDaoTest.php | 23 +++ tests/Dao/TagDaoFilterTest.php | 25 +++ tests/PDOEx/DeleteQueryTest.php | 27 +++ tests/PDOEx/InsertQueryTest.php | 21 ++ tests/PDOEx/SelectQueryTest.php | 188 ++++++++++++++++++ tests/PDOEx/UpdateQueryTest.php | 21 ++ 23 files changed, 740 insertions(+), 61 deletions(-) create mode 100644 src/PDOEx/BaseQuery.php create mode 100644 src/PDOEx/DeleteQuery.php create mode 100644 src/PDOEx/InsertQuery.php rename src/{ => PDOEx}/PDOEx.php (53%) create mode 100644 src/PDOEx/SelectQuery.php create mode 100644 src/PDOEx/UpdateQuery.php create mode 100644 tests/PDOEx/DeleteQueryTest.php create mode 100644 tests/PDOEx/InsertQueryTest.php create mode 100644 tests/PDOEx/SelectQueryTest.php create mode 100644 tests/PDOEx/UpdateQueryTest.php diff --git a/composer.json b/composer.json index a538a906..dcf12d73 100644 --- a/composer.json +++ b/composer.json @@ -1,7 +1,6 @@ { "require": { - "mnapoli/php-di": "~4.0", - "lichtner/fluentpdo": "dev-master" + "mnapoli/php-di": "~4.0" }, "require-dev": { diff --git a/src/Dao/AbstractDao.php b/src/Dao/AbstractDao.php index 348c378e..5f3d2bcb 100644 --- a/src/Dao/AbstractDao.php +++ b/src/Dao/AbstractDao.php @@ -13,7 +13,6 @@ use Szurubooru\SearchServices\Result; abstract class AbstractDao implements ICrudDao, IBatchDao { protected $pdo; - protected $fpdo; protected $tableName; protected $entityConverter; protected $driver; @@ -64,7 +63,7 @@ abstract class AbstractDao implements ICrudDao, IBatchDao public function findAll() { - $query = $this->fpdo->from($this->tableName)->disableSmartJoin(); + $query = $this->pdo->from($this->tableName); $arrayEntities = iterator_to_array($query); return $this->arrayToEntities($arrayEntities); } @@ -81,7 +80,7 @@ abstract class AbstractDao implements ICrudDao, IBatchDao public function findFiltered(IFilter $searchFilter) { - $query = $this->fpdo->from($this->tableName)->disableSmartJoin(); + $query = $this->pdo->from($this->tableName); $orderByString = self::compileOrderBy($searchFilter->getOrder()); if ($orderByString) @@ -95,7 +94,7 @@ abstract class AbstractDao implements ICrudDao, IBatchDao } $entities = $this->arrayToEntities(iterator_to_array($query)); - $query = $this->fpdo->from($this->tableName)->disableSmartJoin(); + $query = $this->pdo->from($this->tableName); $this->decorateQueryFromFilter($query, $searchFilter); $totalRecords = count($query); @@ -114,7 +113,7 @@ abstract class AbstractDao implements ICrudDao, IBatchDao { $this->beforeDelete($entity); } - $this->fpdo->deleteFrom($this->tableName)->execute(); + $this->pdo->deleteFrom($this->tableName)->execute(); } public function deleteById($entityId) @@ -125,14 +124,15 @@ abstract class AbstractDao implements ICrudDao, IBatchDao public function update(Entity $entity) { $arrayEntity = $this->entityConverter->toArray($entity); - $this->fpdo->update($this->tableName)->set($arrayEntity)->where($this->getIdColumn(), $entity->getId())->execute(); + unset($arrayEntity['id']); + $this->pdo->update($this->tableName)->set($arrayEntity)->where($this->getIdColumn(), $entity->getId())->execute(); return $entity; } public function create(Entity $entity) { $arrayEntity = $this->entityConverter->toArray($entity); - $this->fpdo->insertInto($this->tableName)->values($arrayEntity)->execute(); + $this->pdo->insertInto($this->tableName)->values($arrayEntity)->execute(); $entity->setId(intval($this->pdo->lastInsertId())); return $entity; } @@ -144,14 +144,14 @@ abstract class AbstractDao implements ICrudDao, IBatchDao protected function hasAnyRecords() { - return count(iterator_to_array($this->fpdo->from($this->tableName)->limit(1))) > 0; + return count(iterator_to_array($this->pdo->from($this->tableName)->limit(1))) > 0; } protected function findBy($columnName, $value) { if (is_array($value) and empty($value)) return []; - $query = $this->fpdo->from($this->tableName)->disableSmartJoin()->where($columnName, $value); + $query = $this->pdo->from($this->tableName)->where($columnName, $value); $arrayEntities = iterator_to_array($query); return $this->arrayToEntities($arrayEntities); } @@ -170,7 +170,7 @@ abstract class AbstractDao implements ICrudDao, IBatchDao { $this->beforeDelete($entity); } - $this->fpdo->deleteFrom($this->tableName)->disableSmartJoin()->where($columnName, $value)->execute(); + $this->pdo->deleteFrom($this->tableName)->where($columnName, $value)->execute(); } protected function afterLoad(Entity $entity) @@ -197,7 +197,7 @@ abstract class AbstractDao implements ICrudDao, IBatchDao if ($value instanceof RequirementCompositeValue) { $sql = $sqlColumn; - $bindings = [$value->getValues()]; + $bindings = $value->getValues(); if ($requirement->isNegated()) $sql = 'NOT ' . $sql; @@ -239,7 +239,7 @@ abstract class AbstractDao implements ICrudDao, IBatchDao else throw new \Exception('Bad value: ' . get_class($value)); - $query->where($sql, ...$bindings); + $query->where($sql, $bindings); } protected function arrayToEntities(array $arrayEntities, $entityConverter = null) @@ -259,7 +259,6 @@ abstract class AbstractDao implements ICrudDao, IBatchDao private function setDatabaseConnection(DatabaseConnection $databaseConnection) { $this->pdo = $databaseConnection->getPDO(); - $this->fpdo = new \FluentPDO($this->pdo); $this->driver = $databaseConnection->getDriver(); } diff --git a/src/Dao/FavoritesDao.php b/src/Dao/FavoritesDao.php index 99e7afc5..99efea03 100644 --- a/src/Dao/FavoritesDao.php +++ b/src/Dao/FavoritesDao.php @@ -60,7 +60,7 @@ class FavoritesDao extends AbstractDao implements ICrudDao private function get(User $user, Entity $entity) { - $query = $this->fpdo->from($this->tableName)->where('userId', $user->getId()); + $query = $this->pdo->from($this->tableName)->where('userId', $user->getId()); if ($entity instanceof Post) $query->where('postId', $entity->getId()); diff --git a/src/Dao/PostDao.php b/src/Dao/PostDao.php index b28daafb..ffee06cb 100644 --- a/src/Dao/PostDao.php +++ b/src/Dao/PostDao.php @@ -38,12 +38,12 @@ class PostDao extends AbstractDao implements ICrudDao public function getCount() { - return count($this->fpdo->from($this->tableName)); + return count($this->pdo->from($this->tableName)); } public function getTotalFileSize() { - $query = $this->fpdo->from($this->tableName)->select('SUM(originalFileSize) AS __sum'); + $query = $this->pdo->from($this->tableName)->select('SUM(originalFileSize) AS __sum'); return intval(iterator_to_array($query)[0]['__sum']); } @@ -54,10 +54,9 @@ class PostDao extends AbstractDao implements ICrudDao public function findByTagName($tagName) { - $query = $this->fpdo->from('posts') - ->disableSmartJoin() - ->innerJoin('postTags ON postTags.postId = posts.id') - ->innerJoin('tags ON postTags.tagId = tags.id') + $query = $this->pdo->from('posts') + ->innerJoin('postTags', 'postTags.postId = posts.id') + ->innerJoin('tags', 'postTags.tagId = tags.id') ->where('tags.name', $tagName); $arrayEntities = iterator_to_array($query); return $this->arrayToEntities($arrayEntities); @@ -184,21 +183,20 @@ class PostDao extends AbstractDao implements ICrudDao private function getRelatedPosts(Post $post) { - $query = $this->fpdo - ->from('postRelations') - ->where('post1id = :post1id OR post2id = :post2id', [ - ':post1id' => $post->getId(), - ':post2id' => $post->getId()]); - $relatedPostIds = []; - foreach ($query as $arrayEntity) + + foreach ($this->pdo->from('postRelations')->where('post1id', $post->getId()) as $arrayEntity) { - $post1id = intval($arrayEntity['post1id']); - $post2id = intval($arrayEntity['post2id']); - if ($post1id !== $post->getId()) - $relatedPostIds[] = $post1id; - if ($post2id !== $post->getId()) - $relatedPostIds[] = $post2id; + $postId = intval($arrayEntity['post2id']); + if ($postId !== $post->getId()) + $relatedPostIds[] = $postId; + } + + foreach ($this->pdo->from('postRelations')->where('post2id', $post->getId()) as $arrayEntity) + { + $postId = intval($arrayEntity['post1id']); + if ($postId !== $post->getId()) + $relatedPostIds[] = $postId; } return $this->findByIds($relatedPostIds); @@ -242,25 +240,25 @@ class PostDao extends AbstractDao implements ICrudDao { return $arrayEntity['tagId']; }, - iterator_to_array($this->fpdo->from('postTags')->where('postId', $post->getId()))); + iterator_to_array($this->pdo->from('postTags')->where('postId', $post->getId()))); $tagRelationsToInsert = array_diff($tagIds, $existingTagRelationIds); $tagRelationsToDelete = array_diff($existingTagRelationIds, $tagIds); foreach ($tagRelationsToInsert as $tagId) { - $this->fpdo->insertInto('postTags')->values(['postId' => $post->getId(), 'tagId' => $tagId])->execute(); + $this->pdo->insertInto('postTags')->values(['postId' => $post->getId(), 'tagId' => $tagId])->execute(); } foreach ($tagRelationsToDelete as $tagId) { - $this->fpdo->deleteFrom('postTags')->where('postId', $post->getId())->where('tagId', $tagId)->execute(); + $this->pdo->deleteFrom('postTags')->where('postId', $post->getId())->where('tagId', $tagId)->execute(); } } private function syncPostRelations(Post $post) { - $this->fpdo->deleteFrom('postRelations')->where('post1id', $post->getId())->execute(); - $this->fpdo->deleteFrom('postRelations')->where('post2id', $post->getId())->execute(); + $this->pdo->deleteFrom('postRelations')->where('post1id', $post->getId())->execute(); + $this->pdo->deleteFrom('postRelations')->where('post2id', $post->getId())->execute(); $relatedPostIds = array_filter(array_unique(array_map( function ($post) @@ -273,7 +271,7 @@ class PostDao extends AbstractDao implements ICrudDao foreach ($relatedPostIds as $postId) { - $this->fpdo + $this->pdo ->insertInto('postRelations') ->values([ 'post1id' => $post->getId(), diff --git a/src/Dao/ScoreDao.php b/src/Dao/ScoreDao.php index f77ebfbd..d70883af 100644 --- a/src/Dao/ScoreDao.php +++ b/src/Dao/ScoreDao.php @@ -27,7 +27,7 @@ class ScoreDao extends AbstractDao implements ICrudDao public function getScore(User $user, Entity $entity) { - $query = $this->fpdo->from($this->tableName)->where('userId', $user->getId()); + $query = $this->pdo->from($this->tableName)->where('userId', $user->getId()); if ($entity instanceof Post) $query->where('postId', $entity->getId()); diff --git a/src/Dao/SnapshotDao.php b/src/Dao/SnapshotDao.php index 8da43d49..3dad4f47 100644 --- a/src/Dao/SnapshotDao.php +++ b/src/Dao/SnapshotDao.php @@ -24,7 +24,7 @@ class SnapshotDao extends AbstractDao public function findByTypeAndKey($type, $primaryKey) { - $query = $this->fpdo + $query = $this->pdo ->from($this->tableName) ->where('type', $type) ->where('primaryKey', $primaryKey) diff --git a/src/Dao/TagDao.php b/src/Dao/TagDao.php index 56248260..2796cc58 100644 --- a/src/Dao/TagDao.php +++ b/src/Dao/TagDao.php @@ -38,9 +38,8 @@ class TagDao extends AbstractDao implements ICrudDao public function findByPostIds($postIds) { - $query = $this->fpdo->from($this->tableName) - ->disableSmartJoin() - ->innerJoin('postTags ON postTags.tagId = tags.id') + $query = $this->pdo->from($this->tableName) + ->innerJoin('postTags', 'postTags.tagId = tags.id') ->where('postTags.postId', $postIds); $arrayEntities = iterator_to_array($query); return $this->arrayToEntities($arrayEntities); @@ -52,11 +51,10 @@ class TagDao extends AbstractDao implements ICrudDao if (!$tag) return []; $tagId = $tag->getId(); - $query = $this->fpdo->from($this->tableName) + $query = $this->pdo->from($this->tableName) ->select('COUNT(pt2.postId) AS postCount') - ->disableSmartJoin() - ->innerJoin('postTags pt1 ON pt1.tagId = tags.id') - ->innerJoin('postTags pt2 ON pt2.postId = pt1.postId') + ->innerJoin('postTags pt1', 'pt1.tagId = tags.id') + ->innerJoin('postTags pt2', 'pt2.postId = pt1.postId') ->where('pt2.tagId', $tagId) ->groupBy('tags.id') ->orderBy('postCount DESC, name ASC'); @@ -79,7 +77,7 @@ class TagDao extends AbstractDao implements ICrudDao public function export() { $exported = []; - foreach ($this->fpdo->from('tags') as $arrayEntity) + foreach ($this->pdo->from($this->tableName) as $arrayEntity) { $exported[$arrayEntity['id']] = [ 'name' => $arrayEntity['name'], @@ -91,7 +89,7 @@ class TagDao extends AbstractDao implements ICrudDao //upgrades on old databases try { - $relations = iterator_to_array($this->fpdo->from('tagRelations')); + $relations = iterator_to_array($this->pdo->from('tagRelations')); } catch (\Exception $e) { @@ -160,7 +158,7 @@ class TagDao extends AbstractDao implements ICrudDao elseif ($requirement->getType() === TagFilter::REQUIREMENT_CATEGORY) { - $sql = 'IFNULL(category, \'default\') = ?'; + $sql = 'IFNULL(category, \'default\')'; $requirement->setType($sql); return parent::decorateQueryFromRequirement($query, $requirement); } @@ -190,7 +188,7 @@ class TagDao extends AbstractDao implements ICrudDao private function syncRelatedTagsByType(Tag $tag, array $relatedTags, $type) { - $this->fpdo->deleteFrom('tagRelations') + $this->pdo->deleteFrom('tagRelations') ->where('tag1id', $tag->getId()) ->where('type', $type) ->execute(); @@ -206,7 +204,7 @@ class TagDao extends AbstractDao implements ICrudDao foreach ($relatedTagIds as $tagId) { - $this->fpdo + $this->pdo ->insertInto('tagRelations') ->values([ 'tag1id' => $tag->getId(), @@ -219,9 +217,8 @@ class TagDao extends AbstractDao implements ICrudDao private function findRelatedTagsByType(Tag $tag, $type) { $tagId = $tag->getId(); - $query = $this->fpdo->from($this->tableName) - ->disableSmartJoin() - ->innerJoin('tagRelations tr ON tags.id = tr.tag2id') + $query = $this->pdo->from($this->tableName) + ->innerJoin('tagRelations tr', 'tags.id = tr.tag2id') ->where('tr.type', $type) ->where('tr.tag1id', $tagId); $arrayEntities = iterator_to_array($query); diff --git a/src/Dao/TokenDao.php b/src/Dao/TokenDao.php index 20a51c33..7a75a144 100644 --- a/src/Dao/TokenDao.php +++ b/src/Dao/TokenDao.php @@ -20,7 +20,7 @@ class TokenDao extends AbstractDao public function findByAdditionalDataAndPurpose($additionalData, $purpose) { - $query = $this->fpdo->from($this->tableName) + $query = $this->pdo->from($this->tableName) ->where('additionalData', $additionalData) ->where('purpose', $purpose); $arrayEntities = iterator_to_array($query); diff --git a/src/Dao/UserDao.php b/src/Dao/UserDao.php index 6b0cd497..3bfe0bab 100644 --- a/src/Dao/UserDao.php +++ b/src/Dao/UserDao.php @@ -52,7 +52,7 @@ class UserDao extends AbstractDao implements ICrudDao public function deleteByName($userName) { $this->deleteBy('name', $userName); - $this->fpdo->deleteFrom('tokens')->where('additionalData', $userName); + $this->pdo->deleteFrom('tokens')->where('additionalData', $userName); } protected function afterLoad(Entity $user) diff --git a/src/DatabaseConnection.php b/src/DatabaseConnection.php index 4068b3e8..f6eb4952 100644 --- a/src/DatabaseConnection.php +++ b/src/DatabaseConnection.php @@ -1,6 +1,7 @@ pdoex = $pdoex; + $this->table = $table; + $this->clauses = []; + $this->parameters = []; + $this->init(); + } + + public function getIterator() + { + return $this->execute(); + } + + public function getQuery() + { + return $this->buildQuery(); + } + + public function execute() + { + $query = $this->buildQuery(); + $parameters = $this->getParameters(); + + try + { + $result = $this->pdoex->prepare($query); + if (!$result || !$result->execute($parameters)) + $this->throwQueryException($query, $parameters, 'unknown reason'); + } + catch (\PDOException $e) + { + $this->throwQueryException($query, $parameters, $e->getMessage()); + } + return $result; + } + + public function innerJoin($table, $condition) + { + if (!isset($this->clauses[self::CLAUSE_JOIN])) + $this->clauses[self::CLAUSE_JOIN] = []; + + $this->clauses[self::CLAUSE_JOIN][] = 'INNER JOIN ' . $table . ' ON ' . $condition; + return $this; + } + + public function where($key, $value = null) + { + if ($key === null) + { + $this->clauses[self::CLAUSE_WHERE] = []; + return; + } + + if (!isset($this->clauses[self::CLAUSE_WHERE])) + $this->clauses[self::CLAUSE_WHERE] = []; + + $sql = empty($this->clauses[self::CLAUSE_WHERE]) ? 'WHERE' : 'AND'; + + if (strpos($key, '?') !== false) + { + if (!is_array($value)) + $value = [$value]; + assert(substr_count($key, '?') === count($value), 'Binding ' . print_r($value, true) . ' to ' . $key); + $index = 0; + $sql .= ' ' . preg_replace_callback( + '/\?/', + function($match) use (&$index, $value) + { + $ret = $this->bind($value[$index]); + $index ++; + return $ret; + }, + $key); + $this->clauses[self::CLAUSE_WHERE][] = $sql; + return $this; + } + + if (!is_array($value)) + { + if ($value === null) + $sql .= ' ' . $key . ' IS NULL'; + else + $sql .= ' ' . $key . ' = ' . $this->bind($value); + $this->clauses[self::CLAUSE_WHERE][] = $sql; + return $this; + } + + if (empty($value)) + { + $sql .= ' 0'; + $this->clauses[self::CLAUSE_WHERE][] = $sql; + return $this; + } + + $sql .= ' ' . $key . ' IN ('; + foreach ($value as $id => $val) + { + $sql .= $this->bind($val) . ', '; + } + $sql = substr($sql, 0, -2); + $sql .= ')'; + $this->clauses[self::CLAUSE_WHERE][] = $sql; + return $this; + } + + protected function getParameters() + { + return $this->parameters; + } + + protected function buildQuery() + { + $priorities = array_flip([ + self::CLAUSE_BASE, + self::CLAUSE_JOIN, + self::CLAUSE_WHERE, + self::CLAUSE_GROUP, + self::CLAUSE_ORDER, + self::CLAUSE_LIMIT]); + uksort( + $this->clauses, + function($clauseNameA, $clauseNameB) use ($priorities) + { + return $priorities[$clauseNameA] - $priorities[$clauseNameB]; + }); + + $query = ''; + foreach ($this->clauses as $statements) + { + if (!is_array($statements)) + $statements = [$statements]; + foreach ($statements as $statement) + $query .= ' ' . $statement; + } + return trim($query); + } + + protected abstract function init(); + + protected function bind($value) + { + $id = ':' . uniqid(); + $this->parameters[$id] = $value; + return $id; + } + + private function throwQueryException($query, $parameters, $message) + { + throw new \Exception(sprintf( + 'Problem executing query "%s" with parameters %s: %s', + $query, + print_r($parameters, true), + $message)); + } +} diff --git a/src/PDOEx/DeleteQuery.php b/src/PDOEx/DeleteQuery.php new file mode 100644 index 00000000..eef55517 --- /dev/null +++ b/src/PDOEx/DeleteQuery.php @@ -0,0 +1,10 @@ +clauses[self::CLAUSE_BASE] = 'DELETE FROM ' . $this->table; + } +} diff --git a/src/PDOEx/InsertQuery.php b/src/PDOEx/InsertQuery.php new file mode 100644 index 00000000..a42fc27b --- /dev/null +++ b/src/PDOEx/InsertQuery.php @@ -0,0 +1,41 @@ +values = $values; + $this->refreshBaseQuery(); + return $this; + } + + public function where($key, $value = null) + { + throw new \BadMethodCallException('This makes no sense!'); + } + + public function innerJoin($table, $condition) + { + throw new \BadMethodCallException('This makes no sense!'); + } + + protected function init() + { + $this->refreshBaseQuery(); + } + + private function refreshBaseQuery() + { + $sql = 'INSERT INTO ' . $this->table; + $sql .= ' (' . implode(', ', array_keys($this->values)) . ')'; + $sql .= ' VALUES ('; + foreach ($this->values as $value) + $sql .= $this->bind($value) . ', '; + $sql = substr($sql, 0, -2); + $sql .= ')'; + $this->clauses[self::CLAUSE_BASE] = $sql; + } +} diff --git a/src/PDOEx.php b/src/PDOEx/PDOEx.php similarity index 53% rename from src/PDOEx.php rename to src/PDOEx/PDOEx.php index 3730e161..14862699 100644 --- a/src/PDOEx.php +++ b/src/PDOEx/PDOEx.php @@ -1,5 +1,5 @@ statements; } + + public function from($table) + { + return new SelectQuery($this, $table); + } + + public function insertInto($table) + { + return new InsertQuery($this, $table); + } + + public function update($table) + { + return new UpdateQuery($this, $table); + } + + public function deleteFrom($table) + { + return new DeleteQuery($this, $table); + } } diff --git a/src/PDOEx/SelectQuery.php b/src/PDOEx/SelectQuery.php new file mode 100644 index 00000000..71386900 --- /dev/null +++ b/src/PDOEx/SelectQuery.php @@ -0,0 +1,95 @@ +limit = null; + else + $this->limit = intval($limit); + $this->refreshLimitClause(); + return $this; + } + + public function offset($offset) + { + $this->offset = intval($offset); + $this->refreshLimitClause(); + return $this; + } + + public function select($key) + { + if ($key === null) + $this->selectTarget = ''; + else + { + if ($this->selectTarget) + $this->selectTarget .= ', '; + $this->selectTarget .= $key; + } + $this->refreshBaseClause(); + return $this; + } + + public function orderBy($key, $dir = null) + { + if ($key === null) + $this->orderTarget = ''; + else + { + if ($this->orderTarget) + $this->orderTarget .= ', '; + $this->orderTarget .= rtrim($key . ' ' . $dir); + } + $this->refreshOrderClause(); + return $this; + } + + public function groupBy($key) + { + $this->clauses[self::CLAUSE_GROUP] = 'GROUP BY ' . $key; + return $this; + } + + public function count() + { + $query = clone($this); + return iterator_to_array($query->select(null)->select('COUNT(1) AS c'))[0]['c']; + } + + protected function init() + { + $this->selectTarget = $this->table . '.*'; + $this->refreshBaseClause(); + } + + private function refreshBaseClause() + { + $this->clauses[self::CLAUSE_BASE] = 'SELECT ' . $this->selectTarget . ' FROM ' . $this->table; + } + + private function refreshOrderClause() + { + $this->clauses[self::CLAUSE_ORDER] = 'ORDER BY ' . $this->orderTarget; + } + + private function refreshLimitClause() + { + $sql = ''; + if ($this->limit !== null) + { + $sql .= 'LIMIT ' . $this->limit; + if ($this->offset !== null and $this->offset !== 0) + $sql .= ' OFFSET ' . intval($this->offset); + } + $this->clauses[self::CLAUSE_LIMIT] = $sql; + } +} diff --git a/src/PDOEx/UpdateQuery.php b/src/PDOEx/UpdateQuery.php new file mode 100644 index 00000000..eee43705 --- /dev/null +++ b/src/PDOEx/UpdateQuery.php @@ -0,0 +1,34 @@ +values = $values; + $this->refreshBaseQuery(); + return $this; + } + + public function innerJoin($table, $condition) + { + throw new \BadMethodCallException('This makes no sense!'); + } + + protected function init() + { + $this->refreshBaseQuery(); + } + + private function refreshBaseQuery() + { + $sql = 'UPDATE ' . $this->table; + $sql .= ' SET '; + foreach ($this->values as $key => $value) + $sql .= $key . ' = ' . $this->bind($value) . ', '; + $sql = substr($sql, 0, -2); + $this->clauses[self::CLAUSE_BASE] = $sql; + } +} diff --git a/src/SearchServices/Requirements/RequirementCompositeValue.php b/src/SearchServices/Requirements/RequirementCompositeValue.php index c18a3215..fdc91da7 100644 --- a/src/SearchServices/Requirements/RequirementCompositeValue.php +++ b/src/SearchServices/Requirements/RequirementCompositeValue.php @@ -4,6 +4,12 @@ namespace Szurubooru\SearchServices\Requirements; class RequirementCompositeValue implements IRequirementValue { private $values = []; + + public function __construct(array $values = []) + { + $this->setValues($values); + } + public function getValues() { return $this->values; diff --git a/tests/Dao/PostDaoTest.php b/tests/Dao/PostDaoTest.php index 2c3d4c17..51777f8c 100644 --- a/tests/Dao/PostDaoTest.php +++ b/tests/Dao/PostDaoTest.php @@ -145,6 +145,29 @@ final class PostDaoTest extends AbstractDatabaseTestCase $this->assertEquals(1, count($postDao->findAll())); } + public function testFindingByTagName() + { + $tag1 = new Tag(); + $tag1->setName('tag1'); + $tag1->setCreationTime(date('c')); + $tag2 = new Tag(); + $tag2->setName('tag2'); + $tag2->setCreationTime(date('c')); + $this->tagDao->save($tag1); + $this->tagDao->save($tag2); + + $postDao = $this->getPostDao(); + $post1 = self::getTestPost(); + $post1->setTags([$tag1]); + $postDao->save($post1); + $post2 = self::getTestPost(); + $post2->setTags([$tag2]); + $postDao->save($post2); + + $this->assertEntitiesEqual([$post1], array_values($postDao->findByTagName('tag1'))); + $this->assertEntitiesEqual([$post2], array_values($postDao->findByTagName('tag2'))); + } + public function testSavingTags() { $tag1 = new Tag(); diff --git a/tests/Dao/TagDaoFilterTest.php b/tests/Dao/TagDaoFilterTest.php index 6d89ec61..f228a772 100644 --- a/tests/Dao/TagDaoFilterTest.php +++ b/tests/Dao/TagDaoFilterTest.php @@ -5,6 +5,7 @@ use Szurubooru\Entities\Tag; use Szurubooru\SearchServices\Filters\TagFilter; use Szurubooru\SearchServices\Requirements\Requirement; use Szurubooru\SearchServices\Requirements\RequirementSingleValue; +use Szurubooru\SearchServices\Requirements\RequirementCompositeValue; use Szurubooru\SearchServices\Result; use Szurubooru\Tests\AbstractDatabaseTestCase; @@ -34,6 +35,30 @@ final class TagDaoFilterTest extends AbstractDatabaseTestCase $this->assertEntitiesEqual([$tag3, $tag1], array_values($result->getEntities())); } + public function testCompositeCategories() + { + $tag1 = $this->getTestTag('test 1'); + $tag2 = $this->getTestTag('test 2'); + $tag3 = $this->getTestTag('test 3'); + $tag1->setCategory(null); + $tag2->setCategory('misc'); + $tag3->setCategory('other'); + $tagDao = $this->getTagDao(); + $tagDao->save($tag1); + $tagDao->save($tag2); + $tagDao->save($tag3); + + $searchFilter = new TagFilter(); + $requirement = new Requirement(); + $requirement->setType(TagFilter::REQUIREMENT_CATEGORY); + $requirement->setValue(new RequirementCompositeValue(['misc', 'other'])); + $requirement->setNegated(true); + $searchFilter->addRequirement($requirement); + $result = $tagDao->findFiltered($searchFilter); + $this->assertEquals(1, $result->getTotalRecords()); + $this->assertEntitiesEqual([$tag1], array_values($result->getEntities())); + } + private function getTagDao() { return new TagDao($this->databaseConnection); diff --git a/tests/PDOEx/DeleteQueryTest.php b/tests/PDOEx/DeleteQueryTest.php new file mode 100644 index 00000000..e1d88272 --- /dev/null +++ b/tests/PDOEx/DeleteQueryTest.php @@ -0,0 +1,27 @@ +getDeleteQuery(); + $this->assertEquals('DELETE FROM test', $query->getQuery()); + } + + public function testBasicWhere() + { + $query = $this->getDeleteQuery(); + $query->where('column', 'value'); + $this->assertRegExp('/^DELETE FROM test WHERE column = :[\w]*$/', $query->getQuery()); + } + + private function getDeleteQuery() + { + $pdoMock = $this->mock(PDOEx::class); + return new DeleteQuery($pdoMock, 'test'); + } +} diff --git a/tests/PDOEx/InsertQueryTest.php b/tests/PDOEx/InsertQueryTest.php new file mode 100644 index 00000000..423428b8 --- /dev/null +++ b/tests/PDOEx/InsertQueryTest.php @@ -0,0 +1,21 @@ +getInsertQuery(); + $query->values(['key1' => 'value', 'key2' => 'value2']); + $this->assertRegExp('/^INSERT INTO test \(key1, key2\) VALUES \(:\w*, :\w*\)$/', $query->getQuery()); + } + + private function getInsertQuery() + { + $pdoMock = $this->mock(PDOEx::class); + return new InsertQuery($pdoMock, 'test'); + } +} diff --git a/tests/PDOEx/SelectQueryTest.php b/tests/PDOEx/SelectQueryTest.php new file mode 100644 index 00000000..5b25a6db --- /dev/null +++ b/tests/PDOEx/SelectQueryTest.php @@ -0,0 +1,188 @@ +getSelectQuery(); + $this->assertEquals('SELECT test.* FROM test', $query->getQuery()); + } + + public function testAdditionalColumns() + { + $query = $this->getSelectQuery(); + $query->select('SUM(1) AS sum'); + $query->select('something else'); + $this->assertEquals('SELECT test.*, SUM(1) AS sum, something else FROM test', $query->getQuery()); + } + + public function testResettingAdditionalColumns() + { + $query = $this->getSelectQuery(); + $query->select(null); + $query->select('SUM(1) AS sum'); + $this->assertEquals('SELECT SUM(1) AS sum FROM test', $query->getQuery()); + } + + public function testInnerJoin() + { + $query = $this->getSelectQuery(); + $query->innerJoin('test2', 'test2.id = test.foreignId'); + $this->assertEquals('SELECT test.* FROM test INNER JOIN test2 ON test2.id = test.foreignId', $query->getQuery()); + } + + public function testMultipleInnerJoins() + { + $query = $this->getSelectQuery(); + $query->innerJoin('test2', 'test2.id = test.foreignId'); + $query->innerJoin('test3', 'test3.id = test2.foreignId'); + $this->assertEquals('SELECT test.* FROM test INNER JOIN test2 ON test2.id = test.foreignId INNER JOIN test3 ON test3.id = test2.foreignId', $query->getQuery()); + } + + public function testSelectAfterInnerJoin() + { + $query = $this->getSelectQuery(); + $query->innerJoin('test2', 'test2.id = test.foreignId'); + $query->select('whatever'); + $this->assertEquals('SELECT test.*, whatever FROM test INNER JOIN test2 ON test2.id = test.foreignId', $query->getQuery()); + } + + public function testBasicWhere() + { + $query = $this->getSelectQuery(); + $query->where('column', 'value'); + $this->assertRegExp('/^SELECT test\.\* FROM test WHERE column = :[\w]*$/', $query->getQuery()); + } + + public function testMultipleWhere() + { + $query = $this->getSelectQuery(); + $query->where('column1', 'value1'); + $query->where('column2', 'value2'); + $this->assertRegExp('/^SELECT test\.\* FROM test WHERE column1 = :\w* AND column2 = :\w*$/', $query->getQuery()); + } + + public function testManualWhere() + { + $query = $this->getSelectQuery(); + $query->where('column1 >= ? AND column2 <= ?', ['value1', 'value2']); + $this->assertRegExp('/^SELECT test\.\* FROM test WHERE column1 >= :\w* AND column2 <= :\w*$/', $query->getQuery()); + } + + public function testWhereNull() + { + $query = $this->getSelectQuery(); + $query->where('column', null); + $this->assertRegExp('/^SELECT test\.\* FROM test WHERE column IS NULL$/', $query->getQuery()); + } + + public function testResettingWhere() + { + $query = $this->getSelectQuery(); + $query->where('column1', 'value1'); + $query->where(null); + $query->where('column2', 'value2'); + $this->assertRegExp('/^SELECT test\.\* FROM test WHERE column2 = :\w*$/', $query->getQuery()); + } + + public function testEmptyIn() + { + $query = $this->getSelectQuery(); + $query->where('column', []); + $this->assertRegExp('/^SELECT test\.\* FROM test WHERE 0$/', $query->getQuery()); + } + + public function testIn() + { + $query = $this->getSelectQuery(); + $query->where('column', ['val1', 'val2']); + $this->assertRegExp('/^SELECT test\.\* FROM test WHERE column IN \(:\w*, :\w*\)$/', $query->getQuery()); + } + + public function testMixedInAndWhere() + { + $query = $this->getSelectQuery(); + $query->where('column1', ['val1', 'val2']); + $query->where('column2', 'val3'); + $this->assertRegExp('/^SELECT test\.\* FROM test WHERE column1 IN \(:\w*, :\w*\) AND column2 = :\w*$/', $query->getQuery()); + } + + public function testGroupBy() + { + $query = $this->getSelectQuery(); + $query->groupBy('test.id'); + $this->assertEquals('SELECT test.* FROM test GROUP BY test.id', $query->getQuery()); + } + + public function testGroupByAndOrderBy() + { + $query = $this->getSelectQuery(); + $query->groupBy('test.id'); + $query->orderBy('test.whatever'); + $this->assertEquals('SELECT test.* FROM test GROUP BY test.id ORDER BY test.whatever', $query->getQuery()); + } + + public function testOrderBy() + { + $query = $this->getSelectQuery(); + $query->orderBy('id DESC'); + $this->assertEquals('SELECT test.* FROM test ORDER BY id DESC', $query->getQuery()); + } + + public function testOrderByFlavor2() + { + $query = $this->getSelectQuery(); + $query->orderBy('id', 'DESC'); + $this->assertEquals('SELECT test.* FROM test ORDER BY id DESC', $query->getQuery()); + } + + public function testOrderByMultiple() + { + $query = $this->getSelectQuery(); + $query->orderBy('id', 'DESC'); + $query->orderBy('id2', 'ASC'); + $this->assertEquals('SELECT test.* FROM test ORDER BY id DESC, id2 ASC', $query->getQuery()); + } + + public function testResettingOrderBy() + { + $query = $this->getSelectQuery(); + $query->orderBy('id', 'DESC'); + $query->orderBy(null); + $query->orderBy('id2', 'ASC'); + $this->assertEquals('SELECT test.* FROM test ORDER BY id2 ASC', $query->getQuery()); + } + + public function testLimit() + { + $query = $this->getSelectQuery(); + $query->limit(5); + $this->assertEquals('SELECT test.* FROM test LIMIT 5', $query->getQuery()); + } + + public function testLimitWithOffset() + { + $query = $this->getSelectQuery(); + $query->offset(2); + $query->limit(5); + $this->assertEquals('SELECT test.* FROM test LIMIT 5 OFFSET 2', $query->getQuery()); + } + + public function testOffsetWithoutLimit() + { + $query = $this->getSelectQuery(); + $query->offset(2); + $query->limit(null); + $this->assertEquals('SELECT test.* FROM test', $query->getQuery()); + } + + private function getSelectQuery() + { + $pdoMock = $this->mock(PDOEx::class); + return new SelectQuery($pdoMock, 'test'); + } +} diff --git a/tests/PDOEx/UpdateQueryTest.php b/tests/PDOEx/UpdateQueryTest.php new file mode 100644 index 00000000..aa023b26 --- /dev/null +++ b/tests/PDOEx/UpdateQueryTest.php @@ -0,0 +1,21 @@ +getUpdateQuery(); + $query->set(['key1' => 'value', 'key2' => 'value2']); + $this->assertRegExp('/^UPDATE test SET key1 = :\w*, key2 = :\w*$/', $query->getQuery()); + } + + private function getUpdateQuery() + { + $pdoMock = $this->mock(PDOEx::class); + return new UpdateQuery($pdoMock, 'test'); + } +}