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

CRM_Utils_SQL_* - Properly interpolate NULL values #14250

Merged
merged 1 commit into from
Jun 13, 2019

Conversation

totten
Copy link
Member

@totten totten commented May 15, 2019

Overview

The CRM_Utils_SQL_* classes are used to generate SQL expressions with
properly escaped data. However, in many cases, the values of NULL are
processed in a way which clearly doesn't work, e.g.

Before

When passing in a parameter with a NULL value, the parameter is not evaluated at all.

>>> CRM_Utils_SQL_Select::from('civicrm_contact')->where('do_not_email IS @foo', ['foo' => NULL])->toSQL()
=> """
   SELECT *\n
   FROM civicrm_contact\n
   WHERE (do_not_email IS @foo)\n
   """

Specifically:

  • CRM_Utils_SQL_Select - PHP NULL parameters are ignored (as if the value does not exist). No test coverage.
  • CRM_Utils_SQL_Delete - PHP NULL parameters are ignored (as if the value does not exist). No test coverage.
  • CRM_Utils_SQL_Insert - PHP NULL inputs are represented as SQL NULLs. No test coverage.

After

When passing in a parameter with a PHP NULL value, the parameter is encoded as a SQL NULL value.

>>> CRM_Utils_SQL_Select::from('civicrm_contact')->where('do_not_email IS @foo', ['foo' => NULL])->toSQL()
=> """
   SELECT *\n
   FROM civicrm_contact\n
   WHERE (do_not_email IS NULL)\n
   """

Specifically:

  • CRM_Utils_SQL_Select - PHP NULL inputs are represented as SQL NULLs. Has test coverage.
  • CRM_Utils_SQL_Delete - PHP NULL inputs are represented as SQL NULLs. Has test coverage.
  • CRM_Utils_SQL_Insert - PHP NULL inputs are represented as SQL NULLs. Has test coverage.

Comment

I'm generally hesitant about changing behavior like this. However, given
that the old behavior was clearly non-functional, I'm struggling to figure a
way where this would be problematic. Moreover, I'm a bit encouraged by the
fact that:

  • There are unit-tests specifically for these classes,
  • When grepping core for references to CRM_Utils_SQL_ , there are several
    matches involving APIv3 and ActionSchedules -- both of which have a long
    practice of unit-testing.
  • When grepping my local ext's, there are several matches for things in
    apiv4 and civicase - which also some test-coverage.

Overview
--------

The `CRM_Utils_SQL_*` classes are used to generate SQL expressions with
properly escaped data. However, in many cases, the values of `NULL` are
processed in a way which clearly doesn't work, e.g.

Before
------

When passing in a parameter with a `NULL` value, the parameter *is not evaluated at all*.

```
>>> CRM_Utils_SQL_Select::from('civicrm_contact')->where('do_not_email IS @foo', ['foo' => NULL])->toSQL()
=> """
   SELECT *\n
   FROM civicrm_contact\n
   WHERE (do_not_email IS @foo)\n
   """
```

Specifically:

* `CRM_Utils_SQL_Select` - PHP NULL parameters are ignored (as if the value does not exist). No test coverage.
* `CRM_Utils_SQL_Delete` - PHP NULL parameters are ignored (as if the value does not exist). No test coverage.
* `CRM_Utils_SQL_Insert` - PHP NULL inputs are represented as SQL NULLs. No test coverage.

After
-----

When passing in a parameter with a PHP `NULL` value, the parameter is encoded as a SQL `NULL` value.

```
>>> CRM_Utils_SQL_Select::from('civicrm_contact')->where('do_not_email IS @foo', ['foo' => NULL])->toSQL()
=> """
   SELECT *\n
   FROM civicrm_contact\n
   WHERE (do_not_email IS NULL)\n
   """
```

Specifically:

* `CRM_Utils_SQL_Select` - PHP NULL inputs are represented as SQL NULLs. Has test coverage.
* `CRM_Utils_SQL_Delete` - PHP NULL inputs are represented as SQL NULLs. Has test coverage.
* `CRM_Utils_SQL_Insert` - PHP NULL inputs are represented as SQL NULLs. Has test coverage.

Comment
-------

I'm generally hesitant about changing behavior like this.  However, given
that the old behavior was clearly non-functional, I'm struggling to figure a
way where this would be problematic. Moreover, I'm a bit encouraged by the
fact that:

* There are unit-tests specifically for these classes,
* When grepping core for references to `CRM_Utils_SQL_ `, there are several
  matches involving APIv3 and ActionSchedules -- both of which have a long
  practice of unit-testing.
* When grepping my local ext's, there are several matches for things in
  `apiv4` and `civicase` - which also some test-coverage.
@civibot
Copy link

civibot bot commented May 15, 2019

(Standard links)

@civibot civibot bot added the master label May 15, 2019
@seamuslee001
Copy link
Contributor

Jenkins test this please

@colemanw
Copy link
Member

One potential gotcha is that both Api3 and Api4 get actions are built on top of CRM_Utils_SQL_Select, and Api3 has always ignored anything in $params with a value of NULL. Skimming the Api3SelectQuery class I think it is actually skipping NULL values before passing them through to CRM_Utils_SQL_Select - if that's the case then all is well.

@totten
Copy link
Member Author

totten commented May 15, 2019

I think it is actually skipping NULL values before passing them through to CRM_Utils_SQL_Select - if that's the case then all is well.

Yup, that's what I'd expect -- if it tried to pass down a NULL param and do a substitution on it, then the query would look nonsensical (like WHERE (do_not_email IS @foo) above).

@JoeMurray
Copy link
Contributor

It would be appropriate to support WHERE (mytextfield IS NULL) as well as WHERE (mytextfield IS NOT NULL) in the API. Once this PR is merged, could that limitation be removed?

@colemanw
Copy link
Member

@JoeMurray the api can do that already by doing something like

$params['first_name'] = array('IS NOT NULL' => TRUE);

@eileenmcnaughton
Copy link
Contributor

@colemanw is this mergeable - I saw you'd looked at it so haven't dug in myself

@colemanw colemanw merged commit 4c27465 into civicrm:master Jun 13, 2019
@totten totten deleted the master-select-null branch June 13, 2019 20:32
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants