Skip to content

Commit

Permalink
NEW Add sortByField and remove mysql specific code from DataList (#11588
Browse files Browse the repository at this point in the history
)

Co-authored-by: Lenny Obez <github@lennyobez.com>
  • Loading branch information
lekoala and LennyObez authored Feb 3, 2025
1 parent e7cccf6 commit 9d1741c
Show file tree
Hide file tree
Showing 4 changed files with 109 additions and 6 deletions.
22 changes: 22 additions & 0 deletions src/ORM/Connect/Database.php
Original file line number Diff line number Diff line change
Expand Up @@ -954,4 +954,26 @@ abstract public function now();
* @return string Expression for a random value
*/
abstract public function random();

/**
* Generate SQL for sorting by a specific field using CASE logic.
*
* Subclasses can override this method to provide optimized implementations
* (e.g., using MySQL's FIELD method).
*
* @param string $field The name of the field to sort by.
* @param array $values The values to order by.
* @return string SQL snippet for ordering.
*/
public function sortByField(string $field, array $values): string
{
$caseStatements = [];
foreach ($values as $index => $value) {
$escaped = is_int($value) ? $value : "'" . addslashes($value) . "'";
$caseStatements[] = "CASE {$field} = {$escaped} THEN {$index}";
}
$count = count($caseStatements);
$sqlCase = implode(' ', $caseStatements);
return "CASE {$sqlCase} ELSE {$count} END";
}
}
18 changes: 18 additions & 0 deletions src/ORM/Connect/MySQLDatabase.php
Original file line number Diff line number Diff line change
Expand Up @@ -586,4 +586,22 @@ public function clearTable($table)
$this->query("ALTER TABLE \"$table\" AUTO_INCREMENT = 1");
}
}

/**
* Generate SQL for sorting by a specific field using MySQL's FIELD function.
*
* @param string $field The name of the field to sort by.
* @param array $values The values to order by.
* @return string SQL snippet for ordering.
*/
public function sortByField(string $field, array $values): string
{
$escapedValues = [];
foreach ($values as $value) {
$escaped = is_int($value) ? $value : "'" . addslashes($value) . "'";
$escapedValues[] = $escaped;
}
$sqlIds = implode(',', $escapedValues);
return "FIELD({$field}, {$sqlIds})";
}
}
16 changes: 10 additions & 6 deletions src/ORM/DataList.php
Original file line number Diff line number Diff line change
Expand Up @@ -1374,16 +1374,20 @@ private function fetchEagerLoadManyMany(
// Note that $joinRows also holds extra fields data
$joinRows = [];
if (!empty($parentIDs) && !empty($fetchedIDs)) {
$fetchedIDsAsString = implode(',', $fetchedIDs);
$joinRows = DB::query(
'SELECT * FROM "' . $joinTable
// Use sortByField to generate the ORDER BY clause
$orderByClause = DB::get_conn()->sortByField($childIDField, $fetchedIDs);

// Build the query with the order clause
$joinQuery = 'SELECT * FROM "' . $joinTable
// Only get joins relevant for the parent list
. '" WHERE "' . $parentIDField . '" IN (' . implode(',', $parentIDs) . ')'
// Exclude any children that got filtered out
. ' AND ' . $childIDField . ' IN (' . $fetchedIDsAsString . ')'
. ' AND ' . $childIDField . ' IN (' . implode(',', $fetchedIDs) . ')'
// Respect sort order of fetched items
. ' ORDER BY FIELD(' . $childIDField . ', ' . $fetchedIDsAsString . ')'
);
. ' ORDER BY ' . $orderByClause;

// Execute the query
$joinRows = DB::query($joinQuery);
}

// Store the children in an EagerLoadedList against the correct parent
Expand Down
59 changes: 59 additions & 0 deletions tests/php/ORM/DatabaseTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -494,4 +494,63 @@ public function testRewindWithPredicates()
$i++;
}
}

/**
* Test that the sortByField method correctly generates an ORDER BY clause
* and the query returns results in the expected order.
*/
public function testSortByFieldIntegration()
{
// Define test data
$parentIDs = [1, 2];
$fetchedIDs = [3, 2, 1]; // Expected order
$expectedOrder = [3, 2, 1];

$joinTable = 'TestJoinTable';
$parentIDField = 'ParentID';
$childIDField = 'ChildID';

// Create a temporary test table
DB::query("
CREATE TEMPORARY TABLE {$joinTable} (
{$childIDField} INT NOT NULL,
{$parentIDField} INT NOT NULL
)
");

// Insert mock data
DB::query("
INSERT INTO {$joinTable} ({$childIDField}, {$parentIDField})
VALUES (1, 1), (2, 2), (3, 1)
");

// Use sortByField to generate the ORDER BY clause
$orderByClause = DB::get_conn()->sortByField($childIDField, $fetchedIDs);

// Build the query with sortByField
$query = "
SELECT *
FROM {$joinTable}
WHERE {$parentIDField} IN (" . implode(',', $parentIDs) . ")
AND {$childIDField} IN (" . implode(',', $fetchedIDs) . ")
ORDER BY {$orderByClause}
";

// Execute the query and convert results to an array
$result = DB::query($query);
$rows = [];
foreach ($result as $row) {
$rows[] = $row;
}

// Extract the actual order of fetched ChildIDs
$actualOrder = array_column($rows, $childIDField);

// Assert that the results are ordered as expected
$this->assertEquals(
$expectedOrder,
$actualOrder,
'The results should maintain the custom order defined by fetchedIDs'
);
}
}

0 comments on commit 9d1741c

Please sign in to comment.