Skip to content

Commit 85cdf0f

Browse files
jessevzjessevzs3inlc
authored
Made it possible to sort on 1 to 1 relationship attributes (#1833)
* Made it possible to sort on 1 to 1 relationship attributes * Added the possibility to do left, right and outer joins to the ORM, and filtering on joins * Added to the orm a coalesce order filter which is needed to order on taskwrappername or taskname * Added different types of joins to the abstractmodelfactory * Added an additional way to parse the filters, needed for the taskwrappers to handle the unconventional relation between taskwrapper and task * Fixed bug in creating join filter --------- Co-authored-by: jessevz <jesse.van.zutphen@nfi.nl> Co-authored-by: Sein Coray <s3inlc@hashes.org>
1 parent ee9e685 commit 85cdf0f

File tree

10 files changed

+209
-35
lines changed

10 files changed

+209
-35
lines changed

src/dba/AbstractModelFactory.class.php

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -562,6 +562,7 @@ public function getFromDB($pk): ?AbstractModel {
562562
* @return AbstractModel[]|AbstractModel Returns a list of matching objects or Null
563563
*/
564564
private function filterWithJoin(array $options): array|AbstractModel {
565+
$vals = array();
565566
$joins = $this->getJoins($options);
566567
$factories = array($this);
567568
$query = "SELECT " . Util::createPrefixedString($this->getMappedModelTable(), self::getMappedModelKeys($this->getNullObject()));
@@ -580,11 +581,14 @@ private function filterWithJoin(array $options): array|AbstractModel {
580581
}
581582
$match1 = self::getMappedModelKey($localFactory->getNullObject(), $join->getMatch1());
582583
$match2 = self::getMappedModelKey($joinFactory->getNullObject(), $join->getMatch2());
583-
$query .= " INNER JOIN " . $joinFactory->getMappedModelTable() . " ON " . $localFactory->getMappedModelTable() . "." . $match1 . "=" . $joinFactory->getMappedModelTable() . "." . $match2 . " ";
584+
$query .= " " . $join->getJoinType() . " JOIN " . $joinFactory->getMappedModelTable() . " ON " . $localFactory->getMappedModelTable() . "." . $match1 . "=" . $joinFactory->getMappedModelTable() . "." . $match2 . " ";
585+
$joinQueryFilters = $join->getQueryFilters();
586+
if (count($joinQueryFilters) > 0) {
587+
$query .= $this->applyFilters($vals, $joinQueryFilters, true) ;
588+
}
584589
}
585590

586591
// Apply all normal filter to this query
587-
$vals = array();
588592
if (array_key_exists("filter", $options)) {
589593
$query .= $this->applyFilters($vals, $options['filter']);
590594
}
@@ -605,7 +609,6 @@ private function filterWithJoin(array $options): array|AbstractModel {
605609
if (array_key_exists("limit", $options)) {
606610
$query .= $this->applyLimit($options['limit']);
607611
}
608-
609612
$dbh = self::getDB();
610613
$stmt = $dbh->prepare($query);
611614
$stmt->execute($vals);
@@ -709,7 +712,7 @@ public function filter(array $options, bool $single = false) {
709712
* @param $filters Filter|Filter[]
710713
* @return string
711714
*/
712-
private function applyFilters(&$vals, Filter|array $filters): string {
715+
private function applyFilters(&$vals, Filter|array $filters, bool $isJoinFilter = false): string {
713716
$parts = array();
714717
if (!is_array($filters)) {
715718
$filters = array($filters);
@@ -730,6 +733,9 @@ private function applyFilters(&$vals, Filter|array $filters): string {
730733
$vals[] = $v;
731734
}
732735
}
736+
if ($isJoinFilter) {
737+
return " AND " . implode(" AND ", $parts);
738+
}
733739
return " WHERE " . implode(" AND ", $parts);
734740
}
735741

@@ -758,7 +764,7 @@ private function applyJoins($joins): string {
758764
}
759765
$match1 = self::getMappedModelKey($localFactory->getNullObject(), $join->getMatch1());
760766
$match2 = self::getMappedModelKey($joinFactory->getNullObject(), $join->getMatch2());
761-
$query .= " INNER JOIN " . $joinFactory->getMappedModelTable() . " ON " . $localFactory->getMappedModelTable() . "." . $match1 . "=" . $joinFactory->getMappedModelTable() . "." . $match2 . " ";
767+
$query .= " " . $join->getJoinType() . " JOIN " . $joinFactory->getMappedModelTable() . " ON " . $localFactory->getMappedModelTable() . "." . $match1 . "=" . $joinFactory->getMappedModelTable() . "." . $match2 . " ";
762768
}
763769
return $query;
764770
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
<?php
2+
3+
namespace DBA;
4+
5+
class CoalesceOrderFilter extends Order {
6+
// The columns to do the COALESCE function on
7+
private $columns;
8+
private $type;
9+
10+
function __construct($columns, $type) {
11+
$this->columns = $columns;
12+
$this->type = $type;
13+
}
14+
15+
function getQueryString(AbstractModelFactory $factory, bool $includeTable = false): string {
16+
$mapped_columns = [];
17+
foreach($this->columns as $column) {
18+
array_push($mapped_columns, AbstractModelFactory::getMappedModelKey($factory->getNullObject(), $column));
19+
}
20+
return "COALESCE(" . implode(", ", $mapped_columns) . ") " . $this->type;
21+
}
22+
}

src/dba/Join.class.php

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,4 +17,14 @@ abstract function getMatch1();
1717
* @return string
1818
*/
1919
abstract function getMatch2();
20+
21+
/**
22+
* @return string
23+
*/
24+
abstract function getJoinType();
25+
26+
/**
27+
* @return QueryFilter[] array of queryfilters that have to be performed on the join
28+
*/
29+
abstract function getQueryFilters();
2030
}

src/dba/JoinFilter.class.php

Lines changed: 36 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,18 +27,36 @@ class JoinFilter extends Join {
2727
* @var AbstractModelFactory
2828
*/
2929
private $overrideOwnFactory;
30+
31+
/**
32+
* @var string
33+
*/
34+
private $joinType;
35+
36+
/**
37+
* @var QueryFilter[] array of queryfilters that have to be performed on the join
38+
*/
39+
private $queryFilters;
40+
41+
// string constants for the join types
42+
public const string INNER = "INNER";
43+
public const string LEFT = "LEFT";
44+
public const string RIGHT = "RIGHT";
3045

3146
/**
3247
* JoinFilter constructor.
3348
* @param $otherFactory AbstractModelFactory
3449
* @param $matching1 string
3550
* @param $matching2 string
3651
* @param $overrideOwnFactory AbstractModelFactory
52+
* @param $joinType string is normally inner, left or right
3753
*/
38-
function __construct($otherFactory, $matching1, $matching2, $overrideOwnFactory = null) {
54+
function __construct($otherFactory, $matching1, $matching2, $overrideOwnFactory = null, $joinType = JoinFilter::INNER, $queryFilters = []) {
3955
$this->otherFactory = $otherFactory;
4056
$this->match1 = $matching1;
4157
$this->match2 = $matching2;
58+
$this->joinType = $joinType;
59+
$this->queryFilters = $queryFilters;
4260

4361
$this->otherTableName = $this->otherFactory->getMappedModelTable();
4462
$this->overrideOwnFactory = $overrideOwnFactory;
@@ -62,6 +80,23 @@ function getMatch2() {
6280
function getOtherTableName() {
6381
return $this->otherTableName;
6482
}
83+
84+
function getJoinType() {
85+
return $this->joinType;
86+
}
87+
88+
function setJoinType($joinType) {
89+
$this->joinType = $joinType;
90+
}
91+
92+
93+
function getQueryFilters() {
94+
return $this->queryFilters;
95+
}
96+
97+
function setQueryFilters(array $queryFilters) {
98+
$this->queryFilters = $queryFilters;
99+
}
65100

66101
/**
67102
* @return AbstractModelFactory

src/dba/OrderFilter.class.php

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,14 @@ function __construct($by, $type, $overrideFactory = null) {
1515
$this->type = $type;
1616
$this->overrideFactory = $overrideFactory;
1717
}
18+
19+
function getBy(): string {
20+
return $this->by;
21+
}
22+
23+
function getType(): string {
24+
return $this->type;
25+
}
1826

1927
function getQueryString(AbstractModelFactory $factory, bool $includeTable = false): string {
2028
if ($this->overrideFactory != null) {

src/dba/init.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
require_once(dirname(__FILE__) . "/Aggregation.class.php");
1414
require_once(dirname(__FILE__) . "/Filter.class.php");
1515
require_once(dirname(__FILE__) . "/Order.class.php");
16+
require_once(dirname(__FILE__) . "/CoalesceOrderFilter.class.php");
1617
require_once(dirname(__FILE__) . "/Join.class.php");
1718
require_once(dirname(__FILE__) . "/Group.class.php");
1819
require_once(dirname(__FILE__) . "/Limit.class.php");

src/inc/apiv2/common/AbstractBaseAPI.class.php

Lines changed: 52 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -144,6 +144,16 @@ protected function getFeatures(): array {
144144
}
145145
return $features;
146146
}
147+
148+
/**
149+
* Get features based on DBA model features
150+
*
151+
* @param string $dbaClass is the dba class to get the features from
152+
*/
153+
//TODO doesnt retrieve features based on form fields, could be done by adding api class in relationship objects
154+
final protected function getFeaturesOther(string $dbaClass): array {
155+
return call_user_func($dbaClass . '::getFeatures');
156+
}
147157

148158
protected function getUpdateHandlers($id, $current_user): array {
149159
return [];
@@ -174,6 +184,11 @@ public function getAliasedFeatures(): array {
174184
$features = $this->getFeatures();
175185
return $this->mapFeatures($features);
176186
}
187+
188+
public function getAliasedFeaturesOther($dbaClass): array {
189+
$features = $this->getFeaturesOther($dbaClass);
190+
return $this->mapFeatures($features);
191+
}
177192

178193
final protected function mapFeatures($features): array {
179194
$mappedFeatures = [];
@@ -183,6 +198,18 @@ final protected function mapFeatures($features): array {
183198
}
184199
return $mappedFeatures;
185200
}
201+
202+
public static function getToOneRelationships(): array {
203+
return [];
204+
}
205+
206+
public static function getToManyRelationships(): array {
207+
return [];
208+
}
209+
210+
public function getAllRelationships(): array {
211+
return array_merge($this->getToOneRelationships(), $this->getToManyRelationships());
212+
}
186213

187214
/**
188215
* Retrieve currently logged-in user
@@ -1145,19 +1172,39 @@ protected function makeOrderFilterTemplates(Request $request, array $features, s
11451172
$orderings = $this->getQueryParameterAsList($request, 'sort');
11461173
$contains_primary_key = false;
11471174
foreach ($orderings as $order) {
1148-
if (preg_match('/^(?P<operator>[-])?(?P<key>[_a-zA-Z]+)$/', $order, $matches)) {
1175+
$factory = null;
1176+
$joinKey = null;
1177+
$features_sort = $features;
1178+
if (preg_match('/^(?P<operator>[-])?(?P<key>[_a-zA-Z.]+)$/', $order, $matches)) {
11491179
// Special filtering of _id to use for uniform access to model primary key
11501180
$cast_key = $matches['key'] == 'id' ? $this->getPrimaryKey() : $matches['key'];
11511181
if ($cast_key == $this->getPrimaryKey()) {
11521182
$contains_primary_key = true;
11531183
}
1154-
if (array_key_exists($cast_key, $features)) {
1155-
$remappedKey = $features[$cast_key]['dbname'];
1184+
if (strpos($cast_key, ".")) {
1185+
$parts = explode(".", $cast_key);
1186+
if (count($parts) == 2) { // Only relations of 1 deep allowed ex. task.keyspace
1187+
$relationString = $parts[0];
1188+
//currently getting all relationships, but its probably only possible to sort on 1 to 1 relations
1189+
$relations = $this->getAllRelationships();
1190+
if (array_key_exists($relationString, $relations)) {
1191+
$relationClass = $relations[$relationString]['relationType'];
1192+
$relationFeatures = $this->getAliasedFeaturesOther($relationClass);
1193+
$factory = $this->getModelFactory($relationClass);
1194+
$joinKey = $relations[$relationString]['relationKey'];
1195+
$key = $relations[$relationString]['key'];
1196+
$features_sort = $relationFeatures;
1197+
$cast_key = $parts[1];
1198+
}
1199+
}
1200+
}
1201+
if (array_key_exists($cast_key, $features_sort)) {
1202+
$remappedKey = $features_sort[$cast_key]['dbname'];
11561203
$type = ($matches['operator'] == '-') ? "DESC" : "ASC";
11571204
if ($reverseSort) {
11581205
$type = ($type == "ASC") ? "DESC" : "ASC";
11591206
}
1160-
$orderTemplates[] = ['by' => $remappedKey, 'type' => $type];
1207+
$orderTemplates[] = ['by' => $remappedKey, 'type' => $type, 'factory' => $factory, 'joinKey' => $joinKey, 'key' => $key];
11611208
}
11621209
else {
11631210
throw new HttpForbidden("Ordering parameter '" . $order . "' is not valid");
@@ -1170,7 +1217,7 @@ protected function makeOrderFilterTemplates(Request $request, array $features, s
11701217

11711218
//when no primary key has been added in the sort parameter, add the default case of sorting on primary key as last sort
11721219
if (!$contains_primary_key) {
1173-
$orderTemplates[] = ['by' => $this->getPrimaryKey(), 'type' => $defaultSort];
1220+
$orderTemplates[] = ['by' => $this->getPrimaryKey(), 'type' => $defaultSort, 'factory' => null, 'joinKey' => null];
11741221
}
11751222

11761223
return $orderTemplates;

0 commit comments

Comments
 (0)