SQL overhaul: introducing tree-like queries
Reason: until now, PostSearchService was using magic to get around the biggest limitation of SqlQuery.php: it didn't support arbitrary order of operations. You couldn't join with something and tell then to select something from it. Additionally, forging UPDATE queries was a joke. The new Sql* classes replace SqlQuery completely and address these issues. Using Sql* classes might be tedious and ugly at times, but it is necessary step to improve model layer maintainability. It is by no menas complete implementation of SQL grammar, but for current needs it's enough, and, what's most important, it is easily extensible. Additional changes: * Added sorting style aliases - fav_count - tag_count - comment_count * Sorting by multiple tokens in post search is now possible * Searching for disliked posts with "special:disliked" always yields results (even if user has disabled showing disliked posts by default) * More maintainable next/prev post support
This commit is contained in:
parent
1baceb5816
commit
6af3a0e42b
55 changed files with 1345 additions and 762 deletions
|
@ -54,14 +54,14 @@ class IndexController
|
||||||
|
|
||||||
private function featureNewPost()
|
private function featureNewPost()
|
||||||
{
|
{
|
||||||
$query = (new SqlQuery)
|
$stmt = (new SqlSelectStatement)
|
||||||
->select('id')
|
->setColumn('id')
|
||||||
->from('post')
|
->setTable('post')
|
||||||
->where('type = ?')->put(PostType::Image)
|
->setCriterion((new SqlConjunction)
|
||||||
->and('safety = ?')->put(PostSafety::Safe)
|
->add(new SqlEqualsOperator('type', new SqlBinding(PostType::Image)))
|
||||||
->orderBy($this->config->main->dbDriver == 'sqlite' ? 'random()' : 'rand()')
|
->add(new SqlEqualsOperator('safety', new SqlBinding(PostSafety::Safe))))
|
||||||
->desc();
|
->setOrderBy(new SqlRandomOperator(), SqlSelectStatement::ORDER_DESC);
|
||||||
$featuredPostId = Database::fetchOne($query)['id'];
|
$featuredPostId = Database::fetchOne($stmt)['id'];
|
||||||
if (!$featuredPostId)
|
if (!$featuredPostId)
|
||||||
return null;
|
return null;
|
||||||
|
|
||||||
|
|
|
@ -427,17 +427,17 @@ class PostController
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
$this->context->transport->lastSearchQuery = InputHelper::get('last-search-query');
|
$this->context->transport->lastSearchQuery = InputHelper::get('last-search-query');
|
||||||
$prevPostQuery = $this->context->transport->lastSearchQuery . ' prev:' . $id;
|
list ($prevPostId, $nextPostId) =
|
||||||
$nextPostQuery = $this->context->transport->lastSearchQuery . ' next:' . $id;
|
PostSearchService::getPostIdsAround(
|
||||||
$prevPost = current(PostSearchService::getEntities($prevPostQuery, 1, 1));
|
$this->context->transport->lastSearchQuery, $id);
|
||||||
$nextPost = current(PostSearchService::getEntities($nextPostQuery, 1, 1));
|
|
||||||
}
|
}
|
||||||
#search for some reason was invalid, e.g. tag was deleted in the meantime
|
#search for some reason was invalid, e.g. tag was deleted in the meantime
|
||||||
catch (Exception $e)
|
catch (Exception $e)
|
||||||
{
|
{
|
||||||
$this->context->transport->lastSearchQuery = '';
|
$this->context->transport->lastSearchQuery = '';
|
||||||
$prevPost = current(PostSearchService::getEntities('prev:' . $id, 1, 1));
|
list ($prevPostId, $nextPostId) =
|
||||||
$nextPost = current(PostSearchService::getEntities('next:' . $id, 1, 1));
|
PostSearchService::getPostIdsAround(
|
||||||
|
$this->context->transport->lastSearchQuery, $id);
|
||||||
}
|
}
|
||||||
PostSearchService::enableTokenLimit(true);
|
PostSearchService::enableTokenLimit(true);
|
||||||
|
|
||||||
|
@ -449,8 +449,8 @@ class PostController
|
||||||
$this->context->score = $score;
|
$this->context->score = $score;
|
||||||
$this->context->flagged = $flagged;
|
$this->context->flagged = $flagged;
|
||||||
$this->context->transport->post = $post;
|
$this->context->transport->post = $post;
|
||||||
$this->context->transport->prevPostId = $prevPost ? $prevPost->id : null;
|
$this->context->transport->prevPostId = $prevPostId ? $prevPostId : null;
|
||||||
$this->context->transport->nextPostId = $nextPost ? $nextPost->id : null;
|
$this->context->transport->nextPostId = $nextPostId ? $nextPostId : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -24,19 +24,19 @@ class Database
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static function makeStatement(SqlQuery $sqlQuery)
|
protected static function makeStatement(SqlStatement $stmt)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
$stmt = self::$pdo->prepare($sqlQuery->getSql());
|
$pdoStatement = self::$pdo->prepare($stmt->getAsString());
|
||||||
|
foreach ($stmt->getBindings() as $key => $value)
|
||||||
|
$pdoStatement->bindValue(is_numeric($key) ? $key + 1 : ltrim($key, ':'), $value);
|
||||||
}
|
}
|
||||||
catch (Exception $e)
|
catch (Exception $e)
|
||||||
{
|
{
|
||||||
throw new Exception('Problem with ' . $sqlQuery->getSql() . ' (' . $e->getMessage() . ')');
|
throw new Exception('Problem with ' . $stmt->getAsString() . ' (' . $e->getMessage() . ')');
|
||||||
}
|
}
|
||||||
foreach ($sqlQuery->getBindings() as $key => $value)
|
return $pdoStatement;
|
||||||
$stmt->bindValue(is_numeric($key) ? $key + 1 : $key, $value);
|
|
||||||
return $stmt;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static function disconnect()
|
public static function disconnect()
|
||||||
|
@ -49,32 +49,32 @@ class Database
|
||||||
return self::$pdo !== null;
|
return self::$pdo !== null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static function query(SqlQuery $sqlQuery)
|
public static function exec(SqlStatement $stmt)
|
||||||
{
|
{
|
||||||
if (!self::connected())
|
if (!self::connected())
|
||||||
throw new Exception('Database is not connected');
|
throw new Exception('Database is not connected');
|
||||||
$statement = self::makeStatement($sqlQuery);
|
$statement = self::makeStatement($stmt);
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
$statement->execute();
|
$statement->execute();
|
||||||
}
|
}
|
||||||
catch (Exception $e)
|
catch (Exception $e)
|
||||||
{
|
{
|
||||||
throw new Exception('Problem with ' . $sqlQuery->getSql() . ' (' . $e->getMessage() . ')');
|
throw new Exception('Problem with ' . $stmt->getAsString() . ' (' . $e->getMessage() . ')');
|
||||||
}
|
}
|
||||||
self::$queries []= $sqlQuery;
|
self::$queries []= $stmt;
|
||||||
return $statement;
|
return $statement;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static function fetchOne(SqlQuery $sqlQuery)
|
public static function fetchOne(SqlStatement $stmt)
|
||||||
{
|
{
|
||||||
$statement = self::query($sqlQuery);
|
$statement = self::exec($stmt);
|
||||||
return $statement->fetch();
|
return $statement->fetch();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static function fetchAll(SqlQuery $sqlQuery)
|
public static function fetchAll(SqlStatement $stmt)
|
||||||
{
|
{
|
||||||
$statement = self::query($sqlQuery);
|
$statement = self::exec($stmt);
|
||||||
return $statement->fetchAll();
|
return $statement->fetchAll();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -21,12 +21,12 @@ abstract class AbstractCrudModel implements IModel
|
||||||
|
|
||||||
public static function findById($key, $throw = true)
|
public static function findById($key, $throw = true)
|
||||||
{
|
{
|
||||||
$query = (new SqlQuery)
|
$stmt = new SqlSelectStatement();
|
||||||
->select('*')
|
$stmt->setColumn('*');
|
||||||
->from(static::getTableName())
|
$stmt->setTable(static::getTableName());
|
||||||
->where('id = ?')->put($key);
|
$stmt->setCriterion(new SqlEqualsOperator('id', new SqlBinding($key)));
|
||||||
|
|
||||||
$row = Database::fetchOne($query);
|
$row = Database::fetchOne($stmt);
|
||||||
if ($row)
|
if ($row)
|
||||||
return self::convertRow($row);
|
return self::convertRow($row);
|
||||||
|
|
||||||
|
@ -37,12 +37,12 @@ abstract class AbstractCrudModel implements IModel
|
||||||
|
|
||||||
public static function findByIds(array $ids)
|
public static function findByIds(array $ids)
|
||||||
{
|
{
|
||||||
$query = (new SqlQuery)
|
$stmt = new SqlSelectStatement();
|
||||||
->select('*')
|
$stmt->setColumn('*');
|
||||||
->from(static::getTableName())
|
$stmt->setTable(static::getTableName());
|
||||||
->where('id')->in()->genSlots($ids)->put($ids);
|
$stmt->setCriterion(SqlInOperator::fromArray('id', SqlBinding::fromArray($ids)));
|
||||||
|
|
||||||
$rows = Database::fetchAll($query);
|
$rows = Database::fetchAll($stmt);
|
||||||
if ($rows)
|
if ($rows)
|
||||||
return self::convertRows($rows);
|
return self::convertRows($rows);
|
||||||
|
|
||||||
|
@ -51,9 +51,10 @@ abstract class AbstractCrudModel implements IModel
|
||||||
|
|
||||||
public static function getCount()
|
public static function getCount()
|
||||||
{
|
{
|
||||||
$query = new SqlQuery();
|
$stmt = new SqlSelectStatement();
|
||||||
$query->select('count(1)')->as('count')->from(static::getTableName());
|
$stmt->setColumn(new SqlAliasOperator(new SqlCountOperator('1'), 'count'));
|
||||||
return Database::fetchOne($query)['count'];
|
$stmt->setTable(static::getTableName());
|
||||||
|
return Database::fetchOne($stmt)['count'];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -106,13 +107,9 @@ abstract class AbstractCrudModel implements IModel
|
||||||
throw new Exception('Can be run only within transaction');
|
throw new Exception('Can be run only within transaction');
|
||||||
if (!$entity->id)
|
if (!$entity->id)
|
||||||
{
|
{
|
||||||
$config = \Chibi\Registry::getConfig();
|
$stmt = new SqlInsertStatement();
|
||||||
$query = (new SqlQuery);
|
$stmt->setTable($table);
|
||||||
if ($config->main->dbDriver == 'sqlite')
|
Database::exec($stmt);
|
||||||
$query->insertInto($table)->defaultValues();
|
|
||||||
else
|
|
||||||
$query->insertInto($table)->values()->open()->close();
|
|
||||||
Database::query($query);
|
|
||||||
$entity->id = Database::lastInsertId();
|
$entity->id = Database::lastInsertId();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -25,13 +25,14 @@ class CommentModel extends AbstractCrudModel
|
||||||
'comment_date' => $comment->commentDate,
|
'comment_date' => $comment->commentDate,
|
||||||
'commenter_id' => $comment->commenterId];
|
'commenter_id' => $comment->commenterId];
|
||||||
|
|
||||||
$query = (new SqlQuery)
|
$stmt = new SqlUpdateStatement();
|
||||||
->update('comment')
|
$stmt->setTable('comment');
|
||||||
->set(join(', ', array_map(function($key) { return $key . ' = ?'; }, array_keys($bindings))))
|
$stmt->setCriterion(new SqlEqualsOperator('id', new SqlBinding($comment->id)));
|
||||||
->put(array_values($bindings))
|
|
||||||
->where('id = ?')->put($comment->id);
|
|
||||||
|
|
||||||
Database::query($query);
|
foreach ($bindings as $key => $val)
|
||||||
|
$stmt->setColumn($key, new SqlBinding($val));
|
||||||
|
|
||||||
|
Database::exec($stmt);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -39,10 +40,10 @@ class CommentModel extends AbstractCrudModel
|
||||||
{
|
{
|
||||||
Database::transaction(function() use ($comment)
|
Database::transaction(function() use ($comment)
|
||||||
{
|
{
|
||||||
$query = (new SqlQuery)
|
$stmt = new SqlDeleteStatement();
|
||||||
->deleteFrom('comment')
|
$stmt->setTable('comment');
|
||||||
->where('id = ?')->put($comment->id);
|
$stmt->setCriterion(new SqlEqualsOperator('id', new SqlBinding($comment->id)));
|
||||||
Database::query($query);
|
Database::exec($stmt);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -50,14 +51,12 @@ class CommentModel extends AbstractCrudModel
|
||||||
|
|
||||||
public static function findAllByPostId($key)
|
public static function findAllByPostId($key)
|
||||||
{
|
{
|
||||||
$query = new SqlQuery();
|
$stmt = new SqlSelectStatement();
|
||||||
$query
|
$stmt->setColumn('comment.*');
|
||||||
->select('comment.*')
|
$stmt->setTable('comment');
|
||||||
->from('comment')
|
$stmt->setCriterion(new SqlEqualsOperator('post_id', new SqlBinding($key)));
|
||||||
->where('post_id = ?')
|
|
||||||
->put($key);
|
|
||||||
|
|
||||||
$rows = Database::fetchAll($query);
|
$rows = Database::fetchAll($stmt);
|
||||||
if ($rows)
|
if ($rows)
|
||||||
return self::convertRows($rows);
|
return self::convertRows($rows);
|
||||||
return [];
|
return [];
|
||||||
|
|
|
@ -51,12 +51,12 @@ class PostEntity extends AbstractEntity
|
||||||
{
|
{
|
||||||
if ($this->hasCache('favoritee'))
|
if ($this->hasCache('favoritee'))
|
||||||
return $this->getCache('favoritee');
|
return $this->getCache('favoritee');
|
||||||
$query = (new SqlQuery)
|
$stmt = new SqlSelectStatement();
|
||||||
->select('user.*')
|
$stmt->setColumn('user.*');
|
||||||
->from('user')
|
$stmt->setTable('user');
|
||||||
->innerJoin('favoritee')->on('favoritee.user_id = user.id')
|
$stmt->addInnerJoin('favoritee', new SqlEqualsOperator('favoritee.user_id', 'user.id'));
|
||||||
->where('favoritee.post_id = ?')->put($this->id);
|
$stmt->setCriterion(new SqlEqualsOperator('favoritee.post_id', new SqlBinding($this->id)));
|
||||||
$rows = Database::fetchAll($query);
|
$rows = Database::fetchAll($stmt);
|
||||||
$favorites = UserModel::convertRows($rows);
|
$favorites = UserModel::convertRows($rows);
|
||||||
$this->setCache('favoritee', $favorites);
|
$this->setCache('favoritee', $favorites);
|
||||||
return $favorites;
|
return $favorites;
|
||||||
|
@ -69,13 +69,20 @@ class PostEntity extends AbstractEntity
|
||||||
if ($this->hasCache('relations'))
|
if ($this->hasCache('relations'))
|
||||||
return $this->getCache('relations');
|
return $this->getCache('relations');
|
||||||
|
|
||||||
$query = (new SqlQuery)
|
$stmt = new SqlSelectStatement();
|
||||||
->select('post.*')
|
$stmt->setColumn('post.*');
|
||||||
->from('post')
|
$stmt->setTable('post');
|
||||||
->innerJoin('crossref')
|
$binding = new SqlBinding($this->id);
|
||||||
->on()->open()->raw('post.id = crossref.post2_id')->and('crossref.post_id = ?')->close()->put($this->id)
|
$stmt->addInnerJoin('crossref', (new SqlDisjunction)
|
||||||
->or()->open()->raw('post.id = crossref.post_id')->and('crossref.post2_id = ?')->close()->put($this->id);
|
->add(
|
||||||
$rows = Database::fetchAll($query);
|
(new SqlConjunction)
|
||||||
|
->add(new SqlEqualsOperator('post.id', 'crossref.post2_id'))
|
||||||
|
->add(new SqlEqualsOperator('crossref.post_id', $binding)))
|
||||||
|
->add(
|
||||||
|
(new SqlConjunction)
|
||||||
|
->add(new SqlEqualsOperator('post.id', 'crossref.post_id'))
|
||||||
|
->add(new SqlEqualsOperator('crossref.post2_id', $binding))));
|
||||||
|
$rows = Database::fetchAll($stmt);
|
||||||
$posts = PostModel::convertRows($rows);
|
$posts = PostModel::convertRows($rows);
|
||||||
$this->setCache('relations', $posts);
|
$this->setCache('relations', $posts);
|
||||||
return $posts;
|
return $posts;
|
||||||
|
|
|
@ -5,10 +5,10 @@ class TagEntity extends AbstractEntity
|
||||||
|
|
||||||
public function getPostCount()
|
public function getPostCount()
|
||||||
{
|
{
|
||||||
$query = (new SqlQuery)
|
$stmt = new SqlSelectStatement();
|
||||||
->select('count(*)')->as('count')
|
$stmt->setColumn(new SqlAliasOperator(new SqlCountOperator('1'), 'count'));
|
||||||
->from('post_tag')
|
$stmt->setTable('post_tag');
|
||||||
->where('tag_id = ?')->put($this->id);
|
$stmt->setCriterion(new SqlEqualsOperator('tag_id', new SqlBinding($this->id)));
|
||||||
return Database::fetchOne($query)['count'];
|
return Database::fetchOne($stmt)['count'];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -111,22 +111,24 @@ class UserEntity extends AbstractEntity
|
||||||
|
|
||||||
public function hasFavorited($post)
|
public function hasFavorited($post)
|
||||||
{
|
{
|
||||||
$query = (new SqlQuery)
|
$stmt = new SqlSelectStatement();
|
||||||
->select('count(1)')->as('count')
|
$stmt->setColumn(new SqlAliasOperator(new SqlCountOperator('1'), 'count'));
|
||||||
->from('favoritee')
|
$stmt->setTable('favoritee');
|
||||||
->where('user_id = ?')->put($this->id)
|
$stmt->setCriterion((new SqlConjunction)
|
||||||
->and('post_id = ?')->put($post->id);
|
->add(new SqlEqualsOperator('user_id', new SqlBinding($this->id)))
|
||||||
return Database::fetchOne($query)['count'] == 1;
|
->add(new SqlEqualsOperator('post_id', new SqlBinding($post->id))));
|
||||||
|
return Database::fetchOne($stmt)['count'] == 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getScore($post)
|
public function getScore($post)
|
||||||
{
|
{
|
||||||
$query = (new SqlQuery)
|
$stmt = new SqlSelectStatement();
|
||||||
->select('score')
|
$stmt->setColumn('score');
|
||||||
->from('post_score')
|
$stmt->setTable('post_score');
|
||||||
->where('user_id = ?')->put($this->id)
|
$stmt->setCriterion((new SqlConjunction)
|
||||||
->and('post_id = ?')->put($post->id);
|
->add(new SqlEqualsOperator('user_id', new SqlBinding($this->id)))
|
||||||
$row = Database::fetchOne($query);
|
->add(new SqlEqualsOperator('post_id', new SqlBinding($post->id))));
|
||||||
|
$row = Database::fetchOne($stmt);
|
||||||
if ($row)
|
if ($row)
|
||||||
return intval($row['score']);
|
return intval($row['score']);
|
||||||
return null;
|
return null;
|
||||||
|
@ -134,28 +136,28 @@ class UserEntity extends AbstractEntity
|
||||||
|
|
||||||
public function getFavoriteCount()
|
public function getFavoriteCount()
|
||||||
{
|
{
|
||||||
$sqlQuery = (new SqlQuery)
|
$stmt = new SqlSelectStatement();
|
||||||
->select('count(1)')->as('count')
|
$stmt->setColumn(new SqlAliasOperator(new SqlCountOperator('1'), 'count'));
|
||||||
->from('favoritee')
|
$stmt->setTable('favoritee');
|
||||||
->where('user_id = ?')->put($this->id);
|
$stmt->setCriterion(new SqlEqualsOperator('user_id', new SqlBinding($this->id)));
|
||||||
return Database::fetchOne($sqlQuery)['count'];
|
return Database::fetchOne($stmt)['count'];
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getCommentCount()
|
public function getCommentCount()
|
||||||
{
|
{
|
||||||
$sqlQuery = (new SqlQuery)
|
$stmt = new SqlSelectStatement();
|
||||||
->select('count(1)')->as('count')
|
$stmt->setColumn(new SqlAliasOperator(new SqlCountOperator('1'), 'count'));
|
||||||
->from('comment')
|
$stmt->setTable('comment');
|
||||||
->where('commenter_id = ?')->put($this->id);
|
$stmt->setCriterion(new SqlEqualsOperator('commenter_id', new SqlBinding($this->id)));
|
||||||
return Database::fetchOne($sqlQuery)['count'];
|
return Database::fetchOne($stmt)['count'];
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getPostCount()
|
public function getPostCount()
|
||||||
{
|
{
|
||||||
$sqlQuery = (new SqlQuery)
|
$stmt = new SqlSelectStatement();
|
||||||
->select('count(1)')->as('count')
|
$stmt->setColumn(new SqlAliasOperator(new SqlCountOperator('1'), 'count'));
|
||||||
->from('post')
|
$stmt->setTable('post');
|
||||||
->where('uploader_id = ?')->put($this->id);
|
$stmt->setCriterion(new SqlEqualsOperator('uploader_id', new SqlBinding($this->id)));
|
||||||
return Database::fetchOne($sqlQuery)['count'];
|
return Database::fetchOne($stmt)['count'];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -48,48 +48,50 @@ class PostModel extends AbstractCrudModel
|
||||||
'source' => $post->source,
|
'source' => $post->source,
|
||||||
];
|
];
|
||||||
|
|
||||||
$query = (new SqlQuery)
|
$stmt = new SqlUpdateStatement();
|
||||||
->update('post')
|
$stmt->setTable('post');
|
||||||
->set(join(', ', array_map(function($key) { return $key . ' = ?'; }, array_keys($bindings))))
|
|
||||||
->put(array_values($bindings))
|
foreach ($bindings as $key => $value)
|
||||||
->where('id = ?')->put($post->id);
|
$stmt->setColumn($key, new SqlBinding($value));
|
||||||
Database::query($query);
|
|
||||||
|
$stmt->setCriterion(new SqlEqualsOperator('id', new SqlBinding($post->id)));
|
||||||
|
Database::exec($stmt);
|
||||||
|
|
||||||
//tags
|
//tags
|
||||||
$tags = $post->getTags();
|
$tags = $post->getTags();
|
||||||
|
|
||||||
$query = (new SqlQuery)
|
$stmt = new SqlDeleteStatement();
|
||||||
->deleteFrom('post_tag')
|
$stmt->setTable('post_tag');
|
||||||
->where('post_id = ?')->put($post->id);
|
$stmt->setCriterion(new SqlEqualsOperator('post_id', new SqlBinding($post->id)));
|
||||||
Database::query($query);
|
Database::exec($stmt);
|
||||||
|
|
||||||
foreach ($tags as $postTag)
|
foreach ($tags as $postTag)
|
||||||
{
|
{
|
||||||
$query = (new SqlQuery)
|
$stmt = new SqlInsertStatement();
|
||||||
->insertInto('post_tag')
|
$stmt->setTable('post_tag');
|
||||||
->surround('post_id, tag_id')
|
$stmt->setColumn('post_id', new SqlBinding($post->id));
|
||||||
->values()->surround('?, ?')
|
$stmt->setColumn('tag_id', new SqlBinding($postTag->id));
|
||||||
->put([$post->id, $postTag->id]);
|
Database::exec($stmt);
|
||||||
Database::query($query);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
//relations
|
//relations
|
||||||
$relations = $post->getRelations();
|
$relations = $post->getRelations();
|
||||||
|
|
||||||
$query = (new SqlQuery)
|
$stmt = new SqlDeleteStatement();
|
||||||
->deleteFrom('crossref')
|
$stmt->setTable('crossref');
|
||||||
->where('post_id = ?')->put($post->id)
|
$binding = new SqlBinding($post->id);
|
||||||
->or('post2_id = ?')->put($post->id);
|
$stmt->setCriterion((new SqlDisjunction)
|
||||||
Database::query($query);
|
->add(new SqlEqualsOperator('post_id', $binding))
|
||||||
|
->add(new SqlEqualsOperator('post2_id', $binding)));
|
||||||
|
Database::exec($stmt);
|
||||||
|
|
||||||
foreach ($relations as $relatedPost)
|
foreach ($relations as $relatedPost)
|
||||||
{
|
{
|
||||||
$query = (new SqlQuery)
|
$stmt = new SqlInsertStatement();
|
||||||
->insertInto('crossref')
|
$stmt->setTable('crossref');
|
||||||
->surround('post_id, post2_id')
|
$stmt->setColumn('post_id', new SqlBinding($post->id));
|
||||||
->values()->surround('?, ?')
|
$stmt->setColumn('post2_id', new SqlBinding($relatedPost->id));
|
||||||
->put([$post->id, $relatedPost->id]);
|
Database::exec($stmt);
|
||||||
Database::query($query);
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -98,36 +100,31 @@ class PostModel extends AbstractCrudModel
|
||||||
{
|
{
|
||||||
Database::transaction(function() use ($post)
|
Database::transaction(function() use ($post)
|
||||||
{
|
{
|
||||||
$queries = [];
|
$binding = new SqlBinding($post->id);
|
||||||
|
|
||||||
$queries []= (new SqlQuery)
|
$stmt = new SqlDeleteStatement();
|
||||||
->deleteFrom('post_score')
|
$stmt->setTable('post_score');
|
||||||
->where('post_id = ?')->put($post->id);
|
$stmt->setCriterion(new SqlEqualsOperator('post_id', $binding));
|
||||||
|
Database::exec($stmt);
|
||||||
|
|
||||||
$queries []= (new SqlQuery)
|
$stmt->setTable('post_tag');
|
||||||
->deleteFrom('post_tag')
|
Database::exec($stmt);
|
||||||
->where('post_id = ?')->put($post->id);
|
|
||||||
|
|
||||||
$queries []= (new SqlQuery)
|
$stmt->setTable('favoritee');
|
||||||
->deleteFrom('crossref')
|
Database::exec($stmt);
|
||||||
->where('post_id = ?')->put($post->id)
|
|
||||||
->or('post2_id = ?')->put($post->id);
|
|
||||||
|
|
||||||
$queries []= (new SqlQuery)
|
$stmt->setTable('comment');
|
||||||
->deleteFrom('favoritee')
|
Database::exec($stmt);
|
||||||
->where('post_id = ?')->put($post->id);
|
|
||||||
|
|
||||||
$queries []= (new SqlQuery)
|
$stmt->setTable('crossref');
|
||||||
->update('comment')
|
$stmt->setCriterion((new SqlDisjunction)
|
||||||
->set('post_id = NULL')
|
->add(new SqlEqualsOperator('post_id', $binding))
|
||||||
->where('post_id = ?')->put($post->id);
|
->add(new SqlEqualsOperator('post_id', $binding)));
|
||||||
|
Database::exec($stmt);
|
||||||
|
|
||||||
$queries []= (new SqlQuery)
|
$stmt->setTable('post');
|
||||||
->deleteFrom('post')
|
$stmt->setCriterion(new SqlEqualsOperator('id', $binding));
|
||||||
->where('id = ?')->put($post->id);
|
Database::exec($stmt);
|
||||||
|
|
||||||
foreach ($queries as $query)
|
|
||||||
Database::query($query);
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -136,12 +133,12 @@ class PostModel extends AbstractCrudModel
|
||||||
|
|
||||||
public static function findByName($key, $throw = true)
|
public static function findByName($key, $throw = true)
|
||||||
{
|
{
|
||||||
$query = (new SqlQuery)
|
$stmt = new SqlSelectStatement();
|
||||||
->select('*')
|
$stmt->setColumn('*');
|
||||||
->from('post')
|
$stmt->setTable('post');
|
||||||
->where('name = ?')->put($key);
|
$stmt->setCriterion(new SqlEqualsOperator('name', new SqlBinding($key)));
|
||||||
|
|
||||||
$row = Database::fetchOne($query);
|
$row = Database::fetchOne($stmt);
|
||||||
if ($row)
|
if ($row)
|
||||||
return self::convertRow($row);
|
return self::convertRow($row);
|
||||||
|
|
||||||
|
@ -161,12 +158,12 @@ class PostModel extends AbstractCrudModel
|
||||||
|
|
||||||
public static function findByHash($key, $throw = true)
|
public static function findByHash($key, $throw = true)
|
||||||
{
|
{
|
||||||
$query = (new SqlQuery)
|
$stmt = new SqlSelectStatement();
|
||||||
->select('*')
|
$stmt->setColumn('*');
|
||||||
->from('post')
|
$stmt->setTable('post');
|
||||||
->where('file_hash = ?')->put($key);
|
$stmt->setCriterion(new SqlEqualsOperator('file_hash', new SqlBinding($key)));
|
||||||
|
|
||||||
$row = Database::fetchOne($query);
|
$row = Database::fetchOne($stmt);
|
||||||
if ($row)
|
if ($row)
|
||||||
return self::convertRow($row);
|
return self::convertRow($row);
|
||||||
|
|
||||||
|
@ -192,12 +189,13 @@ class PostModel extends AbstractCrudModel
|
||||||
}
|
}
|
||||||
$postIds = array_keys($postMap);
|
$postIds = array_keys($postMap);
|
||||||
|
|
||||||
$sqlQuery = (new SqlQuery)
|
$stmt = new SqlSelectStatement();
|
||||||
->select('tag.*, post_id')
|
$stmt->setTable('tag');
|
||||||
->from('tag')
|
$stmt->addColumn('tag.*');
|
||||||
->innerJoin('post_tag')->on('post_tag.tag_id = tag.id')
|
$stmt->addColumn('post_id');
|
||||||
->where('post_id')->in()->genSlots($postIds)->put($postIds);
|
$stmt->addInnerJoin('post_tag', new SqlEqualsOperator('post_tag.tag_id', 'tag.id'));
|
||||||
$rows = Database::fetchAll($sqlQuery);
|
$stmt->setCriterion(SqlInOperator::fromArray('post_id', SqlBinding::fromArray($postIds)));
|
||||||
|
$rows = Database::fetchAll($stmt);
|
||||||
|
|
||||||
foreach ($rows as $row)
|
foreach ($rows as $row)
|
||||||
{
|
{
|
||||||
|
|
|
@ -20,8 +20,10 @@ class PropertyModel implements IModel
|
||||||
{
|
{
|
||||||
self::$loaded = true;
|
self::$loaded = true;
|
||||||
self::$allProperties = [];
|
self::$allProperties = [];
|
||||||
$query = (new SqlQuery())->select('*')->from('property');
|
$stmt = new SqlSelectStatement();
|
||||||
foreach (Database::fetchAll($query) as $row)
|
$stmt ->setColumn('*');
|
||||||
|
$stmt ->setTable('property');
|
||||||
|
foreach (Database::fetchAll($stmt) as $row)
|
||||||
self::$allProperties[$row['prop_id']] = $row['value'];
|
self::$allProperties[$row['prop_id']] = $row['value'];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -39,33 +41,26 @@ class PropertyModel implements IModel
|
||||||
self::loadIfNecessary();
|
self::loadIfNecessary();
|
||||||
Database::transaction(function() use ($propertyId, $value)
|
Database::transaction(function() use ($propertyId, $value)
|
||||||
{
|
{
|
||||||
$row = Database::query((new SqlQuery)
|
$stmt = new SqlSelectStatement();
|
||||||
->select('id')
|
$stmt->setColumn('id');
|
||||||
->from('property')
|
$stmt->setTable('property');
|
||||||
->where('prop_id = ?')
|
$stmt->setCriterion(new SqlEqualsOperator('prop_id', new SqlBinding($propertyId)));
|
||||||
->put($propertyId));
|
$row = Database::fetchOne($stmt);
|
||||||
|
|
||||||
$query = (new SqlQuery);
|
|
||||||
|
|
||||||
if ($row)
|
if ($row)
|
||||||
{
|
{
|
||||||
$query
|
$stmt = new SqlUpdateStatement();
|
||||||
->update('property')
|
$stmt->setCriterion(new SqlEqualsOperator('prop_id', new SqlBinding($propertyId)));
|
||||||
->set('value = ?')
|
|
||||||
->put($value)
|
|
||||||
->where('prop_id = ?')
|
|
||||||
->put($propertyId);
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
$query
|
$stmt = new SqlInsertStatement();
|
||||||
->insertInto('property')
|
$stmt->setColumn('prop_id', new SqlBinding($propertyId));
|
||||||
->open()->raw('prop_id, value_id')->close()
|
|
||||||
->open()->raw('?, ?')->close()
|
|
||||||
->put([$propertyId, $value]);
|
|
||||||
}
|
}
|
||||||
|
$stmt->setTable('property');
|
||||||
|
$stmt->setColumn('value', new SqlBinding($value));
|
||||||
|
|
||||||
Database::query($query);
|
Database::exec($stmt);
|
||||||
|
|
||||||
self::$allProperties[$propertyId] = $value;
|
self::$allProperties[$propertyId] = $value;
|
||||||
});
|
});
|
||||||
|
|
|
@ -8,17 +8,18 @@ abstract class AbstractSearchService
|
||||||
return $modelClassName;
|
return $modelClassName;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected static function decorate(SqlQuery $sqlQuery, $searchQuery)
|
protected static function decorate(SqlSelectStatement $stmt, $searchQuery)
|
||||||
{
|
{
|
||||||
throw new NotImplementedException();
|
throw new NotImplementedException();
|
||||||
}
|
}
|
||||||
|
|
||||||
protected static function decoratePager(SqlQuery $sqlQuery, $perPage, $page)
|
protected static function decoratePager(SqlSelectStatement $stmt, $perPage, $page)
|
||||||
{
|
{
|
||||||
if ($perPage === null)
|
if ($perPage === null)
|
||||||
return;
|
return;
|
||||||
$sqlQuery->limit('?')->put($perPage);
|
$stmt->setLimit(
|
||||||
$sqlQuery->offset('?')->put(($page - 1) * $perPage);
|
new SqlBinding($perPage),
|
||||||
|
new SqlBinding(($page - 1) * $perPage));
|
||||||
}
|
}
|
||||||
|
|
||||||
public static function getEntitiesRows($searchQuery, $perPage = null, $page = 1)
|
public static function getEntitiesRows($searchQuery, $perPage = null, $page = 1)
|
||||||
|
@ -26,12 +27,12 @@ abstract class AbstractSearchService
|
||||||
$modelClassName = self::getModelClassName();
|
$modelClassName = self::getModelClassName();
|
||||||
$table = $modelClassName::getTableName();
|
$table = $modelClassName::getTableName();
|
||||||
|
|
||||||
$sqlQuery = new SqlQuery();
|
$stmt = new SqlSelectStatement();
|
||||||
$sqlQuery->select($table . '.*');
|
$stmt->setColumn($table . '.*');
|
||||||
static::decorate($sqlQuery, $searchQuery);
|
static::decorate($stmt, $searchQuery);
|
||||||
self::decoratePager($sqlQuery, $perPage, $page);
|
static::decoratePager($stmt, $perPage, $page);
|
||||||
|
|
||||||
$rows = Database::fetchAll($sqlQuery);
|
$rows = Database::fetchAll($stmt);
|
||||||
return $rows;
|
return $rows;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -47,12 +48,13 @@ abstract class AbstractSearchService
|
||||||
$modelClassName = self::getModelClassName();
|
$modelClassName = self::getModelClassName();
|
||||||
$table = $modelClassName::getTableName();
|
$table = $modelClassName::getTableName();
|
||||||
|
|
||||||
$sqlQuery = new SqlQuery();
|
$innerStmt = new SqlSelectStatement();
|
||||||
$sqlQuery->select('count(1)')->as('count');
|
static::decorate($innerStmt, $searchQuery);
|
||||||
$sqlQuery->from()->raw('(')->select('1');
|
|
||||||
static::decorate($sqlQuery, $searchQuery);
|
|
||||||
$sqlQuery->raw(')');
|
|
||||||
|
|
||||||
return Database::fetchOne($sqlQuery)['count'];
|
$stmt = new SqlSelectStatement();
|
||||||
|
$stmt->setColumn(new SqlAliasOperator(new SqlCountOperator('1'), 'count'));
|
||||||
|
$stmt->setSource($innerStmt);
|
||||||
|
|
||||||
|
return Database::fetchOne($stmt)['count'];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,21 +1,14 @@
|
||||||
<?php
|
<?php
|
||||||
class CommentSearchService extends AbstractSearchService
|
class CommentSearchService extends AbstractSearchService
|
||||||
{
|
{
|
||||||
public static function decorate(SqlQuery $sqlQuery, $searchQuery)
|
public static function decorate(SqlSelectStatement $stmt, $searchQuery)
|
||||||
{
|
{
|
||||||
$sqlQuery
|
$stmt->setTable('comment');
|
||||||
->from('comment')
|
$stmt->addInnerJoin('post', new SqlEqualsOperator('post_id', 'post.id'));
|
||||||
->innerJoin('post')
|
|
||||||
->on('post_id = post.id');
|
|
||||||
|
|
||||||
$allowedSafety = PrivilegesHelper::getAllowedSafety();
|
$allowedSafety = PrivilegesHelper::getAllowedSafety();
|
||||||
if (empty($allowedSafety))
|
$stmt->setCriterion(SqlInOperator::fromArray('post.safety', SqlBinding::fromArray($allowedSafety)));
|
||||||
$sqlQuery->where('0');
|
|
||||||
else
|
|
||||||
$sqlQuery->where('post.safety')->in()->genSlots($allowedSafety)->put($allowedSafety);
|
|
||||||
|
|
||||||
$sqlQuery
|
$stmt->addOrderBy('comment.id', SqlSelectStatement::ORDER_DESC);
|
||||||
->orderBy('comment.id')
|
|
||||||
->desc();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,108 +3,148 @@ class PostSearchService extends AbstractSearchService
|
||||||
{
|
{
|
||||||
private static $enableTokenLimit = true;
|
private static $enableTokenLimit = true;
|
||||||
|
|
||||||
|
public static function getPostIdsAround($searchQuery, $postId)
|
||||||
|
{
|
||||||
|
return Database::transaction(function() use ($searchQuery, $postId)
|
||||||
|
{
|
||||||
|
$stmt = new SqlRawStatement('CREATE TEMPORARY TABLE IF NOT EXISTS post_search(id INTEGER PRIMARY KEY, post_id INTEGER)');
|
||||||
|
Database::exec($stmt);
|
||||||
|
|
||||||
|
$stmt = new SqlDeleteStatement();
|
||||||
|
$stmt->setTable('post_search');
|
||||||
|
Database::exec($stmt);
|
||||||
|
|
||||||
|
$innerStmt = new SqlSelectStatement($searchQuery);
|
||||||
|
$innerStmt->setColumn('id');
|
||||||
|
self::decorate($innerStmt, $searchQuery);
|
||||||
|
$stmt = new SqlInsertStatement();
|
||||||
|
$stmt->setTable('post_search');
|
||||||
|
$stmt->setSource(['post_id'], $innerStmt);
|
||||||
|
Database::exec($stmt);
|
||||||
|
|
||||||
|
$stmt = new SqlSelectStatement();
|
||||||
|
$stmt->setColumn('post_id');
|
||||||
|
$stmt->setTable('post_search');
|
||||||
|
$stmt->setCriterion(
|
||||||
|
new SqlEqualsOrLesserOperator(
|
||||||
|
new SqlAbsOperator(
|
||||||
|
new SqlSubtractionOperator('id', (new SqlSelectStatement)
|
||||||
|
->setTable('post_search')
|
||||||
|
->setColumn('id')
|
||||||
|
->setCriterion(new SqlEqualsOperator('post_id', new SqlBinding($postId))))),
|
||||||
|
1));
|
||||||
|
$rows = Database::fetchAll($stmt);
|
||||||
|
$ids = array_map(function($row) { return $row['post_id']; }, $rows);
|
||||||
|
|
||||||
|
if (count($ids) == 1 or // no prev and no next post
|
||||||
|
count($ids) == 0) // even the post we are looking at is hidden from normal search for whatever reason
|
||||||
|
{
|
||||||
|
return [null, null];
|
||||||
|
}
|
||||||
|
elseif (count($ids) == 2) // no prev or no next post
|
||||||
|
{
|
||||||
|
return $ids[0] == $postId
|
||||||
|
? [$ids[1], null]
|
||||||
|
: [null, $ids[0]];
|
||||||
|
}
|
||||||
|
elseif (count($ids) == 3) // both prev and next post
|
||||||
|
{
|
||||||
|
return [$ids[2], $ids[0]];
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
throw new Exception('Unexpected result count (ids: ' . join(',', $ids) . ')');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
public static function enableTokenLimit($enable)
|
public static function enableTokenLimit($enable)
|
||||||
{
|
{
|
||||||
self::$enableTokenLimit = $enable;
|
self::$enableTokenLimit = $enable;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected static function filterUserSafety(SqlQuery $sqlQuery)
|
protected static function decorateNegation(SqlExpression $criterion, $negative)
|
||||||
|
{
|
||||||
|
return !$negative
|
||||||
|
? $criterion
|
||||||
|
: new SqlNegationOperator($criterion);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected static function filterUserSafety(SqlSelectStatement $stmt)
|
||||||
{
|
{
|
||||||
$allowedSafety = PrivilegesHelper::getAllowedSafety();
|
$allowedSafety = PrivilegesHelper::getAllowedSafety();
|
||||||
if (empty($allowedSafety))
|
$stmt->getCriterion()->add(SqlInOperator::fromArray('safety', SqlBinding::fromArray($allowedSafety)));
|
||||||
$sqlQuery->raw('0');
|
|
||||||
else
|
|
||||||
$sqlQuery->raw('safety')->in()->genSlots($allowedSafety)->put($allowedSafety);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected static function filterChain(SqlQuery $sqlQuery)
|
protected static function filterTag(SqlSelectStatement $stmt, $val, $neg)
|
||||||
{
|
|
||||||
if (isset($sqlQuery->__chained))
|
|
||||||
$sqlQuery->and();
|
|
||||||
else
|
|
||||||
$sqlQuery->where();
|
|
||||||
$sqlQuery->__chained = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected static function filterNegate(SqlQuery $sqlQuery)
|
|
||||||
{
|
|
||||||
$sqlQuery->not();
|
|
||||||
}
|
|
||||||
|
|
||||||
protected static function filterTag($sqlQuery, $val)
|
|
||||||
{
|
{
|
||||||
$tag = TagModel::findByName($val);
|
$tag = TagModel::findByName($val);
|
||||||
$sqlQuery
|
$innerStmt = new SqlSelectStatement();
|
||||||
->exists()
|
$innerStmt->setTable('post_tag');
|
||||||
->open()
|
$innerStmt->setCriterion((new SqlConjunction)
|
||||||
->select('1')
|
->add(new SqlEqualsOperator('post_id', 'post.id'))
|
||||||
->from('post_tag')
|
->add(new SqlEqualsOperator('post_tag.tag_id', new SqlBinding($tag->id))));
|
||||||
->where('post_id = post.id')
|
$stmt->getCriterion()->add(self::decorateNegation(new SqlExistsOperator($innerStmt), $neg));
|
||||||
->and('post_tag.tag_id = ?')->put($tag->id)
|
|
||||||
->close();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected static function filterTokenId($searchContext, SqlQuery $sqlQuery, $val)
|
protected static function filterTokenId($val)
|
||||||
{
|
{
|
||||||
$ids = preg_split('/[;,]/', $val);
|
$ids = preg_split('/[;,]/', $val);
|
||||||
$ids = array_map('intval', $ids);
|
$ids = array_map('intval', $ids);
|
||||||
if (empty($ids))
|
return SqlInOperator::fromArray('id', $ids);
|
||||||
$sqlQuery->raw('0');
|
|
||||||
else
|
|
||||||
$sqlQuery->raw('id')->in()->genSlots($ids)->put($ids);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected static function filterTokenIdMin($searchContext, SqlQuery $sqlQuery, $val)
|
protected static function filterTokenIdMin($val)
|
||||||
{
|
{
|
||||||
$sqlQuery->raw('id >= ?')->put(intval($val));
|
return new SqlEqualsOrGreaterOperator('id', new SqlBinding(intval($val)));
|
||||||
}
|
}
|
||||||
|
|
||||||
protected static function filterTokenIdMax($searchContext, SqlQuery $sqlQuery, $val)
|
protected static function filterTokenIdMax($val)
|
||||||
{
|
{
|
||||||
$sqlQuery->raw('id <= ?')->put(intval($val));
|
return new SqlEqualsOrLesserOperator('id', new SqlBinding(intval($val)));
|
||||||
}
|
}
|
||||||
|
|
||||||
protected static function filterTokenScoreMin($searchContext, SqlQuery $sqlQuery, $val)
|
protected static function filterTokenScoreMin($val)
|
||||||
{
|
{
|
||||||
$sqlQuery->raw('score >= ?')->put(intval($val));
|
return new SqlEqualsOrGreaterOperator('score', new SqlBinding(intval($val)));
|
||||||
}
|
}
|
||||||
|
|
||||||
protected static function filterTokenScoreMax($searchContext, SqlQuery $sqlQuery, $val)
|
protected static function filterTokenScoreMax($val)
|
||||||
{
|
{
|
||||||
$sqlQuery->raw('score <= ?')->put(intval($val));
|
return new SqlEqualsOrLesserOperator('score', new SqlBinding(intval($val)));
|
||||||
}
|
}
|
||||||
|
|
||||||
protected static function filterTokenTagMin($searchContext, SqlQuery $sqlQuery, $val)
|
protected static function filterTokenTagMin($val)
|
||||||
{
|
{
|
||||||
$sqlQuery->raw('tag_count >= ?')->put(intval($val));
|
return new SqlEqualsOrGreaterOperator('tag_count', new SqlBinding(intval($val)));
|
||||||
}
|
}
|
||||||
|
|
||||||
protected static function filterTokenTagMax($searchContext, SqlQuery $sqlQuery, $val)
|
protected static function filterTokenTagMax($val)
|
||||||
{
|
{
|
||||||
$sqlQuery->raw('tag_count <= ?')->put(intval($val));
|
return new SqlEqualsOrLesserOperator('tag_count', new SqlBinding(intval($val)));
|
||||||
}
|
}
|
||||||
|
|
||||||
protected static function filterTokenFavMin($searchContext, SqlQuery $sqlQuery, $val)
|
protected static function filterTokenFavMin($val)
|
||||||
{
|
{
|
||||||
$sqlQuery->raw('fav_count >= ?')->put(intval($val));
|
return new SqlEqualsOrGreaterOperator('fav_count', new SqlBinding(intval($val)));
|
||||||
}
|
}
|
||||||
|
|
||||||
protected static function filterTokenFavMax($searchContext, SqlQuery $sqlQuery, $val)
|
protected static function filterTokenFavMax($val)
|
||||||
{
|
{
|
||||||
$sqlQuery->raw('fav_count <= ?')->put(intval($val));
|
return new SqlEqualsOrLesserOperator('fav_count', new SqlBinding(intval($val)));
|
||||||
}
|
}
|
||||||
|
|
||||||
protected static function filterTokenCommentMin($searchContext, SqlQuery $sqlQuery, $val)
|
protected static function filterTokenCommentMin($val)
|
||||||
{
|
{
|
||||||
$sqlQuery->raw('comment_count >= ?')->put(intval($val));
|
return new SqlEqualsOrGreaterOperator('comment_count', new SqlBinding(intval($val)));
|
||||||
}
|
}
|
||||||
|
|
||||||
protected static function filterTokenCommentMax($searchContext, SqlQuery $sqlQuery, $val)
|
protected static function filterTokenCommentMax($val)
|
||||||
{
|
{
|
||||||
$sqlQuery->raw('comment_count <= ?')->put(intval($val));
|
return new SqlEqualsOrLesserOperator('comment_count', new SqlBinding(intval($val)));
|
||||||
}
|
}
|
||||||
|
|
||||||
protected static function filterTokenSpecial($searchContext, SqlQuery $sqlQuery, $val)
|
protected static function filterTokenSpecial($val)
|
||||||
{
|
{
|
||||||
$context = \Chibi\Registry::getContext();
|
$context = \Chibi\Registry::getContext();
|
||||||
|
|
||||||
|
@ -112,40 +152,33 @@ class PostSearchService extends AbstractSearchService
|
||||||
{
|
{
|
||||||
case 'liked':
|
case 'liked':
|
||||||
case 'likes':
|
case 'likes':
|
||||||
$sqlQuery
|
$innerStmt = new SqlSelectStatement();
|
||||||
->exists()
|
$innerStmt->setTable('post_score');
|
||||||
->open()
|
$innerStmt->setCriterion((new SqlConjunction)
|
||||||
->select('1')
|
->add(new SqlGreaterOperator('score', '0'))
|
||||||
->from('post_score')
|
->add(new SqlEqualsOperator('post_id', 'post.id'))
|
||||||
->where('post_id = post.id')
|
->add(new SqlEqualsOperator('user_id', new SqlBinding($context->user->id))));
|
||||||
->and('score > 0')
|
return new SqlExistsOperator($innerStmt);
|
||||||
->and('user_id = ?')->put($context->user->id)
|
|
||||||
->close();
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'disliked':
|
case 'disliked':
|
||||||
case 'dislikes':
|
case 'dislikes':
|
||||||
$sqlQuery
|
$innerStmt = new SqlSelectStatement();
|
||||||
->exists()
|
$innerStmt->setTable('post_score');
|
||||||
->open()
|
$innerStmt->setCriterion((new SqlConjunction)
|
||||||
->select('1')
|
->add(new SqlLesserOperator('score', '0'))
|
||||||
->from('post_score')
|
->add(new SqlEqualsOperator('post_id', 'post.id'))
|
||||||
->where('post_id = post.id')
|
->add(new SqlEqualsOperator('user_id', new SqlBinding($context->user->id))));
|
||||||
->and('score < 0')
|
return new SqlExistsOperator($innerStmt);
|
||||||
->and('user_id = ?')->put($context->user->id)
|
|
||||||
->close();
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'hidden':
|
case 'hidden':
|
||||||
$sqlQuery->raw('hidden');
|
return new SqlStringExpression('hidden');
|
||||||
break;
|
|
||||||
|
|
||||||
default:
|
default:
|
||||||
throw new SimpleException('Unknown special "' . $val . '"');
|
throw new SimpleException('Unknown special "' . $val . '"');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected static function filterTokenType($searchContext, SqlQuery $sqlQuery, $val)
|
protected static function filterTokenType($val)
|
||||||
{
|
{
|
||||||
switch ($val)
|
switch ($val)
|
||||||
{
|
{
|
||||||
|
@ -162,7 +195,7 @@ class PostSearchService extends AbstractSearchService
|
||||||
default:
|
default:
|
||||||
throw new SimpleException('Unknown type "' . $val . '"');
|
throw new SimpleException('Unknown type "' . $val . '"');
|
||||||
}
|
}
|
||||||
$sqlQuery->raw('type = ?')->put($type);
|
return new SqlEqualsOperator('type', new SqlBinding($type));
|
||||||
}
|
}
|
||||||
|
|
||||||
protected static function __filterTokenDateParser($val)
|
protected static function __filterTokenDateParser($val)
|
||||||
|
@ -180,142 +213,101 @@ class PostSearchService extends AbstractSearchService
|
||||||
return [$timeMin, $timeMax];
|
return [$timeMin, $timeMax];
|
||||||
}
|
}
|
||||||
|
|
||||||
protected static function filterTokenDate($searchContext, SqlQuery $sqlQuery, $val)
|
protected static function filterTokenDate($val)
|
||||||
{
|
{
|
||||||
list ($timeMin, $timeMax) = self::__filterTokenDateParser($val);
|
list ($timeMin, $timeMax) = self::__filterTokenDateParser($val);
|
||||||
$sqlQuery
|
return (new SqlConjunction)
|
||||||
->raw('upload_date >= ?')->put($timeMin)
|
->add(new SqlEqualsOrGreaterOperator('upload_date', new SqlBinding($timeMin)))
|
||||||
->and('upload_date <= ?')->put($timeMax);
|
->add(new SqlEqualsOrLesserOperator('upload_date', new SqlBinding($timeMax)));
|
||||||
}
|
}
|
||||||
|
|
||||||
protected static function filterTokenDateMin($searchContext, SqlQuery $sqlQuery, $val)
|
protected static function filterTokenDateMin($val)
|
||||||
{
|
{
|
||||||
list ($timeMin, $timeMax) = self::__filterTokenDateParser($val);
|
list ($timeMin, $timeMax) = self::__filterTokenDateParser($val);
|
||||||
$sqlQuery->raw('upload_date >= ?')->put($timeMin);
|
return new SqlEqualsOrGreaterOperator('upload_date', new SqlBinding($timeMin));
|
||||||
}
|
}
|
||||||
|
|
||||||
protected static function filterTokenDateMax($searchContext, SqlQuery $sqlQuery, $val)
|
protected static function filterTokenDateMax($val)
|
||||||
{
|
{
|
||||||
list ($timeMin, $timeMax) = self::__filterTokenDateParser($val);
|
list ($timeMin, $timeMax) = self::__filterTokenDateParser($val);
|
||||||
$sqlQuery->raw('upload_date <= ?')->put($timeMax);
|
return new SqlEqualsOrLesserOperator('upload_date', new SqlBinding($timeMax));
|
||||||
}
|
}
|
||||||
|
|
||||||
protected static function filterTokenFav($searchContext, SqlQuery $sqlQuery, $val)
|
protected static function filterTokenFav($val)
|
||||||
{
|
{
|
||||||
$user = UserModel::findByNameOrEmail($val);
|
$user = UserModel::findByNameOrEmail($val);
|
||||||
$sqlQuery
|
$innerStmt = (new SqlSelectStatement)
|
||||||
->exists()
|
->setTable('favoritee')
|
||||||
->open()
|
->setCriterion((new SqlConjunction)
|
||||||
->select('1')
|
->add(new SqlEqualsOperator('post_id', 'post.id'))
|
||||||
->from('favoritee')
|
->add(new SqlEqualsOperator('favoritee.user_id', new SqlBinding($user->id))));
|
||||||
->where('post_id = post.id')
|
return new SqlExistsOperator($innerStmt);
|
||||||
->and('favoritee.user_id = ?')->put($user->id)
|
|
||||||
->close();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected static function filterTokenFavs($searchContext, SqlQuery $sqlQuery, $val)
|
protected static function filterTokenFavs($val)
|
||||||
{
|
{
|
||||||
return self::filterTokenFav($searchContext, $sqlQuery, $val);
|
return self::filterTokenFav($val);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected static function filterTokenComment($searchContext, SqlQuery $sqlQuery, $val)
|
protected static function filterTokenComment($val)
|
||||||
{
|
{
|
||||||
$user = UserModel::findByNameOrEmail($val);
|
$user = UserModel::findByNameOrEmail($val);
|
||||||
$sqlQuery
|
$innerStmt = (new SqlSelectStatement)
|
||||||
->exists()
|
->setTable('comment')
|
||||||
->open()
|
->setCriterion((new SqlConjunction)
|
||||||
->select('1')
|
->add(new SqlEqualsOperator('post_id', 'post.id'))
|
||||||
->from('comment')
|
->add(new SqlEqualsOperator('commenter_id', new SqlBinding($user->id))));
|
||||||
->where('post_id = post.id')
|
return new SqlExistsOperator($innerStmt);
|
||||||
->and('commenter_id = ?')->put($user->id)
|
|
||||||
->close();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected static function filterTokenCommenter($searchContext, SqlQuery $sqlQuery, $val)
|
protected static function filterTokenCommenter($val)
|
||||||
{
|
{
|
||||||
return self::filterTokenComment($searchContext, $sqlQuery, $val);
|
return self::filterTokenComment($searchContext, $stmt, $val);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected static function filterTokenSubmit($searchContext, SqlQuery $sqlQuery, $val)
|
protected static function filterTokenSubmit($val)
|
||||||
{
|
{
|
||||||
$user = UserModel::findByNameOrEmail($val);
|
$user = UserModel::findByNameOrEmail($val);
|
||||||
$sqlQuery->raw('uploader_id = ?')->put($user->id);
|
return new SqlEqualsOperator('uploader_id', new SqlBinding($user->id));
|
||||||
}
|
}
|
||||||
|
|
||||||
protected static function filterTokenUploader($searchContext, SqlQuery $sqlQuery, $val)
|
protected static function filterTokenUploader($val)
|
||||||
{
|
{
|
||||||
return self::filterTokenSubmit($searchContext, $sqlQuery, $val);
|
return self::filterTokenSubmit($val);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected static function filterTokenUpload($searchContext, SqlQuery $sqlQuery, $val)
|
protected static function filterTokenUpload($val)
|
||||||
{
|
{
|
||||||
return self::filterTokenSubmit($searchContext, $sqlQuery, $val);
|
return self::filterTokenSubmit($val);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected static function filterTokenUploaded($searchContext, SqlQuery $sqlQuery, $val)
|
protected static function filterTokenUploaded($val)
|
||||||
{
|
{
|
||||||
return self::filterTokenSubmit($searchContext, $sqlQuery, $val);
|
return self::filterTokenSubmit($val);
|
||||||
}
|
|
||||||
|
|
||||||
protected static function filterTokenPrev($searchContext, SqlQuery $sqlQuery, $val)
|
|
||||||
{
|
|
||||||
self::__filterTokenPrevNext($searchContext, $sqlQuery, $val);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected static function filterTokenNext($searchContext, SqlQuery $sqlQuery, $val)
|
|
||||||
{
|
|
||||||
$searchContext->orderDir *= -1;
|
|
||||||
self::__filterTokenPrevNext($searchContext, $sqlQuery, $val);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected static function __filterTokenPrevNext($searchContext, SqlQuery $sqlQuery, $val)
|
|
||||||
{
|
|
||||||
$op1 = $searchContext->orderDir == 1 ? '<' : '>';
|
|
||||||
$op2 = $searchContext->orderDir != 1 ? '<' : '>';
|
|
||||||
$sqlQuery
|
|
||||||
->open()
|
|
||||||
->open()
|
|
||||||
->raw($searchContext->orderColumn . ' ' . $op1 . ' ')
|
|
||||||
->open()
|
|
||||||
->select($searchContext->orderColumn)
|
|
||||||
->from('post p2')
|
|
||||||
->where('p2.id = ?')->put(intval($val))
|
|
||||||
->close()
|
|
||||||
->and('id != ?')->put($val)
|
|
||||||
->close()
|
|
||||||
->or()
|
|
||||||
->open()
|
|
||||||
->raw($searchContext->orderColumn . ' = ')
|
|
||||||
->open()
|
|
||||||
->select($searchContext->orderColumn)
|
|
||||||
->from('post p2')
|
|
||||||
->where('p2.id = ?')->put(intval($val))
|
|
||||||
->close()
|
|
||||||
->and('id ' . $op1 . ' ?')->put(intval($val))
|
|
||||||
->close()
|
|
||||||
->close();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
protected static function parseOrderToken($searchContext, $val)
|
|
||||||
|
protected static function changeOrder($stmt, $val, $neg = true)
|
||||||
{
|
{
|
||||||
$randomReset = true;
|
$randomReset = true;
|
||||||
|
|
||||||
$orderDir = 1;
|
$orderDir = SqlSelectStatement::ORDER_DESC;
|
||||||
if (substr($val, -4) == 'desc')
|
if (substr($val, -4) == 'desc')
|
||||||
{
|
{
|
||||||
$orderDir = 1;
|
$orderDir = SqlSelectStatement::ORDER_DESC;
|
||||||
$val = rtrim(substr($val, 0, -4), ',');
|
$val = rtrim(substr($val, 0, -4), ',');
|
||||||
}
|
}
|
||||||
elseif (substr($val, -3) == 'asc')
|
elseif (substr($val, -3) == 'asc')
|
||||||
{
|
{
|
||||||
$orderDir = -1;
|
$orderDir = SqlSelectStatement::ORDER_ASC;
|
||||||
$val = rtrim(substr($val, 0, -3), ',');
|
$val = rtrim(substr($val, 0, -3), ',');
|
||||||
}
|
}
|
||||||
if ($val{0} == '-')
|
if ($neg)
|
||||||
{
|
{
|
||||||
$orderDir *= -1;
|
$orderDir = $orderDir == SqlSelectStatement::ORDER_DESC
|
||||||
$val = substr($val, 1);
|
? SqlSelectStatement::ORDER_ASC
|
||||||
|
: SqlSelectStatement::ORDER_DESC;
|
||||||
}
|
}
|
||||||
|
|
||||||
switch ($val)
|
switch ($val)
|
||||||
|
@ -329,11 +321,13 @@ class PostSearchService extends AbstractSearchService
|
||||||
case 'comment':
|
case 'comment':
|
||||||
case 'comments':
|
case 'comments':
|
||||||
case 'commentcount':
|
case 'commentcount':
|
||||||
|
case 'comment_count':
|
||||||
$orderColumn = 'comment_count';
|
$orderColumn = 'comment_count';
|
||||||
break;
|
break;
|
||||||
case 'fav':
|
case 'fav':
|
||||||
case 'favs':
|
case 'favs':
|
||||||
case 'favcount':
|
case 'favcount':
|
||||||
|
case 'fav_count':
|
||||||
$orderColumn = 'fav_count';
|
$orderColumn = 'fav_count';
|
||||||
break;
|
break;
|
||||||
case 'score':
|
case 'score':
|
||||||
|
@ -342,6 +336,7 @@ class PostSearchService extends AbstractSearchService
|
||||||
case 'tag':
|
case 'tag':
|
||||||
case 'tags':
|
case 'tags':
|
||||||
case 'tagcount':
|
case 'tagcount':
|
||||||
|
case 'tag_count':
|
||||||
$orderColumn = 'tag_count';
|
$orderColumn = 'tag_count';
|
||||||
break;
|
break;
|
||||||
case 'random':
|
case 'random':
|
||||||
|
@ -363,61 +358,26 @@ class PostSearchService extends AbstractSearchService
|
||||||
if ($randomReset and isset($_SESSION['browsing-seed']))
|
if ($randomReset and isset($_SESSION['browsing-seed']))
|
||||||
unset($_SESSION['browsing-seed']);
|
unset($_SESSION['browsing-seed']);
|
||||||
|
|
||||||
$searchContext->orderColumn = $orderColumn;
|
$stmt->setOrderBy($orderColumn, $orderDir);
|
||||||
$searchContext->orderDir = $orderDir;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
protected static function iterateTokens($tokens, $callback)
|
public static function decorate(SqlSelectStatement $stmt, $searchQuery)
|
||||||
{
|
|
||||||
$unparsedTokens = [];
|
|
||||||
|
|
||||||
foreach ($tokens as $origToken)
|
|
||||||
{
|
|
||||||
$token = $origToken;
|
|
||||||
$neg = false;
|
|
||||||
if ($token{0} == '-')
|
|
||||||
{
|
|
||||||
$token = substr($token, 1);
|
|
||||||
$neg = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
$pos = strpos($token, ':');
|
|
||||||
if ($pos === false)
|
|
||||||
{
|
|
||||||
$key = null;
|
|
||||||
$val = $token;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
$key = substr($token, 0, $pos);
|
|
||||||
$val = substr($token, $pos + 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
$parsed = $callback($neg, $key, $val);
|
|
||||||
|
|
||||||
if (!$parsed)
|
|
||||||
$unparsedTokens []= $origToken;
|
|
||||||
}
|
|
||||||
return $unparsedTokens;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static function decorate(SqlQuery $sqlQuery, $searchQuery)
|
|
||||||
{
|
{
|
||||||
$config = \Chibi\Registry::getConfig();
|
$config = \Chibi\Registry::getConfig();
|
||||||
|
|
||||||
$sqlQuery->from('post');
|
$stmt->setTable('post');
|
||||||
|
$stmt->setCriterion(new SqlConjunction());
|
||||||
|
|
||||||
self::filterChain($sqlQuery);
|
self::filterUserSafety($stmt);
|
||||||
self::filterUserSafety($sqlQuery);
|
|
||||||
|
|
||||||
/* query tokens */
|
/* query tokens */
|
||||||
$tokens = array_filter(array_unique(explode(' ', strtolower($searchQuery))));
|
$tokens = array_filter(array_unique(preg_split('/\s+/', strtolower($searchQuery))));
|
||||||
if (self::$enableTokenLimit and count($tokens) > $config->browsing->maxSearchTokens)
|
if (self::$enableTokenLimit and count($tokens) > $config->browsing->maxSearchTokens)
|
||||||
throw new SimpleException('Too many search tokens (maximum: ' . $config->browsing->maxSearchTokens . ')');
|
throw new SimpleException('Too many search tokens (maximum: ' . $config->browsing->maxSearchTokens . ')');
|
||||||
|
|
||||||
if (\Chibi\Registry::getContext()->user->hasEnabledHidingDislikedPosts())
|
if (\Chibi\Registry::getContext()->user->hasEnabledHidingDislikedPosts() and !in_array('special:disliked', $tokens))
|
||||||
$tokens []= '-special:disliked';
|
$tokens []= '-special:disliked';
|
||||||
if (!PrivilegesHelper::confirm(Privilege::ListPosts, 'hidden') or !in_array('special:hidden', $tokens))
|
if (!PrivilegesHelper::confirm(Privilege::ListPosts, 'hidden') or !in_array('special:hidden', $tokens))
|
||||||
$tokens []= '-special:hidden';
|
$tokens []= '-special:hidden';
|
||||||
|
@ -426,62 +386,43 @@ class PostSearchService extends AbstractSearchService
|
||||||
$searchContext->orderColumn = 'id';
|
$searchContext->orderColumn = 'id';
|
||||||
$searchContext->orderDir = 1;
|
$searchContext->orderDir = 1;
|
||||||
|
|
||||||
$tokens = self::iterateTokens($tokens, function($neg, $key, $val) use ($searchContext, $sqlQuery, &$orderToken)
|
foreach ($tokens as $token)
|
||||||
{
|
{
|
||||||
if ($key != 'order')
|
$neg = false;
|
||||||
return false;
|
if ($token{0} == '-')
|
||||||
|
{
|
||||||
|
$neg = true;
|
||||||
|
$token = substr($token, 1);
|
||||||
|
}
|
||||||
|
|
||||||
if ($neg)
|
if (strpos($token, ':') !== false)
|
||||||
$orderToken = '-' . $val;
|
{
|
||||||
|
list ($key, $val) = explode(':', $token);
|
||||||
|
$key = strtolower($key);
|
||||||
|
if ($key == 'order')
|
||||||
|
{
|
||||||
|
self::changeOrder($stmt, $val, $neg);
|
||||||
|
}
|
||||||
else
|
else
|
||||||
$orderToken = $val;
|
|
||||||
self::parseOrderToken($searchContext, $orderToken);
|
|
||||||
|
|
||||||
return true;
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
$tokens = self::iterateTokens($tokens, function($neg, $key, $val) use ($searchContext, $sqlQuery)
|
|
||||||
{
|
|
||||||
if ($key !== null)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
self::filterChain($sqlQuery);
|
|
||||||
if ($neg)
|
|
||||||
self::filterNegate($sqlQuery);
|
|
||||||
self::filterTag($sqlQuery, $val);
|
|
||||||
return true;
|
|
||||||
});
|
|
||||||
|
|
||||||
$tokens = self::iterateTokens($tokens, function($neg, $key, $val) use ($searchContext, $sqlQuery)
|
|
||||||
{
|
{
|
||||||
$methodName = 'filterToken' . TextHelper::kebabCaseToCamelCase($key);
|
$methodName = 'filterToken' . TextHelper::kebabCaseToCamelCase($key);
|
||||||
if (!method_exists(__CLASS__, $methodName))
|
if (!method_exists(__CLASS__, $methodName))
|
||||||
return false;
|
throw new SimpleException('Unknown search token "' . $key . '"');
|
||||||
|
|
||||||
self::filterChain($sqlQuery);
|
$criterion = self::$methodName($val);
|
||||||
if ($neg)
|
$criterion = self::decorateNegation($criterion, $neg);
|
||||||
self::filterNegate($sqlQuery);
|
$stmt->getCriterion()->add($criterion);
|
||||||
self::$methodName($searchContext, $sqlQuery, $val);
|
|
||||||
return true;
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!empty($tokens))
|
|
||||||
throw new SimpleException('Unknown search token "' . array_shift($tokens) . '"');
|
|
||||||
|
|
||||||
$sqlQuery->orderBy($searchContext->orderColumn);
|
|
||||||
if ($searchContext->orderDir == 1)
|
|
||||||
$sqlQuery->desc();
|
|
||||||
else
|
|
||||||
$sqlQuery->asc();
|
|
||||||
|
|
||||||
if ($searchContext->orderColumn != 'id')
|
|
||||||
{
|
|
||||||
$sqlQuery->raw(', id');
|
|
||||||
if ($searchContext->orderDir == 1)
|
|
||||||
$sqlQuery->desc();
|
|
||||||
else
|
|
||||||
$sqlQuery->asc();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
self::filterTag($stmt, $token, $neg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$stmt->addOrderBy('id',
|
||||||
|
empty($stmt->getOrderBy())
|
||||||
|
? SqlSelectStatement::ORDER_DESC
|
||||||
|
: $stmt->getOrderBy()[0][1]);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,23 +1,15 @@
|
||||||
<?php
|
<?php
|
||||||
class TagSearchService extends AbstractSearchService
|
class TagSearchService extends AbstractSearchService
|
||||||
{
|
{
|
||||||
public static function decorate(SqlQuery $sqlQuery, $searchQuery)
|
public static function decorate(SqlSelectStatement $stmt, $searchQuery)
|
||||||
{
|
{
|
||||||
$allowedSafety = PrivilegesHelper::getAllowedSafety();
|
$allowedSafety = PrivilegesHelper::getAllowedSafety();
|
||||||
$sqlQuery
|
$stmt
|
||||||
->raw(', COUNT(post_tag.post_id)')
|
->addColumn('COUNT(post_tag.post_id) AS post_count')
|
||||||
->as('post_count')
|
->setTable('tag')
|
||||||
->from('tag')
|
->addInnerJoin('post_tag', new SqlEqualsOperator('tag.id', 'post_tag.tag_id'))
|
||||||
->innerJoin('post_tag')
|
->addInnerJoin('post', new SqlEqualsOperator('post.id', 'post_tag.post_id'));
|
||||||
->on('tag.id = post_tag.tag_id')
|
$stmt->setCriterion((new SqlConjunction)->add(SqlInOperator::fromArray('safety', SqlBinding::fromArray($allowedSafety))));
|
||||||
->innerJoin('post')
|
|
||||||
->on('post.id = post_tag.post_id');
|
|
||||||
if (empty($allowedSafety))
|
|
||||||
$sqlQuery->where('0');
|
|
||||||
else
|
|
||||||
$sqlQuery->where('safety')->in()->genSlots($allowedSafety);
|
|
||||||
foreach ($allowedSafety as $s)
|
|
||||||
$sqlQuery->put($s);
|
|
||||||
|
|
||||||
$orderToken = null;
|
$orderToken = null;
|
||||||
|
|
||||||
|
@ -40,21 +32,17 @@ class TagSearchService extends AbstractSearchService
|
||||||
if (strlen($token) >= 3)
|
if (strlen($token) >= 3)
|
||||||
$token = '%' . $token;
|
$token = '%' . $token;
|
||||||
$token .= '%';
|
$token .= '%';
|
||||||
$sqlQuery
|
$stmt->getCriterion()->add(new SqlNoCaseOperator(new SqlLikeOperator('tag.name', new SqlBinding($token))));
|
||||||
->and('tag.name')
|
|
||||||
->like('?')
|
|
||||||
->put($token)
|
|
||||||
->collate()->nocase();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
$sqlQuery->groupBy('tag.id');
|
$stmt->groupBy('tag.id');
|
||||||
if ($orderToken)
|
if ($orderToken)
|
||||||
self::order($sqlQuery,$orderToken);
|
self::order($stmt,$orderToken);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static function order(SqlQuery $sqlQuery, $value)
|
private static function order(SqlSelectStatement $stmt, $value)
|
||||||
{
|
{
|
||||||
if (strpos($value, ',') !== false)
|
if (strpos($value, ',') !== false)
|
||||||
{
|
{
|
||||||
|
@ -69,17 +57,18 @@ class TagSearchService extends AbstractSearchService
|
||||||
switch ($orderColumn)
|
switch ($orderColumn)
|
||||||
{
|
{
|
||||||
case 'popularity':
|
case 'popularity':
|
||||||
$sqlQuery->orderBy('post_count');
|
$stmt->setOrderBy('post_count',
|
||||||
|
$orderDir == 'asc'
|
||||||
|
? SqlSelectStatement::ORDER_ASC
|
||||||
|
: SqlSelectStatement::ORDER_DESC);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 'alpha':
|
case 'alpha':
|
||||||
$sqlQuery->orderBy('tag.name')->collate()->nocase();
|
$stmt->setOrderBy(new SqlNoCaseOperator('tag.name'),
|
||||||
|
$orderDir == 'asc'
|
||||||
|
? SqlSelectStatement::ORDER_ASC
|
||||||
|
: SqlSelectStatement::ORDER_DESC);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($orderDir == 'asc')
|
|
||||||
$sqlQuery->asc();
|
|
||||||
else
|
|
||||||
$sqlQuery->desc();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,28 +1,29 @@
|
||||||
<?php
|
<?php
|
||||||
class UserSearchService extends AbstractSearchService
|
class UserSearchService extends AbstractSearchService
|
||||||
{
|
{
|
||||||
protected static function decorate(SQLQuery $sqlQuery, $searchQuery)
|
protected static function decorate(SqlSelectStatement $stmt, $searchQuery)
|
||||||
{
|
{
|
||||||
$sqlQuery->from('user');
|
$stmt->setTable('user');
|
||||||
|
|
||||||
$sortStyle = $searchQuery;
|
$sortStyle = $searchQuery;
|
||||||
switch ($sortStyle)
|
switch ($sortStyle)
|
||||||
{
|
{
|
||||||
case 'alpha,asc':
|
case 'alpha,asc':
|
||||||
$sqlQuery->orderBy('name')->collate()->nocase()->asc();
|
$stmt->setOrderBy(new SqlNoCaseOperator('name'), SqlSelectStatement::ORDER_ASC);
|
||||||
break;
|
break;
|
||||||
case 'alpha,desc':
|
case 'alpha,desc':
|
||||||
$sqlQuery->orderBy('name')->collate()->nocase()->desc();
|
$stmt->setOrderBy(new SqlNoCaseOperator('name'), SqlSelectStatement::ORDER_DESC);
|
||||||
break;
|
break;
|
||||||
case 'date,asc':
|
case 'date,asc':
|
||||||
$sqlQuery->orderBy('join_date')->asc();
|
$stmt->setOrderBy('join_date', SqlSelectStatement::ORDER_ASC);
|
||||||
break;
|
break;
|
||||||
case 'date,desc':
|
case 'date,desc':
|
||||||
$sqlQuery->orderBy('join_date')->desc();
|
$stmt->setOrderBy('join_date', SqlSelectStatement::ORDER_DESC);
|
||||||
break;
|
break;
|
||||||
case 'pending':
|
case 'pending':
|
||||||
$sqlQuery->where('staff_confirmed IS NULL');
|
$stmt->setCriterion((new SqlDisjunction)
|
||||||
$sqlQuery->or('staff_confirmed = 0');
|
->add(new SqlIsNullOperator('staff_confirmed'))
|
||||||
|
->add(new SqlEqualsOperator('staff_confirmed', '0')));
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
throw new SimpleException('Unknown sort style "' . $sortStyle . '"');
|
throw new SimpleException('Unknown sort style "' . $sortStyle . '"');
|
||||||
|
|
|
@ -12,27 +12,29 @@ class TagModel extends AbstractCrudModel
|
||||||
{
|
{
|
||||||
self::forgeId($tag, 'tag');
|
self::forgeId($tag, 'tag');
|
||||||
|
|
||||||
$query = (new SqlQuery)
|
$stmt = new SqlUpdateStatement();
|
||||||
->update('tag')
|
$stmt->setTable('tag');
|
||||||
->set('name = ?')->put($tag->name)
|
$stmt->setColumn('name', new SqlBinding($tag->name));
|
||||||
->where('id = ?')->put($tag->id);
|
$stmt->setCriterion(new SqlEqualsOperator('id', new SqlBinding($tag->id)));
|
||||||
|
|
||||||
Database::query($query);
|
Database::exec($stmt);
|
||||||
});
|
});
|
||||||
return $tag->id;
|
return $tag->id;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static function remove($tag)
|
public static function remove($tag)
|
||||||
{
|
{
|
||||||
$query = (new SqlQuery)
|
$binding = new SqlBinding($tag->id);
|
||||||
->deleteFrom('post_tag')
|
|
||||||
->where('tag_id = ?')->put($tag->id);
|
|
||||||
Database::query($query);
|
|
||||||
|
|
||||||
$query = (new SqlQuery)
|
$stmt = new SqlDeleteStatement();
|
||||||
->deleteFrom('tag')
|
$stmt->setTable('post_tag');
|
||||||
->where('id = ?')->put($tag->id);
|
$stmt->setCriterion(new SqlEqualsOperator('tag_id', $binding));
|
||||||
Database::query($query);
|
Database::exec($stmt);
|
||||||
|
|
||||||
|
$stmt = new SqlDeleteStatement();
|
||||||
|
$stmt->setTable('tag');
|
||||||
|
$stmt->setCriterion(new SqlEqualsOperator('id', $binding));
|
||||||
|
Database::exec($stmt);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static function rename($sourceName, $targetName)
|
public static function rename($sourceName, $targetName)
|
||||||
|
@ -60,38 +62,40 @@ class TagModel extends AbstractCrudModel
|
||||||
if ($sourceTag->id == $targetTag->id)
|
if ($sourceTag->id == $targetTag->id)
|
||||||
throw new SimpleException('Source and target tag are the same');
|
throw new SimpleException('Source and target tag are the same');
|
||||||
|
|
||||||
$query = (new SqlQuery)
|
$stmt = new SqlSelectStatement();
|
||||||
->select('post.id')
|
$stmt->setColumn('post.id');
|
||||||
->from('post')
|
$stmt->setTable('post');
|
||||||
->where()
|
$stmt->setCriterion(
|
||||||
->exists()
|
(new SqlConjunction)
|
||||||
->open()
|
->add(
|
||||||
->select('1')
|
new SqlExistsOperator(
|
||||||
->from('post_tag')
|
(new SqlSelectStatement)
|
||||||
->where('post_tag.post_id = post.id')
|
->setTable('post_tag')
|
||||||
->and('post_tag.tag_id = ?')->put($sourceTag->id)
|
->setCriterion(
|
||||||
->close()
|
(new SqlConjunction)
|
||||||
->and()
|
->add(new SqlEqualsOperator('post_tag.post_id', 'post.id'))
|
||||||
->not()->exists()
|
->add(new SqlEqualsOperator('post_tag.tag_id', new SqlBinding($sourceTag->id))))))
|
||||||
->open()
|
->add(
|
||||||
->select('1')
|
new SqlNegationOperator(
|
||||||
->from('post_tag')
|
new SqlExistsOperator(
|
||||||
->where('post_tag.post_id = post.id')
|
(new SqlSelectStatement)
|
||||||
->and('post_tag.tag_id = ?')->put($targetTag->id)
|
->setTable('post_tag')
|
||||||
->close();
|
->setCriterion(
|
||||||
$rows = Database::fetchAll($query);
|
(new SqlConjunction)
|
||||||
|
->add(new SqlEqualsOperator('post_tag.post_id', 'post.id'))
|
||||||
|
->add(new SqlEqualsOperator('post_tag.tag_id', new SqlBinding($targetTag->id))))))));
|
||||||
|
$rows = Database::fetchAll($stmt);
|
||||||
$postIds = array_map(function($row) { return $row['id']; }, $rows);
|
$postIds = array_map(function($row) { return $row['id']; }, $rows);
|
||||||
|
|
||||||
self::remove($sourceTag);
|
self::remove($sourceTag);
|
||||||
|
|
||||||
foreach ($postIds as $postId)
|
foreach ($postIds as $postId)
|
||||||
{
|
{
|
||||||
$query = (new SqlQuery)
|
$stmt = new SqlInsertStatement();
|
||||||
->insertInto('post_tag')
|
$stmt->setTable('post_tag');
|
||||||
->surround('post_id, tag_id')
|
$stmt->setColumn('post_id', new SqlBinding($postId));
|
||||||
->values()->surround('?, ?')
|
$stmt->setColumn('tag_id', new SqlBinding($targetTag->id));
|
||||||
->put([$postId, $targetTag->id]);
|
Database::exec($stmt);
|
||||||
Database::query($query);
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -99,16 +103,13 @@ class TagModel extends AbstractCrudModel
|
||||||
|
|
||||||
public static function findAllByPostId($key)
|
public static function findAllByPostId($key)
|
||||||
{
|
{
|
||||||
$query = new SqlQuery();
|
$stmt = new SqlSelectStatement();
|
||||||
$query
|
$stmt->setColumn('tag.*');
|
||||||
->select('tag.*')
|
$stmt->setTable('tag');
|
||||||
->from('tag')
|
$stmt->addInnerJoin('post_tag', new SqlEqualsOperator('post_tag.tag_id', 'tag.id'));
|
||||||
->innerJoin('post_tag')
|
$stmt->setCriterion(new SqlEqualsOperator('post_tag.post_id', new SqlBinding($key)));
|
||||||
->on('post_tag.tag_id = tag.id')
|
|
||||||
->where('post_tag.post_id = ?')
|
|
||||||
->put($key);
|
|
||||||
|
|
||||||
$rows = Database::fetchAll($query);
|
$rows = Database::fetchAll($stmt);
|
||||||
if ($rows)
|
if ($rows)
|
||||||
return self::convertRows($rows);
|
return self::convertRows($rows);
|
||||||
return [];
|
return [];
|
||||||
|
@ -116,13 +117,12 @@ class TagModel extends AbstractCrudModel
|
||||||
|
|
||||||
public static function findByName($key, $throw = true)
|
public static function findByName($key, $throw = true)
|
||||||
{
|
{
|
||||||
$query = (new SqlQuery)
|
$stmt = new SqlSelectStatement();
|
||||||
->select('*')
|
$stmt->setColumn('tag.*');
|
||||||
->from('tag')
|
$stmt->setTable('tag');
|
||||||
->where('name = ?')->put($key)
|
$stmt->setCriterion(new SqlNoCaseOperator(new SqlEqualsOperator('name', new SqlBinding($key))));
|
||||||
->collate()->nocase();
|
|
||||||
|
|
||||||
$row = Database::fetchOne($query);
|
$row = Database::fetchOne($stmt);
|
||||||
if ($row)
|
if ($row)
|
||||||
return self::convertRow($row);
|
return self::convertRow($row);
|
||||||
|
|
||||||
|
@ -135,16 +135,15 @@ class TagModel extends AbstractCrudModel
|
||||||
|
|
||||||
public static function removeUnused()
|
public static function removeUnused()
|
||||||
{
|
{
|
||||||
$query = (new SqlQuery)
|
$stmt = (new SqlDeleteStatement)
|
||||||
->deleteFrom('tag')
|
->setTable('tag')
|
||||||
->where()
|
->setCriterion(
|
||||||
->not()->exists()
|
new SqlNegationOperator(
|
||||||
->open()
|
new SqlExistsOperator(
|
||||||
->select('1')
|
(new SqlSelectStatement)
|
||||||
->from('post_tag')
|
->setTable('post_tag')
|
||||||
->where('post_tag.tag_id = tag.id')
|
->setCriterion(new SqlEqualsOperator('post_tag.tag_id', 'tag.id')))));
|
||||||
->close();
|
Database::exec($stmt);
|
||||||
Database::query($query);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -20,12 +20,14 @@ implements IModel
|
||||||
'expires' => $token->expires,
|
'expires' => $token->expires,
|
||||||
];
|
];
|
||||||
|
|
||||||
$query = (new SqlQuery)
|
$stmt = new SqlUpdateStatement();
|
||||||
->update('user_token')
|
$stmt->setTable('user_token');
|
||||||
->set(join(', ', array_map(function($key) { return $key . ' = ?'; }, array_keys($bindings))))
|
$stmt->setCriterion(new SqlEqualsOperator('id', new SqlBinding($token->id)));
|
||||||
->put(array_values($bindings))
|
|
||||||
->where('id = ?')->put($token->id);
|
foreach ($bindings as $key => $val)
|
||||||
Database::query($query);
|
$stmt->setColumn($key, new SqlBinding($val));
|
||||||
|
|
||||||
|
Database::exec($stmt);
|
||||||
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -37,12 +39,12 @@ implements IModel
|
||||||
if (empty($key))
|
if (empty($key))
|
||||||
throw new SimpleNotFoundException('Invalid security token');
|
throw new SimpleNotFoundException('Invalid security token');
|
||||||
|
|
||||||
$query = (new SqlQuery)
|
$stmt = new SqlSelectStatement();
|
||||||
->select('*')
|
$stmt->setTable('user_token');
|
||||||
->from('user_token')
|
$stmt->setColumn('*');
|
||||||
->where('token = ?')->put($key);
|
$stmt->setCriterion(new SqlEqualsOperator('token', new SqlBinding($key)));
|
||||||
|
|
||||||
$row = Database::fetchOne($query);
|
$row = Database::fetchOne($stmt);
|
||||||
if ($row)
|
if ($row)
|
||||||
return self::convertRow($row);
|
return self::convertRow($row);
|
||||||
|
|
||||||
|
|
|
@ -40,12 +40,14 @@ class UserModel extends AbstractCrudModel
|
||||||
'banned' => $user->banned
|
'banned' => $user->banned
|
||||||
];
|
];
|
||||||
|
|
||||||
$query = (new SqlQuery)
|
$stmt = (new SqlUpdateStatement)
|
||||||
->update('user')
|
->setTable('user')
|
||||||
->set(join(', ', array_map(function($key) { return $key . ' = ?'; }, array_keys($bindings))))
|
->setCriterion(new SqlEqualsOperator('id', new SqlBinding($user->id)));
|
||||||
->put(array_values($bindings))
|
|
||||||
->where('id = ?')->put($user->id);
|
foreach ($bindings as $key => $val)
|
||||||
Database::query($query);
|
$stmt->setColumn($key, new SqlBinding($val));
|
||||||
|
|
||||||
|
Database::exec($stmt);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -53,32 +55,31 @@ class UserModel extends AbstractCrudModel
|
||||||
{
|
{
|
||||||
Database::transaction(function() use ($user)
|
Database::transaction(function() use ($user)
|
||||||
{
|
{
|
||||||
$queries = [];
|
$binding = new SqlBinding($user->id);
|
||||||
|
|
||||||
$queries []= (new SqlQuery)
|
$stmt = new SqlDeleteStatement();
|
||||||
->deleteFrom('post_score')
|
$stmt->setTable('post_score');
|
||||||
->where('user_id = ?')->put($user->id);
|
$stmt->setCriterion(new SqlEqualsOperator('user_id', $binding));
|
||||||
|
Database::exec($stmt);
|
||||||
|
|
||||||
$queries []= (new SqlQuery)
|
$stmt->setTable('favoritee');
|
||||||
->update('comment')
|
Database::exec($stmt);
|
||||||
->set('commenter_id = NULL')
|
|
||||||
->where('commenter_id = ?')->put($user->id);
|
|
||||||
|
|
||||||
$queries []= (new SqlQuery)
|
$stmt->setTable('user');
|
||||||
->update('post')
|
$stmt->setCriterion(new SqlEqualsOperator('id', $binding));
|
||||||
->set('uploader_id = NULL')
|
Database::exec($stmt);
|
||||||
->where('uploader_id = ?')->put($user->id);
|
|
||||||
|
|
||||||
$queries []= (new SqlQuery)
|
$stmt = new SqlUpdateStatement();
|
||||||
->deleteFrom('favoritee')
|
$stmt->setTable('comment');
|
||||||
->where('user_id = ?')->put($user->id);
|
$stmt->setCriterion(new SqlEqualsOperator('commenter_id', $binding));
|
||||||
|
$stmt->setColumn('commenter_id', new SqlNullOperator());
|
||||||
|
Database::exec($stmt);
|
||||||
|
|
||||||
$queries []= (new SqlQuery)
|
$stmt = new SqlUpdateStatement();
|
||||||
->deleteFrom('user')
|
$stmt->setTable('post');
|
||||||
->where('id = ?')->put($user->id);
|
$stmt->setCriterion(new SqlEqualsOperator('uploader_id', $binding));
|
||||||
|
$stmt->setColumn('uploader_id', new SqlNullOperator());
|
||||||
foreach ($queries as $query)
|
Database::exec($stmt);
|
||||||
Database::query($query);
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -86,13 +87,12 @@ class UserModel extends AbstractCrudModel
|
||||||
|
|
||||||
public static function findByName($key, $throw = true)
|
public static function findByName($key, $throw = true)
|
||||||
{
|
{
|
||||||
$query = (new SqlQuery)
|
$stmt = new SqlSelectStatement();
|
||||||
->select('*')
|
$stmt->setColumn('*');
|
||||||
->from('user')
|
$stmt->setTable('user');
|
||||||
->where('name = ?')->put(trim($key))
|
$stmt->setCriterion(new SqlNoCaseOperator(new SqlEqualsOperator('name', new SqlBinding(trim($key)))));
|
||||||
->collate()->nocase();
|
|
||||||
|
|
||||||
$row = Database::fetchOne($query);
|
$row = Database::fetchOne($stmt);
|
||||||
if ($row)
|
if ($row)
|
||||||
return self::convertRow($row);
|
return self::convertRow($row);
|
||||||
|
|
||||||
|
@ -103,15 +103,14 @@ class UserModel extends AbstractCrudModel
|
||||||
|
|
||||||
public static function findByNameOrEmail($key, $throw = true)
|
public static function findByNameOrEmail($key, $throw = true)
|
||||||
{
|
{
|
||||||
$query = new SqlQuery();
|
$stmt = new SqlSelectStatement();
|
||||||
$query->select('*')
|
$stmt->setColumn('*');
|
||||||
->from('user')
|
$stmt->setTable('user');
|
||||||
->where('name = ?')->put(trim($key))
|
$stmt->setCriterion((new SqlDisjunction)
|
||||||
->collate()->nocase()
|
->add(new SqlNoCaseOperator(new SqlEqualsOperator('name', new SqlBinding(trim($key)))))
|
||||||
->or('email_confirmed = ?')->put(trim($key))
|
->add(new SqlNoCaseOperator(new SqlEqualsOperator('email_confirmed', new SqlBinding(trim($key))))));
|
||||||
->collate()->nocase();
|
|
||||||
|
|
||||||
$row = Database::fetchOne($query);
|
$row = Database::fetchOne($stmt);
|
||||||
if ($row)
|
if ($row)
|
||||||
return self::convertRow($row);
|
return self::convertRow($row);
|
||||||
|
|
||||||
|
@ -126,20 +125,21 @@ class UserModel extends AbstractCrudModel
|
||||||
{
|
{
|
||||||
Database::transaction(function() use ($user, $post, $score)
|
Database::transaction(function() use ($user, $post, $score)
|
||||||
{
|
{
|
||||||
$query = (new SqlQuery)
|
$stmt = new SqlDeleteStatement();
|
||||||
->deleteFrom('post_score')
|
$stmt->setTable('post_score');
|
||||||
->where('post_id = ?')->put($post->id)
|
$stmt->setCriterion((new SqlConjunction)
|
||||||
->and('user_id = ?')->put($user->id);
|
->add(new SqlEqualsOperator('post_id', new SqlBinding($post->id)))
|
||||||
Database::query($query);
|
->add(new SqlEqualsOperator('user_id', new SqlBinding($user->id))));
|
||||||
|
Database::exec($stmt);
|
||||||
$score = intval($score);
|
$score = intval($score);
|
||||||
if ($score != 0)
|
if ($score != 0)
|
||||||
{
|
{
|
||||||
$query = (new SqlQuery);
|
$stmt = new SqlInsertStatement();
|
||||||
$query->insertInto('post_score')
|
$stmt->setTable('post_score');
|
||||||
->surround('post_id, user_id, score')
|
$stmt->setColumn('post_id', new SqlBinding($post->id));
|
||||||
->values()->surround('?, ?, ?')
|
$stmt->setColumn('user_id', new SqlBinding($user->id));
|
||||||
->put([$post->id, $user->id, $score]);
|
$stmt->setColumn('score', new SqlBinding($score));
|
||||||
Database::query($query);
|
Database::exec($stmt);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -149,12 +149,11 @@ class UserModel extends AbstractCrudModel
|
||||||
Database::transaction(function() use ($user, $post)
|
Database::transaction(function() use ($user, $post)
|
||||||
{
|
{
|
||||||
self::removeFromUserFavorites($user, $post);
|
self::removeFromUserFavorites($user, $post);
|
||||||
$query = (new SqlQuery);
|
$stmt = new SqlInsertStatement();
|
||||||
$query->insertInto('favoritee')
|
$stmt->setTable('favoritee');
|
||||||
->surround('post_id, user_id')
|
$stmt->setColumn('post_id', new SqlBinding($post->id));
|
||||||
->values()->surround('?, ?')
|
$stmt->setColumn('user_id', new SqlBinding($user->id));
|
||||||
->put([$post->id, $user->id]);
|
Database::exec($stmt);
|
||||||
Database::query($query);
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -162,11 +161,12 @@ class UserModel extends AbstractCrudModel
|
||||||
{
|
{
|
||||||
Database::transaction(function() use ($user, $post)
|
Database::transaction(function() use ($user, $post)
|
||||||
{
|
{
|
||||||
$query = (new SqlQuery)
|
$stmt = new SqlDeleteStatement();
|
||||||
->deleteFrom('favoritee')
|
$stmt->setTable('favoritee');
|
||||||
->where('post_id = ?')->put($post->id)
|
$stmt->setCriterion((new SqlConjunction)
|
||||||
->and('user_id = ?')->put($user->id);
|
->add(new SqlEqualsOperator('post_id', new SqlBinding($post->id)))
|
||||||
Database::query($query);
|
->add(new SqlEqualsOperator('user_id', new SqlBinding($user->id))));
|
||||||
|
Database::exec($stmt);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,8 @@
|
||||||
|
<?php
|
||||||
|
class SqlAdditionOperator extends SqlBinaryOperator
|
||||||
|
{
|
||||||
|
public function __construct($subject, $target)
|
||||||
|
{
|
||||||
|
parent::__construct($subject, $target, '+');
|
||||||
|
}
|
||||||
|
}
|
13
src/Sql/Operators/BinaryOperators/SqlAliasOperator.php
Normal file
13
src/Sql/Operators/BinaryOperators/SqlAliasOperator.php
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
<?php
|
||||||
|
class SqlAliasOperator extends SqlBinaryOperator
|
||||||
|
{
|
||||||
|
public function __construct($subject, $target)
|
||||||
|
{
|
||||||
|
parent::__construct($subject, $target, 'AS');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getAsString()
|
||||||
|
{
|
||||||
|
return '(' . $this->subject->getAsString() . ') ' . $this->operator . ' ' . $this->target->getAsString();
|
||||||
|
}
|
||||||
|
}
|
8
src/Sql/Operators/BinaryOperators/SqlEqualsOperator.php
Normal file
8
src/Sql/Operators/BinaryOperators/SqlEqualsOperator.php
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
<?php
|
||||||
|
class SqlEqualsOperator extends SqlBinaryOperator
|
||||||
|
{
|
||||||
|
public function __construct($subject, $target)
|
||||||
|
{
|
||||||
|
parent::__construct($subject, $target, '=');
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,8 @@
|
||||||
|
<?php
|
||||||
|
class SqlEqualsOrGreaterOperator extends SqlBinaryOperator
|
||||||
|
{
|
||||||
|
public function __construct($subject, $target)
|
||||||
|
{
|
||||||
|
parent::__construct($subject, $target, '>=');
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,8 @@
|
||||||
|
<?php
|
||||||
|
class SqlEqualsOrLesserOperator extends SqlBinaryOperator
|
||||||
|
{
|
||||||
|
public function __construct($subject, $target)
|
||||||
|
{
|
||||||
|
parent::__construct($subject, $target, '<=');
|
||||||
|
}
|
||||||
|
}
|
8
src/Sql/Operators/BinaryOperators/SqlGreaterOperator.php
Normal file
8
src/Sql/Operators/BinaryOperators/SqlGreaterOperator.php
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
<?php
|
||||||
|
class SqlGreaterOperator extends SqlBinaryOperator
|
||||||
|
{
|
||||||
|
public function __construct($subject, $target)
|
||||||
|
{
|
||||||
|
parent::__construct($subject, $target, '>');
|
||||||
|
}
|
||||||
|
}
|
8
src/Sql/Operators/BinaryOperators/SqlLesserOperator.php
Normal file
8
src/Sql/Operators/BinaryOperators/SqlLesserOperator.php
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
<?php
|
||||||
|
class SqlLesserOperator extends SqlBinaryOperator
|
||||||
|
{
|
||||||
|
public function __construct($subject, $target)
|
||||||
|
{
|
||||||
|
parent::__construct($subject, $target, '<');
|
||||||
|
}
|
||||||
|
}
|
8
src/Sql/Operators/BinaryOperators/SqlLikeOperator.php
Normal file
8
src/Sql/Operators/BinaryOperators/SqlLikeOperator.php
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
<?php
|
||||||
|
class SqlLikeOperator extends SqlBinaryOperator
|
||||||
|
{
|
||||||
|
public function __construct($subject, $target)
|
||||||
|
{
|
||||||
|
parent::__construct($subject, $target, 'LIKE');
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,8 @@
|
||||||
|
<?php
|
||||||
|
class SqlSubtractionOperator extends SqlBinaryOperator
|
||||||
|
{
|
||||||
|
public function __construct($subject, $target)
|
||||||
|
{
|
||||||
|
parent::__construct($subject, $target, '-');
|
||||||
|
}
|
||||||
|
}
|
8
src/Sql/Operators/NullaryOperators/SqlNullOperator.php
Normal file
8
src/Sql/Operators/NullaryOperators/SqlNullOperator.php
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
<?php
|
||||||
|
class SqlNullOperator extends SqlNullaryOperator
|
||||||
|
{
|
||||||
|
public function getAsString()
|
||||||
|
{
|
||||||
|
return 'NULL';
|
||||||
|
}
|
||||||
|
}
|
11
src/Sql/Operators/NullaryOperators/SqlRandomOperator.php
Normal file
11
src/Sql/Operators/NullaryOperators/SqlRandomOperator.php
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
<?php
|
||||||
|
class SqlRandomOperator extends SqlNullaryOperator
|
||||||
|
{
|
||||||
|
public function getAsString()
|
||||||
|
{
|
||||||
|
$config = \Chibi\Registry::getConfig();
|
||||||
|
return $config->main->dbDriver == 'sqlite'
|
||||||
|
? 'RANDOM()'
|
||||||
|
: 'RAND()';
|
||||||
|
}
|
||||||
|
}
|
19
src/Sql/Operators/SqlBinaryOperator.php
Normal file
19
src/Sql/Operators/SqlBinaryOperator.php
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
<?php
|
||||||
|
class SqlBinaryOperator extends SqlOperator
|
||||||
|
{
|
||||||
|
protected $subject;
|
||||||
|
protected $target;
|
||||||
|
protected $operator;
|
||||||
|
|
||||||
|
public function __construct($subject, $target, $operator)
|
||||||
|
{
|
||||||
|
$this->subject = $this->attachExpression($subject);
|
||||||
|
$this->target = $this->attachExpression($target);
|
||||||
|
$this->operator = $operator;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getAsString()
|
||||||
|
{
|
||||||
|
return '(' . $this->subject->getAsString() . ') ' . $this->operator . ' (' . $this->target->getAsString() . ')';
|
||||||
|
}
|
||||||
|
}
|
4
src/Sql/Operators/SqlNullaryOperator.php
Normal file
4
src/Sql/Operators/SqlNullaryOperator.php
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
<?php
|
||||||
|
abstract class SqlNullaryOperator extends SqlOperator
|
||||||
|
{
|
||||||
|
}
|
4
src/Sql/Operators/SqlOperator.php
Normal file
4
src/Sql/Operators/SqlOperator.php
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
<?php
|
||||||
|
abstract class SqlOperator extends SqlExpression
|
||||||
|
{
|
||||||
|
}
|
25
src/Sql/Operators/SqlUnaryOperator.php
Normal file
25
src/Sql/Operators/SqlUnaryOperator.php
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
<?php
|
||||||
|
abstract class SqlUnaryOperator extends SqlOperator
|
||||||
|
{
|
||||||
|
protected $subject;
|
||||||
|
|
||||||
|
public function __construct($subject)
|
||||||
|
{
|
||||||
|
$this->subject = $this->attachExpression($subject);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getAsString()
|
||||||
|
{
|
||||||
|
if (empty($this->subject->getAsString()))
|
||||||
|
return $this->getAsStringEmpty();
|
||||||
|
|
||||||
|
return $this->getAsStringNonEmpty();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getAsStringEmpty()
|
||||||
|
{
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
public abstract function getAsStringNonEmpty();
|
||||||
|
}
|
38
src/Sql/Operators/SqlVariableOperator.php
Normal file
38
src/Sql/Operators/SqlVariableOperator.php
Normal file
|
@ -0,0 +1,38 @@
|
||||||
|
<?php
|
||||||
|
abstract class SqlVariableOperator extends SqlOperator
|
||||||
|
{
|
||||||
|
protected $subjects;
|
||||||
|
|
||||||
|
public function __construct()
|
||||||
|
{
|
||||||
|
$this->subjects = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function add($subject)
|
||||||
|
{
|
||||||
|
$this->subjects []= $this->attachExpression($subject);
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public abstract function getAsStringNonEmpty();
|
||||||
|
public abstract function getAsStringEmpty();
|
||||||
|
|
||||||
|
public function getAsString()
|
||||||
|
{
|
||||||
|
if (empty(array_filter($this->subjects, function($x) { return !empty($x->getAsString()); })))
|
||||||
|
return $this->getAsStringEmpty();
|
||||||
|
|
||||||
|
return $this->getAsStringNonEmpty();
|
||||||
|
}
|
||||||
|
|
||||||
|
//variable arguments
|
||||||
|
public static function fromArray()
|
||||||
|
{
|
||||||
|
$args = func_get_args();
|
||||||
|
$subjects = array_pop($args);
|
||||||
|
$instance = (new ReflectionClass(get_called_class()))->newInstanceArgs($args);
|
||||||
|
foreach ($subjects as $subject)
|
||||||
|
$instance->add($subject);
|
||||||
|
return $instance;
|
||||||
|
}
|
||||||
|
}
|
8
src/Sql/Operators/UnaryOperators/SqlAbsOperator.php
Normal file
8
src/Sql/Operators/UnaryOperators/SqlAbsOperator.php
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
<?php
|
||||||
|
class SqlAbsOperator extends SqlUnaryOperator
|
||||||
|
{
|
||||||
|
public function getAsStringNonEmpty()
|
||||||
|
{
|
||||||
|
return 'ABS (' . $this->subject->getAsString() . ')';
|
||||||
|
}
|
||||||
|
}
|
8
src/Sql/Operators/UnaryOperators/SqlCountOperator.php
Normal file
8
src/Sql/Operators/UnaryOperators/SqlCountOperator.php
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
<?php
|
||||||
|
class SqlCountOperator extends SqlUnaryOperator
|
||||||
|
{
|
||||||
|
public function getAsStringNonEmpty()
|
||||||
|
{
|
||||||
|
return 'COUNT (' . $this->subject->getAsString() . ')';
|
||||||
|
}
|
||||||
|
}
|
8
src/Sql/Operators/UnaryOperators/SqlExistsOperator.php
Normal file
8
src/Sql/Operators/UnaryOperators/SqlExistsOperator.php
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
<?php
|
||||||
|
class SqlExistsOperator extends SqlUnaryOperator
|
||||||
|
{
|
||||||
|
public function getAsStringNonEmpty()
|
||||||
|
{
|
||||||
|
return 'EXISTS (' . $this->subject->getAsString() . ')';
|
||||||
|
}
|
||||||
|
}
|
8
src/Sql/Operators/UnaryOperators/SqlIsNullOperator.php
Normal file
8
src/Sql/Operators/UnaryOperators/SqlIsNullOperator.php
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
<?php
|
||||||
|
class SqlIsNullOperator extends SqlUnaryOperator
|
||||||
|
{
|
||||||
|
public function getAsStringNonEmpty()
|
||||||
|
{
|
||||||
|
return '(' . $this->subject->getAsString() . ') IS NULL';
|
||||||
|
}
|
||||||
|
}
|
8
src/Sql/Operators/UnaryOperators/SqlNegationOperator.php
Normal file
8
src/Sql/Operators/UnaryOperators/SqlNegationOperator.php
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
<?php
|
||||||
|
class SqlNegationOperator extends SqlUnaryOperator
|
||||||
|
{
|
||||||
|
public function getAsStringNonEmpty()
|
||||||
|
{
|
||||||
|
return 'NOT (' . $this->subject->getAsString() . ')';
|
||||||
|
}
|
||||||
|
}
|
8
src/Sql/Operators/UnaryOperators/SqlNoCaseOperator.php
Normal file
8
src/Sql/Operators/UnaryOperators/SqlNoCaseOperator.php
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
<?php
|
||||||
|
class SqlNoCaseOperator extends SqlUnaryOperator
|
||||||
|
{
|
||||||
|
public function getAsStringNonEmpty()
|
||||||
|
{
|
||||||
|
return $this->subject->getAsString() . ' COLLATE NOCASE';
|
||||||
|
}
|
||||||
|
}
|
16
src/Sql/Operators/VariableOperators/SqlConjunction.php
Normal file
16
src/Sql/Operators/VariableOperators/SqlConjunction.php
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
<?php
|
||||||
|
class SqlConjunction extends SqlVariableOperator
|
||||||
|
{
|
||||||
|
public function getAsStringEmpty()
|
||||||
|
{
|
||||||
|
return '1';
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getAsStringNonEmpty()
|
||||||
|
{
|
||||||
|
return '(' . join(' AND ', array_map(function($subject)
|
||||||
|
{
|
||||||
|
return $subject->getAsString();
|
||||||
|
}, $this->subjects)) . ')';
|
||||||
|
}
|
||||||
|
}
|
16
src/Sql/Operators/VariableOperators/SqlDisjunction.php
Normal file
16
src/Sql/Operators/VariableOperators/SqlDisjunction.php
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
<?php
|
||||||
|
class SqlDisjunction extends SqlVariableOperator
|
||||||
|
{
|
||||||
|
public function getAsStringEmpty()
|
||||||
|
{
|
||||||
|
return '1';
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getAsStringNonEmpty()
|
||||||
|
{
|
||||||
|
return '(' . join(' OR ', array_map(function($subject)
|
||||||
|
{
|
||||||
|
return $subject->getAsString();
|
||||||
|
}, $this->subjects)) . ')';
|
||||||
|
}
|
||||||
|
}
|
23
src/Sql/Operators/VariableOperators/SqlInOperator.php
Normal file
23
src/Sql/Operators/VariableOperators/SqlInOperator.php
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
<?php
|
||||||
|
class SqlInOperator extends SqlVariableOperator
|
||||||
|
{
|
||||||
|
protected $subject;
|
||||||
|
|
||||||
|
public function __construct($subject)
|
||||||
|
{
|
||||||
|
$this->subject = $this->attachExpression($subject);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getAsStringEmpty()
|
||||||
|
{
|
||||||
|
return '0';
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getAsStringNonEmpty()
|
||||||
|
{
|
||||||
|
return '(' . $this->subject->getAsString() . ') IN (' . join(', ', array_map(function($subject)
|
||||||
|
{
|
||||||
|
return $subject->getAsString();
|
||||||
|
}, $this->subjects)) . ')';
|
||||||
|
}
|
||||||
|
}
|
31
src/Sql/SqlBinding.php
Normal file
31
src/Sql/SqlBinding.php
Normal file
|
@ -0,0 +1,31 @@
|
||||||
|
<?php
|
||||||
|
class SqlBinding
|
||||||
|
{
|
||||||
|
protected $content;
|
||||||
|
protected $name;
|
||||||
|
private static $bindingCount = 0;
|
||||||
|
|
||||||
|
public function __construct($content)
|
||||||
|
{
|
||||||
|
$this->content = $content;
|
||||||
|
$this->name = ':p' . (self::$bindingCount ++);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getName()
|
||||||
|
{
|
||||||
|
return $this->name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getValue()
|
||||||
|
{
|
||||||
|
return $this->content;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function fromArray(array $contents)
|
||||||
|
{
|
||||||
|
return array_map(function($content)
|
||||||
|
{
|
||||||
|
return new SqlBinding($content);
|
||||||
|
}, $contents);
|
||||||
|
}
|
||||||
|
}
|
45
src/Sql/SqlExpression.php
Normal file
45
src/Sql/SqlExpression.php
Normal file
|
@ -0,0 +1,45 @@
|
||||||
|
<?php
|
||||||
|
abstract class SqlExpression
|
||||||
|
{
|
||||||
|
abstract public function getAsString();
|
||||||
|
|
||||||
|
protected $bindings = [];
|
||||||
|
protected $subExpressions = [];
|
||||||
|
|
||||||
|
private function bind($key, $val)
|
||||||
|
{
|
||||||
|
$this->bindings[$key] = $val;
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getBindings()
|
||||||
|
{
|
||||||
|
$stack = array_merge([], $this->subExpressions);
|
||||||
|
$bindings = $this->bindings;
|
||||||
|
while (!empty($stack))
|
||||||
|
{
|
||||||
|
$item = array_pop($stack);
|
||||||
|
$stack = array_merge($stack, $item->subExpressions);
|
||||||
|
$bindings = array_merge($bindings, $item->bindings);
|
||||||
|
}
|
||||||
|
return $bindings;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function attachExpression($object)
|
||||||
|
{
|
||||||
|
if ($object instanceof SqlBinding)
|
||||||
|
{
|
||||||
|
$this->bind($object->getName(), $object->getValue());
|
||||||
|
return new SqlStringExpression($object->getName());
|
||||||
|
}
|
||||||
|
else if ($object instanceof SqlExpression)
|
||||||
|
{
|
||||||
|
$this->subExpressions []= $object;
|
||||||
|
return $object;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return new SqlStringExpression((string) $object);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
15
src/Sql/SqlStringExpression.php
Normal file
15
src/Sql/SqlStringExpression.php
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
<?php
|
||||||
|
class SqlStringExpression extends SqlExpression
|
||||||
|
{
|
||||||
|
protected $text;
|
||||||
|
|
||||||
|
public function __construct($text)
|
||||||
|
{
|
||||||
|
$this->text = $text;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getAsString()
|
||||||
|
{
|
||||||
|
return $this->text;
|
||||||
|
}
|
||||||
|
}
|
39
src/Sql/Statements/SqlDeleteStatement.php
Normal file
39
src/Sql/Statements/SqlDeleteStatement.php
Normal file
|
@ -0,0 +1,39 @@
|
||||||
|
<?php
|
||||||
|
class SqlDeleteStatement extends SqlStatement
|
||||||
|
{
|
||||||
|
protected $table;
|
||||||
|
protected $criterion;
|
||||||
|
|
||||||
|
public function getTable()
|
||||||
|
{
|
||||||
|
return $this->table;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setTable($table)
|
||||||
|
{
|
||||||
|
$this->table = new SqlStringExpression($table);
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getCriterion()
|
||||||
|
{
|
||||||
|
return $this->criterion;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setCriterion($criterion)
|
||||||
|
{
|
||||||
|
$this->criterion = $this->attachExpression($criterion);
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getAsString()
|
||||||
|
{
|
||||||
|
$sql = 'DELETE FROM ' . $this->table->getAsString() . ' ';
|
||||||
|
|
||||||
|
if (!empty($this->criterion) and !empty($this->criterion->getAsString()))
|
||||||
|
$sql .= ' WHERE ' . $this->criterion->getAsString();
|
||||||
|
|
||||||
|
return $sql;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
62
src/Sql/Statements/SqlInsertStatement.php
Normal file
62
src/Sql/Statements/SqlInsertStatement.php
Normal file
|
@ -0,0 +1,62 @@
|
||||||
|
<?php
|
||||||
|
class SqlInsertStatement extends SqlStatement
|
||||||
|
{
|
||||||
|
protected $table;
|
||||||
|
protected $columns;
|
||||||
|
protected $source;
|
||||||
|
|
||||||
|
public function getTable()
|
||||||
|
{
|
||||||
|
return $this->table;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setTable($table)
|
||||||
|
{
|
||||||
|
$this->table = new SqlStringExpression($table);
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setColumn($column, $what)
|
||||||
|
{
|
||||||
|
$this->columns[$column] = $this->attachExpression($what);
|
||||||
|
$this->source = null;
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setSource($columns, $what)
|
||||||
|
{
|
||||||
|
$this->source = $this->attachExpression($what);
|
||||||
|
$this->columns = $columns;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getAsString()
|
||||||
|
{
|
||||||
|
$sql = 'INSERT INTO ' . $this->table->getAsString() . ' ';
|
||||||
|
if (!empty($this->source))
|
||||||
|
{
|
||||||
|
$sql .= ' (' . join(', ', $this->columns) . ')';
|
||||||
|
$sql .= ' ' . $this->source->getAsString();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (empty($this->columns))
|
||||||
|
{
|
||||||
|
$config = \Chibi\Registry::getConfig();
|
||||||
|
if ($config->main->dbDriver == 'sqlite')
|
||||||
|
$sql .= ' DEFAULT VALUES';
|
||||||
|
else
|
||||||
|
$sql .= ' VALUES()';
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
$sql .= ' (' . join(', ', array_keys($this->columns)) . ')';
|
||||||
|
|
||||||
|
$sql .= ' VALUES (' . join(', ', array_map(function($val)
|
||||||
|
{
|
||||||
|
return '(' . $val->getAsString() . ')';
|
||||||
|
}, array_values($this->columns))) . ')';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return $sql;
|
||||||
|
}
|
||||||
|
}
|
15
src/Sql/Statements/SqlRawStatement.php
Normal file
15
src/Sql/Statements/SqlRawStatement.php
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
<?php
|
||||||
|
class SqlRawStatement extends SqlStatement
|
||||||
|
{
|
||||||
|
protected $text;
|
||||||
|
|
||||||
|
public function __construct($text)
|
||||||
|
{
|
||||||
|
$this->text = $text;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getAsString()
|
||||||
|
{
|
||||||
|
return $this->text;
|
||||||
|
}
|
||||||
|
}
|
197
src/Sql/Statements/SqlSelectStatement.php
Normal file
197
src/Sql/Statements/SqlSelectStatement.php
Normal file
|
@ -0,0 +1,197 @@
|
||||||
|
<?php
|
||||||
|
class SqlSelectStatement extends SqlStatement
|
||||||
|
{
|
||||||
|
const ORDER_ASC = 1;
|
||||||
|
const ORDER_DESC = 2;
|
||||||
|
|
||||||
|
protected $columns = null;
|
||||||
|
protected $source = null;
|
||||||
|
protected $innerJoins = [];
|
||||||
|
protected $outerJoins = [];
|
||||||
|
protected $criterion = null;
|
||||||
|
protected $orderBy = [];
|
||||||
|
protected $limit = null;
|
||||||
|
protected $offset = null;
|
||||||
|
protected $groupBy = null;
|
||||||
|
|
||||||
|
public function getColumns()
|
||||||
|
{
|
||||||
|
return $this->columns;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function resetColumns()
|
||||||
|
{
|
||||||
|
$this->columns = [];
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setColumn($what)
|
||||||
|
{
|
||||||
|
$this->setColumns([$what]);
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function addColumn($what)
|
||||||
|
{
|
||||||
|
$this->columns []= $this->attachExpression($what);
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setColumns($what)
|
||||||
|
{
|
||||||
|
$this->resetColumns();
|
||||||
|
foreach ($what as $item)
|
||||||
|
$this->addColumn($item);
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getTable()
|
||||||
|
{
|
||||||
|
return $this->source;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setTable($table)
|
||||||
|
{
|
||||||
|
$this->source = new SqlStringExpression($table);
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setSource(SqlExpression $source)
|
||||||
|
{
|
||||||
|
$this->source = $this->attachExpression($source);
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function addInnerJoin($table, SqlExpression $expression)
|
||||||
|
{
|
||||||
|
$this->innerJoins []= [$table, $this->attachExpression($expression)];
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function addOuterJoin($table, $expression)
|
||||||
|
{
|
||||||
|
$this->innerJoins []= [$table, $this->attachExpression($expression)];
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getCriterion()
|
||||||
|
{
|
||||||
|
return $this->criterion;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setCriterion($criterion)
|
||||||
|
{
|
||||||
|
$this->criterion = $this->attachExpression($criterion);
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function resetOrderBy()
|
||||||
|
{
|
||||||
|
$this->orderBy = [];
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setOrderBy($what, $dir = self::ORDER_ASC)
|
||||||
|
{
|
||||||
|
$this->resetOrderBy();
|
||||||
|
$this->addOrderBy($this->attachExpression($what), $dir);
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function addOrderBy($what, $dir = self::ORDER_ASC)
|
||||||
|
{
|
||||||
|
$this->orderBy []= [$this->attachExpression($what), $dir];
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getOrderBy()
|
||||||
|
{
|
||||||
|
return $this->orderBy;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function resetLimit()
|
||||||
|
{
|
||||||
|
$this->limit = null;
|
||||||
|
$this->offset = null;
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setLimit($limit, $offset = null)
|
||||||
|
{
|
||||||
|
$this->limit = $this->attachExpression($limit);
|
||||||
|
$this->offset = $this->attachExpression($offset);
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function groupBy($groupBy)
|
||||||
|
{
|
||||||
|
$this->groupBy = $this->attachExpression($groupBy);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getAsString()
|
||||||
|
{
|
||||||
|
$sql = 'SELECT ';
|
||||||
|
if (!empty($this->columns))
|
||||||
|
$sql .= join(', ', array_map(function($column)
|
||||||
|
{
|
||||||
|
return $column->getAsString();
|
||||||
|
}, $this->columns));
|
||||||
|
else
|
||||||
|
$sql .= '1';
|
||||||
|
$sql .= ' FROM (' . $this->source->getAsString() . ')';
|
||||||
|
|
||||||
|
foreach ($this->innerJoins as $join)
|
||||||
|
{
|
||||||
|
list ($table, $criterion) = $join;
|
||||||
|
$sql .= ' INNER JOIN ' . $table . ' ON ' . $criterion->getAsString();
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach ($this->outerJoins as $outerJoin)
|
||||||
|
{
|
||||||
|
list ($table, $criterion) = $join;
|
||||||
|
$sql .= ' OUTER JOIN ' . $table . ' ON ' . $criterion->getAsString();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!empty($this->criterion) and !empty($this->criterion->getAsString()))
|
||||||
|
$sql .= ' WHERE ' . $this->criterion->getAsString();
|
||||||
|
|
||||||
|
if (!empty($this->groupBy) and !empty($this->groupBy->getAsString()))
|
||||||
|
{
|
||||||
|
$sql .= ' GROUP BY ' . $this->groupBy->getAsString();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!empty($this->orderBy))
|
||||||
|
{
|
||||||
|
$f = true;
|
||||||
|
foreach ($this->orderBy as $orderBy)
|
||||||
|
{
|
||||||
|
$sql .= $f ? ' ORDER BY' : ', ';
|
||||||
|
$f = false;
|
||||||
|
list ($orderColumn, $orderDir) = $orderBy;
|
||||||
|
$sql .= ' ' . $orderColumn->getAsString();
|
||||||
|
switch ($orderDir)
|
||||||
|
{
|
||||||
|
case self::ORDER_DESC:
|
||||||
|
$sql .= ' DESC';
|
||||||
|
break;
|
||||||
|
case self::ORDER_ASC:
|
||||||
|
$sql .= ' ASC';
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!empty($this->limit) and !empty($this->limit->getAsString()))
|
||||||
|
{
|
||||||
|
$sql .= ' LIMIT ';
|
||||||
|
$sql .= $this->limit->getAsString();
|
||||||
|
if (!empty($this->offset))
|
||||||
|
{
|
||||||
|
$sql .= ' OFFSET ';
|
||||||
|
$sql .= $this->offset->getAsString();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $sql;
|
||||||
|
}
|
||||||
|
}
|
4
src/Sql/Statements/SqlStatement.php
Normal file
4
src/Sql/Statements/SqlStatement.php
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
<?php
|
||||||
|
abstract class SqlStatement extends SqlExpression
|
||||||
|
{
|
||||||
|
}
|
53
src/Sql/Statements/SqlUpdateStatement.php
Normal file
53
src/Sql/Statements/SqlUpdateStatement.php
Normal file
|
@ -0,0 +1,53 @@
|
||||||
|
<?php
|
||||||
|
class SqlUpdateStatement extends SqlStatement
|
||||||
|
{
|
||||||
|
protected $table;
|
||||||
|
protected $criterion;
|
||||||
|
protected $columns;
|
||||||
|
|
||||||
|
public function getTable()
|
||||||
|
{
|
||||||
|
return $this->table;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setTable($table)
|
||||||
|
{
|
||||||
|
$this->table = new SqlStringExpression($table);
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getCriterion()
|
||||||
|
{
|
||||||
|
return $this->criterion;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setCriterion($criterion)
|
||||||
|
{
|
||||||
|
$this->criterion = $this->attachExpression($criterion);
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setColumn($column, $what)
|
||||||
|
{
|
||||||
|
$this->columns[$column] = $this->attachExpression($what);
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getAsString()
|
||||||
|
{
|
||||||
|
$sql = 'UPDATE ' . $this->table->getAsString();
|
||||||
|
|
||||||
|
if (!empty($this->columns))
|
||||||
|
{
|
||||||
|
$sql .= ' SET ' . join(', ', array_map(function($key)
|
||||||
|
{
|
||||||
|
return $key . ' = (' . $this->columns[$key]->getAsString() . ')';
|
||||||
|
}, array_keys($this->columns)));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!empty($this->criterion) and !empty($this->criterion->getAsString()))
|
||||||
|
$sql .= ' WHERE ' . $this->criterion->getAsString();
|
||||||
|
|
||||||
|
return $sql;
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,99 +0,0 @@
|
||||||
<?php
|
|
||||||
class SqlQuery
|
|
||||||
{
|
|
||||||
protected $sql;
|
|
||||||
protected $bindings;
|
|
||||||
|
|
||||||
public function __construct()
|
|
||||||
{
|
|
||||||
$this->sql = '';
|
|
||||||
$this->bindings = [];
|
|
||||||
}
|
|
||||||
|
|
||||||
public function __call($name, array $arguments)
|
|
||||||
{
|
|
||||||
$name = TextHelper::camelCaseToKebabCase($name);
|
|
||||||
$name = str_replace('-', ' ', $name);
|
|
||||||
$this->sql .= $name . ' ';
|
|
||||||
|
|
||||||
if (!empty($arguments))
|
|
||||||
{
|
|
||||||
$arg = array_shift($arguments);
|
|
||||||
assert(empty($arguments));
|
|
||||||
|
|
||||||
if (is_object($arg))
|
|
||||||
{
|
|
||||||
throw new Exception('Not implemented');
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
$this->sql .= $arg . ' ';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return $this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function put($arg)
|
|
||||||
{
|
|
||||||
if (is_array($arg))
|
|
||||||
{
|
|
||||||
foreach ($arg as $key => $val)
|
|
||||||
{
|
|
||||||
if (is_numeric($key))
|
|
||||||
$this->bindings []= $val;
|
|
||||||
else
|
|
||||||
$this->bindings[$key] = $val;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
$this->bindings []= $arg;
|
|
||||||
}
|
|
||||||
return $this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function raw($raw)
|
|
||||||
{
|
|
||||||
$this->sql .= $raw . ' ';
|
|
||||||
return $this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function open()
|
|
||||||
{
|
|
||||||
$this->sql .= '(';
|
|
||||||
return $this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function close()
|
|
||||||
{
|
|
||||||
$this->sql .= ') ';
|
|
||||||
return $this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function surround($raw)
|
|
||||||
{
|
|
||||||
$this->sql .= '(' . $raw . ') ';
|
|
||||||
return $this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function genSlots($bindings)
|
|
||||||
{
|
|
||||||
if (empty($bindings))
|
|
||||||
return $this;
|
|
||||||
$this->sql .= '(';
|
|
||||||
$this->sql .= join(',', array_fill(0, count($bindings), '?'));
|
|
||||||
$this->sql .= ') ';
|
|
||||||
return $this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getBindings()
|
|
||||||
{
|
|
||||||
return $this->bindings;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getSql()
|
|
||||||
{
|
|
||||||
return trim($this->sql);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -50,7 +50,7 @@ LayoutHelper::addScript('core.js');
|
||||||
$bindings = [];
|
$bindings = [];
|
||||||
foreach ($query->getBindings() as $k => $v)
|
foreach ($query->getBindings() as $k => $v)
|
||||||
$bindings []= $k . '=' . $v;
|
$bindings []= $k . '=' . $v;
|
||||||
printf('<p>%s [%s]</p>', htmlspecialchars($query->getSql()), join(', ', $bindings));
|
printf('<p>%s [%s]</p>', htmlspecialchars($query->getAsString()), join(', ', $bindings));
|
||||||
} ?>
|
} ?>
|
||||||
</pre>
|
</pre>
|
||||||
<?php endif ?>
|
<?php endif ?>
|
||||||
|
|
|
@ -4,7 +4,14 @@ $config = \Chibi\Registry::getConfig();
|
||||||
|
|
||||||
function getDbVersion()
|
function getDbVersion()
|
||||||
{
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
$dbVersion = PropertyModel::get(PropertyModel::DbVersion);
|
$dbVersion = PropertyModel::get(PropertyModel::DbVersion);
|
||||||
|
}
|
||||||
|
catch (Exception $e)
|
||||||
|
{
|
||||||
|
return [null, null];
|
||||||
|
}
|
||||||
if (strpos($dbVersion, '.') !== false)
|
if (strpos($dbVersion, '.') !== false)
|
||||||
{
|
{
|
||||||
list ($dbVersionMajor, $dbVersionMinor) = explode('.', $dbVersion);
|
list ($dbVersionMajor, $dbVersionMinor) = explode('.', $dbVersion);
|
||||||
|
@ -50,7 +57,7 @@ foreach ($upgrades as $upgradePath)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
Database::query((new SqlQuery)->raw($query));
|
Database::exec(new SqlRawStatement($query));
|
||||||
}
|
}
|
||||||
catch (Exception $e)
|
catch (Exception $e)
|
||||||
{
|
{
|
||||||
|
|
Loading…
Reference in a new issue