Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Database] modify the _implodeWithKeys() function to allow IS NULL in query #3551

Merged

Conversation

PapillonMcGill
Copy link
Contributor

@PapillonMcGill PapillonMcGill commented Mar 16, 2018

This pull request let IS NULL value in the WHERE clause. It convert null value in $param array to IS NULL in the SQL query.

required for an upcoming PR

Step to test.
create a test file to call implodeWithKeys directly with an associative array containing NULL as a value as no code currently in Loris do it and ensure that the query generated by the prepared statement contain and "IS NULL" for that value. For that, you will need to rename the _implodeWithKeys to implodeWithKeys and make it public.

if ($item===null || $item==='') {
if ($item===null ) {
$output[] = "`$key` IS NULL";
} elseif ( $item==='') {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This seems like it deserves unit tests to make sure it doesn't cause any regressions

@ZainVirani ZainVirani added the Passed manual tests PR has been successfully tested by at least one peer label Mar 19, 2018
@@ -978,7 +978,9 @@ class Database
}

foreach ($dataArray as $key => $item ) {
if ($item===null || $item==='') {
if ($item===null) {
Copy link
Collaborator

@ridz1208 ridz1208 Mar 19, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@driusan correct me if i'm wrong but isn't this function also used to build the $set array for UPDATE statements ?

in that case it would generate UPDATE table_name SET variable1=5, variable 2 IS NULL, variable3...

In which case this is highly non-backwards compatible and can lead to some errors

Copy link
Contributor Author

@PapillonMcGill PapillonMcGill Mar 20, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@ridz1208 @driusan @xlecours You are right about that. It should not be the case now as it was just not permitted before.
Does adding an optional parameter for NULL value, link = null with possible value 'null' default, 'IS' and 'IS NOT'.
This should be an array of the same size of $param as it is possible that some parameter need to be IS NULL while other migth be IS NOT NULL.

This way we will fix it for every possible situation.

Copy link
Collaborator

@driusan driusan Mar 20, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@PapillonMcGill think @ridz1208 is talking about the SET part an UPDATE query, not the WHERE part. The SET part never needs "IS NULL/IS NOT NULL" while the WHERE part always does.. shouldn't a single extra bool parameter be sufficient?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@driusan Yes, I could add a parameter $clause = 'set' by default and allow 'where', but if in a where clause we later need IS NOT NULL instead of IS NULL, we will still be stuck. So with an array (NULL by default), we could add for each parameter where we need a comparison with null to be either IS NULL or IS NOT NULL as required and for those parameter where it is not a comparison with null or it is in a SET clause, leave that parameter as null and '=' will be used.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we should have an _implodeSetWithKeys() and an _implodeWhereWithKeys()

Copy link
Collaborator

@driusan driusan Mar 21, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There's already 2 calls (one for set, and one for where), so you still don't need an array (but @ridz1208's proposal of 2 private functions with different names also makes more sense than an extra param to me..)... but now that I'm looking at the code, the update path is only using implodeWithKeys for printing the query. It uses _implodeAsPrepared for the actual query that it runs.

(Delete should also be using the prepared variant, I'm not sure why it isn't.)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@driusan using _implodeAsPrepared() will be hard for "IS NULL" or "IS NOT NULL" as PDO do not support that in prepared statements.
So I will have to stick with _implodeWithKeys(). But adding a variable $clause = 'set' in the function definition will not solve the future possibility of "IS NOT NULL". Unless we use a dirty trick like using PHP_EOL constant to convert to IS NOT NULL but that might induce other problem.

@ridz1208 ridz1208 removed the Passed manual tests PR has been successfully tested by at least one peer label Mar 20, 2018
@PapillonMcGill PapillonMcGill added State: Needs work PR awaiting additional work by the author to proceed and removed State: Needs work PR awaiting additional work by the author to proceed labels Apr 9, 2018
*
* @return string the string containing the imploded array
* @access private
*/
function _implodeWithKeys($glue, $dataArray)
function _implodeWithKeys($glue, $dataArray, $clause = 'set_')
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since $clause seems to be used as a boolean, I'd be in favour of naming it $isSetClause with value true for set clauses and false otherwise.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is already a variable used as "set_" elsewhere in that module. So I used it the same way. It is also more clear to a humain than 0 or 1, no need to put comment. Of course binary would be (a little bit) faster at execution

if ($exec_vals !== null) {
$exec_vals["$prefix$varname"] = $item;
$varname = str_replace("%", "_percent_", $key);
if (is_null($item) && $prefix == "where_") {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nitpick: maybe rewrite this as

if (is_null($item)) {
    $output[] = $prefix == 'where_'
        ? "`$key` IS NULL" : "`$key` = NULL";
} else {
...

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

would effectively be more concise

@ridz1208 ridz1208 added the State: Needs work PR awaiting additional work by the author to proceed label Apr 25, 2018
@PapillonMcGill PapillonMcGill removed the State: Needs work PR awaiting additional work by the author to proceed label Apr 30, 2018
@PapillonMcGill
Copy link
Contributor Author

@nicolasbrossard Since I can't implement the $clause variable as you requested without lots of modification to existing code, Could you re-review or dismiss your request to change $clause for a boolean variable?

@PapillonMcGill
Copy link
Contributor Author

@nicolasbrossard Could you re-review? see previous comment.

in IS NULL in the SQL query

minor lint fix

addition of unit test

modify to show it ONLY delete where field IS NULL

modified _implodeAsPrepared to support 'IS NULL' and '= NULL' as _implodeWithKeys
modified _implodeWithKeys to correctly handle '= NULL' in SET clause

added 'set_' or 'where_' to existing call

bug fix

change if structure as requested

lint fix

restore deleted lines by some mistake

corrected varname
@zaliqarosli zaliqarosli added the State: Needs rebase PR that needs to be rebased to proceed (conflicts, wrong branch...) label Aug 13, 2018
@PapillonMcGill PapillonMcGill force-pushed the 2018-03-16_allow_delete_where_IS_NULL branch from 95c9bec to 8e59f02 Compare August 14, 2018 11:41
@PapillonMcGill PapillonMcGill removed the State: Needs rebase PR that needs to be rebased to proceed (conflicts, wrong branch...) label Aug 14, 2018
@zaliqarosli
Copy link
Contributor

@PapillonMcGill could you update the PR description to include steps for testing?

@PapillonMcGill
Copy link
Contributor Author

PapillonMcGill commented Aug 27, 2018

@zaliqarosli see the steps in the description.

Here is the test script I made. Unit test would fail I it require to temporarily make a private function public.

<?php

require_once __DIR__ . "/Database.class.inc";
require_once __DIR__ . "/testConfig.inc";

$array = array('VisitID' => '12', 'Label' => null, 'LegacyLabel' => 'mouain');

$db = new Database();
$db = Database::singleton($database, $username, $password, $host);

$setkey = $db->implodeWithKeys(',', $array, 'set_');
$wherekey = $db->implodeWithKeys(' AND ', $array, 'where_');

$empty = array();
$setPrep = $db->implodeAsPrepared(',', $array, $empty, 'set_');
$wherePrep = $db->implodeAsPrepared(' AND ', $array, $empty, 'where_');

    $prep   = $db->_PDO->prepare("UPDATE visit SET $setPrep WHERE $wherePrep");
    $result = $prep->execute($empty);
    $query = $prep->debugDumpParams();
    print($query."\n");

print("set:\n$setkey\n$setPrep\n\n");
print("where:\n$wherekey\n$wherePrep\n");
?>

Just need to put the DB credential in testConfig.inc file.

@johnsaigle johnsaigle added the Passed manual tests PR has been successfully tested by at least one peer label Nov 28, 2018
@johnsaigle
Copy link
Contributor

Tested using the supplied script and IS NULL appeared in the final print statements.

@ridz1208 ridz1208 self-assigned this Dec 18, 2018
zaliqarosli
zaliqarosli previously approved these changes Jan 7, 2019
@samirdas
Copy link
Contributor

samirdas commented Jan 11, 2019

@xlecours Please review this one more carefully than usual.

Copy link
Contributor

@xlecours xlecours left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The unittest need to be changed but the code is working.

@PapillonMcGill could you add test for insert and replace as well?

test/unittests/Database_Test.php Outdated Show resolved Hide resolved
test/unittests/Database_Test.php Show resolved Hide resolved
@xlecours xlecours added the State: Needs work PR awaiting additional work by the author to proceed label Jan 14, 2019
@driusan
Copy link
Collaborator

driusan commented Jan 14, 2019

@xlecours When you say the code is working, did you test it in all of:

  1. the update/insert case for columns
  2. the where case for select/update
  3. the showDatabaseQueries case for printing?

@PapillonMcGill
Copy link
Contributor Author

@xlecours I implemented your request for update but insert you just don't provide data for an attribute and the regular insert function just won't have to call this code.

@PapillonMcGill PapillonMcGill removed the State: Needs work PR awaiting additional work by the author to proceed label Jan 17, 2019
@PapillonMcGill
Copy link
Contributor Author

@xlecours Could you re-review?

@xlecours
Copy link
Contributor

@PapillonMcGill

... but insert you just don't provide data for an attribute and the regular insert function just won't have to call this code.

I think you are right only when default value is null.

@xlecours
Copy link
Contributor

@xlecours When you say the code is working, did you test it in all of:

  1. the update/insert case for columns
  2. the where case for select/update
  3. the showDatabaseQueries case for printing?

@driusan Yes.

<?php
require_once __DIR__ . "/vendor/autoload.php";

class DatabaseTest
{
    private $database;

    public function __construct()
    {
        $this->database = \Database::singleton(
            'dbname',
            'username',
            'password',
            'hostname',
            true
        );
    }

    public function testInsert() {
        $insert_result = $this->database->insert(
            'Project',
            array(
                'Name' => 'Bob 1',
                'recruitmentTarget' => null
            )
        );

        var_dump($insert_result);

        $select_result = $this->database->pselect('select * from Project', array());
        var_dump($select_result);
    }

    public function testUpdate() 
    {
        $update_result = $this->database->update(
            'Project',
            array(
                'Name' => null,
                'recruitmentTarget' => 100
            ),
            array(
                'recruitmentTarget' => null,
                'Name' => 'Bob 1'
            )
        );
        $select_result = $this->database->pselect('select * from Project', array());
        var_dump($select_result);
    }

    public function testReplace()
    {
        $projectid = $this->database->pselectOne('SELECT MAX(ProjectID) FROM Project', array());
        $replace_result = $this->database->replace(
            'Project',
            array(
                'ProjectID' => $projectid,
                'Name' => null,
                'recruitmentTarget' => null
            ),
        );
        $select_result = $this->database->pselect('select * from Project', array());
        var_dump($select_result);
    }

    public function testDelete()
    {
        $delete_result = $this->database->delete(
            'Project',
            array(
                'Name' => null,
                'recruitmentTarget' => null,
            )
        );
        $select_result = $this->database->pselect('select * from Project', array());
        var_dump($select_result);
    }
}

$t = new DatabaseTest();
$t->testInsert();
$t->testUpdate();
$t->testReplace();
$t->testDelete();

@PapillonMcGill
Copy link
Contributor Author

@xlecours rigth, I had not saw that realInsert function is using SET in stead of VALUES()

Copy link
Contributor

@xlecours xlecours left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you add a test for replace as well?
Even if the new tests are not specifically testing _implodeWithKeys and _implodeAsPrepared behaviors, I think that we should at least have tests for higher level functions. Thank you

test/unittests/Database_Test.php Outdated Show resolved Hide resolved
@ridz1208 ridz1208 removed their assignment Jan 24, 2019
@driusan driusan merged commit 373a8fe into aces:minor Jan 28, 2019
@ridz1208 ridz1208 added this to the 20.3.0 milestone Feb 2, 2019
kchatpar pushed a commit to kchatpar/Loris that referenced this pull request Apr 15, 2019
… query (aces#3551)

This modifies the Database class to allow null values to be used in WHERE clauses or as SET values.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Passed manual tests PR has been successfully tested by at least one peer
Projects
None yet
Development

Successfully merging this pull request may close these issues.

9 participants