Problem/Motivation

As a developer using PostgreSQL DBMS, I'm able to use LEAST() SQL function to return the lowest value from a list of given values.

But, I'm using SQLite during KernelTests for performance reasons, and this LEAST() function is not recognized, therefore my original SQL query is not a valid statement.

Proposed resolution

We can make this function available by creating a SQLite function thanks to $pdo->sqliteCreateFunction().

Since a similar task has already been done for GREATEST(),
I'm wondering if we can inspire from $pdo->sqliteCreateFunction('greatest', [__CLASS__, 'sqlFunctionGreatest']);

So, my proposal is
in `/core/lib/Drupal/Core/Database/Driver/sqlite/Connection.php`,
- to write a static method called `sqlFunctionLeast` that covers `least` keyword.
- to apply it on sqlite thanks to $pdo in the `Connection::open()` method. (like this: `$pdo->sqliteCreateFunction('least', [__CLASS__, 'sqlFunctionLeast']);`)

Technical concerns

`LEAST()` function behaves in a sightly different way depending on the DBMS.

Examples

  • With PostgreSQL, `LEAST()` will return NULL as the lowest value, only if all the given values are NULL, regarding this documentation
  • With MySQL, `LEAST()` will return NULL as the lowest value, if any of the given values is NULL, regarding this documentation

For my use-case, I used the PostgreSQL definition of `LEAST()`. Accordingly, these scenarios should be covered:

$values = ['A', 'B', 'C', NULL];
sqlFunctionLeast($values); // must return A;
$values = [1, 2, 3, -1, NULL, FALSE, 0];
sqlFunctionLeast($values); // must return -1;
$values = [1, 2, 3, NULL];
sqlFunctionLeast($values); // must return 1;
$values = [NULL, NULL];
sqlFunctionLeast($values); // must return NULL;

I attached a PATCH for drupal 8.7.x version, so anyone else that needs this can use it and I will port it on 8.8.x ASAP.

And we can discussed the implementation below.

CommentFileSizeAuthor
#27 3120892-27.patch2.3 KBravi.shankar
#27 interdiff_25-27.txt978 bytesravi.shankar
#25 interdiff_18-25.txt589 bytesKumar Kundan
#25 3120892-25.patch2.3 KBKumar Kundan
#18 interdiff_15-18.txt543 bytesravi.shankar
#18 3120892-18.patch2.3 KBravi.shankar
#15 interdiff_12-15.txt1.42 KBravi.shankar
#15 3120892-15.patch2.34 KBravi.shankar
#12 interdiff_8-12.txt799 bytesKumar Kundan
#12 3120892-12.patch2.35 KBKumar Kundan
#11 interdiff_8-11.txt1.04 KBKumar Kundan
#11 3120892-11.patch2.4 KBKumar Kundan
#8 interdiff_5-8.txt1.78 KBKumar Kundan
#8 3120892-8.patch2.34 KBKumar Kundan
#5 interdiff_1-5.txt2.95 KBKumar Kundan
#5 3120892-5.patch2.29 KBKumar Kundan
sqlite-compatibility-implementation-least-8-7-x-1.patch1.17 KBjulienjoye
Support from Acquia helps fund testing for Drupal Acquia logo

Comments

julienjoye created an issue. See original summary.

daffie’s picture

Version: 8.7.x-dev » 9.1.x-dev
Status: Needs review » Needs work

Hi @julienjoye: Thank you for your patch!

The patch looks good. To get this patch committed there needs to be automated testing. We want to be sure that the new code does what it suppost to do and we want to make sure that somebody later does not accidently removes the added code. My suggestion would be to add what we call a KernelTest. See:https://www.drupal.org/docs/8/testing/types-of-tests-in-drupal-8. Add a new test class called SelectLeastTest in the directory "core/tests/Drupal/KernelTests/Core/Database/". In that class add a test with a provider method. See: https://phpunit.readthedocs.io/en/9.0/writing-tests-for-phpunit.html#dat... and search our code for the key "dataProvider". I will help you and review your patch.

kkalashnikov’s picture

Issue tags: +Needs tests
Kumar Kundan’s picture

Assigned: Unassigned » Kumar Kundan
Kumar Kundan’s picture

Preapred test case only.

daffie’s picture

Issue tags: -Needs tests

@Kumar Kundan: Thank you for working on this issue.

+++ b/core/tests/Drupal/KernelTests/Core/Database/SelectLeastTest.php
@@ -0,0 +1,36 @@
+  public function testSelectLeast($arg1, $arg2, $arg3, $arg4, $arg5, $expected) {
+    $this->assertSame($expected, min($arg1, $arg2, $arg3, $arg4, $arg5));
+  }

The test in not testing the SQL LEAST operator. Can we change the test to:

  /**
   * Tests the SQL LEAST operator.
   *
   * @dataProvider selectLeastProvider
   */
  public function testSelectLeast($values, $expected) {
    $least = Database::getConnection()->query("SELECT LEAST(:values[])", [':values[]' => $values])->fetchField();
    $this->assertEquals($expected, $least);
  }

To make this test work the test need to be a Kernel test.
I changed the row of arguments into an array. In this way we can test more combinations.

Kumar Kundan’s picture

Thanks @daffie. I started working on this approach.

Kumar Kundan’s picture

Assigned: Kumar Kundan » Unassigned
Status: Needs work » Needs review
Kumar Kundan’s picture

Assigned: Unassigned » Kumar Kundan
Status: Needs review » Needs work

I am working for Patch Improvment of SQLite database.

Kumar Kundan’s picture

Kumar Kundan’s picture

Kumar Kundan’s picture

Assigned: Kumar Kundan » Unassigned
Status: Needs work » Needs review
daffie’s picture

Status: Needs review » Needs work

Patch looks a lot better.

  1. +++ b/core/lib/Drupal/Core/Database/Driver/sqlite/Connection.php
    @@ -239,6 +240,15 @@ public static function sqlFunctionGreatest() {
    +    $values = array_filter(func_get_args(), 'strlen');
    

    Could we add a comment to this line. Something like: "Remove all NULL, FALSE and empty strings values but leaves 0 (zero) values."

  2. +++ b/core/tests/Drupal/KernelTests/Core/Database/SelectLeastTest.php
    @@ -0,0 +1,35 @@
    +      [['A', 'B', 'C', 'NULL'], 'A'],
    +      [['A', 'B', 'C', 'NULL'], 'A'],
    +      [[1, 2, 3, -1, 'NULL', 'FALSE', 0], -1],
    +      [[1, 2, 3, -1, 'NULL', 'FALSE', 0], -1]
    

    These line a double. This is not necessary.

  3. +++ b/core/tests/Drupal/KernelTests/Core/Database/SelectLeastTest.php
    @@ -0,0 +1,35 @@
    + * Tests THE SQL LEAST operator.
    

    Could we change the word "THE" to lowercase.

  4. The testbot fails for the PostgreSQL database. This is because PostgreSQL order the value NULL differently.
  5. +++ b/core/tests/Drupal/KernelTests/Core/Database/SelectLeastTest.php
    @@ -0,0 +1,35 @@
    +      [[1, 2, 3, -1, 'NULL', 'FALSE', 0], -1],
    

    This line fails for PostgreSQL, because it interpreted the values as strings and not as intergers. I think it is best just to remove lines with negative numbers.

ravi.shankar’s picture

Here I have tried to address comment #14, please review.

Status: Needs review » Needs work

The last submitted patch, 15: 3120892-15.patch, failed testing. View results
- codesniffer_fixes.patch Interdiff of automated coding standards fixes only.

daffie’s picture

+++ b/core/tests/Drupal/KernelTests/Core/Database/SelectLeastTest.php
@@ -0,0 +1,33 @@
+      [['A', 'B', 'C', 'NULL'], 'A'],
+      [['A', 'B', 'C', 'NULL'], 'A'],

These are double. We do not need to test the same thing twice.

ravi.shankar’s picture

Status: Needs review » Needs work

The last submitted patch, 18: 3120892-18.patch, failed testing. View results
- codesniffer_fixes.patch Interdiff of automated coding standards fixes only.

Kumar Kundan’s picture

Assigned: Unassigned » Kumar Kundan
Kumar Kundan’s picture

Assigned: Kumar Kundan » Unassigned
ravi.shankar’s picture

Status: Needs work » Needs review

Changing status to needs review as patch #18 passed the tests.

daffie’s picture

Status: Needs review » Needs work

Patch looks good. Almost RTBC.

  1. +++ b/core/tests/Drupal/KernelTests/Core/Database/SelectLeastTest.php
    @@ -0,0 +1,32 @@
    +      [['A', 'B', 'C', 'NULL', 'F', 0], 'A'],
    

    Can we remove the number zero fro the values array. The function LEAST is made for comparing items of the SAME kind.

  2. There are also a number of coding standard violation that need to be fixed. See: https://www.drupal.org/pift-ci-job/1775696
Kumar Kundan’s picture

Assigned: Unassigned » Kumar Kundan
daffie’s picture

#23.2 still needs to be addressed.

ravi.shankar’s picture

Here I have addressed #23.2.

Kumar Kundan’s picture

Assigned: Kumar Kundan » Unassigned
daffie’s picture

Status: Needs review » Reviewed & tested by the community

All code changes look good to me.
There is testing added for the added functionality.
For me it is RTBC.

jungle’s picture

+++ b/core/tests/Drupal/KernelTests/Core/Database/SelectLeastTest.php
@@ -0,0 +1,32 @@
+    $least = $this->connection->query("SELECT LEAST(:values[])", [':values[]' => $values])->fetchField();

One question, instead of using raw SQL query, is it possible using $this->connection->select() here?

daffie’s picture

One question, instead of using raw SQL query, is it possible using $this->connection->select() here?

That is something that can be done. In combination with ->addExpression('LEAST(1,2,3,4,5)', 'num'). Only I do not see how that will improve the testing coverage.

jungle’s picture

@daffie, thanks, just asking.

  • catch committed b5343b9 on 9.1.x
    Issue #3120892 by Kumar Kundan, ravi.shankar, julienjoye, daffie:...
catch’s picture

Status: Reviewed & tested by the community » Fixed

Committed b5343b9 and pushed to 9.1.x. Thanks!

Status: Fixed » Closed (fixed)

Automatically closed - issue fixed for 2 weeks with no activity.

Beakerboy’s picture

SQL Server does not support the LEAST operator, so there are now many core testing failures under sqlsrv.