Skip to content

Commit

Permalink
refactor: SQL class
Browse files Browse the repository at this point in the history
Signed-off-by: Varun Patil <radialapps@gmail.com>
  • Loading branch information
pulsejet committed Mar 31, 2024
1 parent 92b1a11 commit a62e3b7
Show file tree
Hide file tree
Showing 13 changed files with 167 additions and 123 deletions.
3 changes: 2 additions & 1 deletion lib/ClustersBackend/AlbumsBackend.php
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
namespace OCA\Memories\ClustersBackend;

use OCA\Memories\Db\AlbumsQuery;
use OCA\Memories\Db\SQL;
use OCA\Memories\Db\TimelineQuery;
use OCA\Memories\Exceptions;
use OCA\Memories\Util;
Expand Down Expand Up @@ -83,7 +84,7 @@ public function transformDayQuery(IQueryBuilder &$query, bool $aggregate): void
public function getClustersInternal(int $fileid = 0): array
{
// Materialize the query
$materialize = static fn (IQueryBuilder & $query): IQueryBuilder => TimelineQuery::materialize($query, 'pa');
$materialize = static fn (IQueryBuilder & $query): IQueryBuilder => SQL::materialize($query, 'pa');

// Function to add etag
$etag = static fn (string $name): \Closure => static function (IQueryBuilder &$query) use ($name): void {
Expand Down
8 changes: 4 additions & 4 deletions lib/ClustersBackend/Backend.php
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@

namespace OCA\Memories\ClustersBackend;

use OCA\Memories\Db\TimelineQuery;
use OCA\Memories\Db\SQL;
use OCA\Memories\Util;
use OCP\DB\QueryBuilder\IQueryBuilder;
use OCP\Files\SimpleFS\ISimpleFile;
Expand Down Expand Up @@ -264,7 +264,7 @@ final protected function selectCover(
->andWhere($validSq->expr()->eq("cov_objs.{$objectTableClusterId}", "{$clusterTable}.{$clusterTableId}"))
;

$clauses[] = $query->createFunction("EXISTS ({$validSq->getSQL()})");
$clauses[] = SQL::exists($query, $validSq);
}

// Subquery if the file is still in the user's timeline tree
Expand All @@ -279,7 +279,7 @@ final protected function selectCover(
->where($treeSq->expr()->eq('cov_f.fileid', 'mcov.fileid'))
;

$clauses[] = $query->createFunction("EXISTS ({$treeSq->getSQL()})");
$clauses[] = SQL::exists($query, $treeSq);
}

// Make subquery to select the cover
Expand All @@ -291,7 +291,7 @@ final protected function selectCover(
;

// SELECT the cover
$query->selectAlias(TimelineQuery::subquery($query, $cvQ), $field);
$query->selectAlias(SQL::subquery($query, $cvQ), $field);
}

/**
Expand Down
9 changes: 5 additions & 4 deletions lib/ClustersBackend/FaceRecognitionBackend.php
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@

namespace OCA\Memories\ClustersBackend;

use OCA\Memories\Db\SQL;
use OCA\Memories\Db\TimelineQuery;
use OCA\Memories\Util;
use OCP\DB\QueryBuilder\IQueryBuilder;
Expand Down Expand Up @@ -288,7 +289,7 @@ private function getFaceRecognitionClusters(int $fileid = 0): array
$query->setMaxResults(15);

// SELECT covers
$query = $this->tq->materialize($query, 'frp');
$query = SQL::materialize($query, 'frp');
$this->selectCover(
query: $query,
clusterTable: 'frp',
Expand All @@ -299,7 +300,7 @@ private function getFaceRecognitionClusters(int $fileid = 0): array
);

// SELECT etag for the cover
$query = $this->tq->materialize($query, 'frp');
$query = SQL::materialize($query, 'frp');
$this->tq->selectEtag($query, 'cover', 'cover_etag');

// FETCH all faces
Expand Down Expand Up @@ -346,7 +347,7 @@ private function getFaceRecognitionPersons(int $fileid = 0): array
$query->addOrderBy('frp.name', 'ASC');

// SELECT to get all covers
$query = $this->tq->materialize($query, 'frp');
$query = SQL::materialize($query, 'frp');
$this->selectCover(
query: $query,
clusterTable: 'frp',
Expand All @@ -357,7 +358,7 @@ private function getFaceRecognitionPersons(int $fileid = 0): array
);

// SELECT etag for the cover
$query = $this->tq->materialize($query, 'frp');
$query = SQL::materialize($query, 'frp');
$this->tq->selectEtag($query, 'frp.cover', 'cover_etag');

// FETCH all faces
Expand Down
9 changes: 5 additions & 4 deletions lib/ClustersBackend/PlacesBackend.php
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@

namespace OCA\Memories\ClustersBackend;

use OCA\Memories\Db\SQL;
use OCA\Memories\Db\TimelineQuery;
use OCA\Memories\Settings\SystemConfig;
use OCA\Memories\Util;
Expand Down Expand Up @@ -90,7 +91,7 @@ public function getClustersInternal(int $fileid = 0): array
->where($sub->expr()->eq('mp_sq.osm_id', $query->createNamedParameter($inside, \PDO::PARAM_INT)))
->andWhere($sub->expr()->eq('mp_sq.fileid', 'mp.fileid'))
;
$mpJoinOn[] = $query->createFunction("EXISTS ({$sub->getSQL()})");
$mpJoinOn[] = SQL::exists($query, $sub);

// Add WHERE clauses to main query to filter out admin_levels
$sub = $this->tq->getBuilder();
Expand Down Expand Up @@ -129,7 +130,7 @@ public function getClustersInternal(int $fileid = 0): array
// We use this as the subquery for the main query, where we also re-join with
// oc_memories_planet to the the names from the IDS
// If we just AGGREGATE+GROUP with the name in one query, then it can't use indexes
$query = $this->tq->materialize($query, 'sub');
$query = SQL::materialize($query, 'sub');

// INNER JOIN back on the planet table to get the names
$query->innerJoin('sub', 'memories_planet', 'e', $query->expr()->eq('e.osm_id', 'sub.osm_id'));
Expand All @@ -150,7 +151,7 @@ public function getClustersInternal(int $fileid = 0): array

// SELECT to get all covers
if ($covers) {
$query = $this->tq->materialize($query, 'sub');
$query = SQL::materialize($query, 'sub');
$this->selectCover(
query: $query,
clusterTable: 'sub',
Expand All @@ -161,7 +162,7 @@ public function getClustersInternal(int $fileid = 0): array
);

// SELECT etag for the cover
$query = $this->tq->materialize($query, 'sub');
$query = SQL::materialize($query, 'sub');
$this->tq->selectEtag($query, 'sub.cover', 'cover_etag');
}

Expand Down
10 changes: 5 additions & 5 deletions lib/ClustersBackend/RecognizeBackend.php
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@

namespace OCA\Memories\ClustersBackend;

use OCA\Memories\Db\SQL;
use OCA\Memories\Db\TimelineQuery;
use OCA\Memories\Util;
use OCP\DB\QueryBuilder\IQueryBuilder;
Expand Down Expand Up @@ -157,9 +158,8 @@ public function getClustersInternal(int $fileid = 0): array
$query->expr()->eq('rfd.cluster_id', 'rfc.id'),
$query->expr()->eq('rfd.file_id', $query->createNamedParameter($fileid, \PDO::PARAM_INT)),
))
->getSQL()
;
$query->andWhere($query->createFunction("EXISTS ({$fSq})"));
$query->andWhere(SQL::exists($query, $fSq));
}

// GROUP by ID of face cluster
Expand All @@ -171,7 +171,7 @@ public function getClustersInternal(int $fileid = 0): array
$query->addOrderBy('rfc.id'); // tie-breaker

// SELECT to get all covers
$query = $this->tq->materialize($query, 'rfc');
$query = SQL::materialize($query, 'rfc');
$this->selectCover(
query: $query,
clusterTable: 'rfc',
Expand All @@ -183,14 +183,14 @@ public function getClustersInternal(int $fileid = 0): array

// SELECT etag for the cover
// Since the "cover" is the face detection, we need the actual file for etag
$query = $this->tq->materialize($query, 'rfc');
$query = SQL::materialize($query, 'rfc');
$cfSq = $this->tq->getBuilder();
$cfSq->select('file_id')
->from('recognize_face_detections', 'rfd')
->where($cfSq->expr()->eq('rfd.id', 'rfc.cover'))
->setMaxResults(1)
;
$this->tq->selectEtag($query, $this->tq->subquery($query, $cfSq), 'cover_etag');
$this->tq->selectEtag($query, SQL::subquery($query, $cfSq), 'cover_etag');

// FETCH all faces
$faces = $this->tq->executeQueryWithCTEs($query)->fetchAll() ?: [];
Expand Down
5 changes: 3 additions & 2 deletions lib/ClustersBackend/TagsBackend.php
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@

namespace OCA\Memories\ClustersBackend;

use OCA\Memories\Db\SQL;
use OCA\Memories\Db\TimelineQuery;
use OCA\Memories\Util;
use OCP\DB\QueryBuilder\IQueryBuilder;
Expand Down Expand Up @@ -96,7 +97,7 @@ public function getClustersInternal(int $fileid = 0): array
$query->addOrderBy('st.id'); // tie-breaker

// SELECT cover photo
$query = $this->tq->materialize($query, 'st');
$query = SQL::materialize($query, 'st');
$this->selectCover(
query: $query,
clusterTable: 'st',
Expand All @@ -107,7 +108,7 @@ public function getClustersInternal(int $fileid = 0): array
);

// SELECT etag for the cover
$query = $this->tq->materialize($query, 'st');
$query = SQL::materialize($query, 'st');
$this->tq->selectEtag($query, 'st.cover', 'cover_etag');

// FETCH all tags
Expand Down
3 changes: 1 addition & 2 deletions lib/Db/AlbumsQuery.php
Original file line number Diff line number Diff line change
Expand Up @@ -76,9 +76,8 @@ public function getList(
$query->expr()->eq('paf.album_id', 'pa.album_id'),
$query->expr()->eq('paf.file_id', $query->createNamedParameter($fileid, IQueryBuilder::PARAM_INT)),
))
->getSQL()
;
$query->andWhere($query->createFunction("EXISTS ({$fSq})"));
$query->andWhere(SQL::exists($query, $fSq));
}

// Apply further transformations
Expand Down
106 changes: 106 additions & 0 deletions lib/Db/SQL.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
<?php

declare(strict_types=1);

namespace OCA\Memories\Db;

use OCP\DB\QueryBuilder\IQueryBuilder;
use OCP\DB\QueryBuilder\IQueryFunction;

class SQL
{
/**
* @return never
*/
public static function debugQuery(IQueryBuilder &$query, string $sql = '')
{
// Print the query and exit
$sql = empty($sql) ? $query->getSQL() : $sql;
$sql = str_replace('*PREFIX*', 'oc_', $sql);
$sql = self::replaceQueryParams($query, $sql);
echo "{$sql}";

exit; // only for debugging, so this is okay
}

public static function replaceQueryParams(IQueryBuilder &$query, string $sql): string
{
$params = $query->getParameters();
$platform = $query->getConnection()->getDatabasePlatform();
foreach ($params as $key => $value) {
if (\is_array($value)) {
$value = implode(',', array_map(static fn ($v) => $platform->quoteStringLiteral($v), $value));
} elseif (\is_bool($value)) {
$value = $platform->quoteStringLiteral($value ? '1' : '0');
} elseif (null === $value) {
$value = $platform->quoteStringLiteral('NULL');
} else {
$value = $platform->quoteStringLiteral((string) $value);
}

$sql = str_replace(':'.$key, $value, $sql);
}

return $sql;
}

/**
* Materialize a query as a subquery and select everything from it.
* This is very useful for optimization.
*
* @param IQueryBuilder $query The query to materialize
* @param string $alias The alias to use for the subquery
*/
public static function materialize(IQueryBuilder $query, string $alias): IQueryBuilder
{
// Create new query and copy over parameters (and types)
$outer = $query->getConnection()->getQueryBuilder();
$outer->setParameters($query->getParameters(), $query->getParameterTypes());

// Create the subquery function for selecting from it
$outer->select("{$alias}.*")->from(self::subquery($outer, $query), $alias);

return $outer;
}

/**
* Create a subquery function.
*
* @param IQueryBuilder $query The query to create the function on
* @param IQueryBuilder $subquery The subquery to use
*/
public static function subquery(IQueryBuilder &$query, IQueryBuilder &$subquery): IQueryFunction
{
return $query->createFunction("({$subquery->getSQL()})");
}

/**
* Create an EXISTS expression.
*
* @param IQueryBuilder $query The query to create the function on
* @param IQueryBuilder|string $clause The clause to check for existence
*/
public static function exists(IQueryBuilder &$query, IQueryBuilder|string &$clause): IQueryFunction
{
if ($clause instanceof IQueryBuilder) {
$clause = $clause->getSQL();
}

return $query->createFunction("EXISTS ({$clause})");
}

/**
* Create a NOT EXISTS expression.
*
* @param IQueryBuilder $query The query to create the function on
* @param IQueryBuilder|string $clause The clause to check for existence
*/
public static function notExists(IQueryBuilder &$query, IQueryBuilder|string &$clause): IQueryFunction
{
if ($clause instanceof IQueryBuilder) {
$clause = $clause->getSQL();
}

return $query->createFunction("NOT EXISTS ({$clause})");
}
}

0 comments on commit a62e3b7

Please sign in to comment.