Skip to content

DB_query_builder->_where_in() produce an db error if $values array is…#4186

Closed
j0inty wants to merge 1 commit into
bcit-ci:developfrom
j0inty:j0inty-patch-db_qb-where_in
Closed

DB_query_builder->_where_in() produce an db error if $values array is…#4186
j0inty wants to merge 1 commit into
bcit-ci:developfrom
j0inty:j0inty-patch-db_qb-where_in

Conversation

@j0inty
Copy link
Copy Markdown
Contributor

@j0inty j0inty commented Oct 22, 2015

Hi,

today I saw an database error coming up to me as a result of the DB_query_builder->_where_in() function.

The function only checks is the parameter $values !== NULL and is $values an array, but not the count of elements.

Error Number: 42601/7
ERROR: Syntaxerror at „)“ LINE 3: WHERE domain IN() ^
SELECT * FROM "domain" WHERE domain IN() AND "domain" NOT LIKE 'ALL'
Filename: F:/workspace/postfixadmin-ng/build/dev/application/models/Domains_model.php
Line Number: 56

… empty

Hi,

today I saw an database error coming up to me as a result of the DB_query_builder->_where_in() function.

The function only checks is the parameter $values !== NULL and is $values an array, but not the count of elements.

```
Error Number: 42601/7
ERROR: Syntaxerror at „)“ LINE 3: WHERE domain IN() ^
SELECT * FROM "domain" WHERE domain IN() AND "domain" NOT LIKE 'ALL'
Filename: F:/workspace/postfixadmin-ng/build/dev/application/models/Domains_model.php
Line Number: 56
```
@narfbg
Copy link
Copy Markdown
Contributor

narfbg commented Oct 23, 2015

Why would you pass an empty array to where_in()?

@ivantcholakov
Copy link
Copy Markdown
Contributor

Sample use-case: A search form with a multi-select control could produce such an empty array eventually, if a user has not been selected anything.

@j0inty
Copy link
Copy Markdown
Contributor Author

j0inty commented Oct 23, 2015

-----BEGIN PGP SIGNED MESSAGE-----
Hash: SHA256

Hi,

that was a situation in one of my scripts while I was develop on the
postfixadmin-ng project. The PA is my first project where I want to
test and check how can I use ActiveRecords/QueryBuilder and DBCache
for other projects where I'm involved and we use CI for it.

But that's not a reason to not fix this annoying bug in your Framework.

regards
Steffen

PS: I used my fixes since yesterday for the PA project and it works
like a charm. Have a nice weekend. ;)

Am 23.10.2015 um 13:09 schrieb Andrey Andreev:

Why would you pass an empty array to |where_in()|?

— Reply to this email directly or view it on GitHub
<#4186 (comment)
21>.

-----BEGIN PGP SIGNATURE-----
Version: GnuPG v2

iQIcBAEBCAAGBQJWKhfKAAoJEIyoWxU/fWMU2dAP/A9LtvzglUDxF83tDv0h7lWY
fq9D1Q7amPMAwPuI+hWejAWbJ2yNIDBm1Obq9YKgefCgmRBZLSa1SMfT9nX6R6xK
o2OEp2n2yKNcsK7FC2nzVAgJVC0j7ELVcaqufi8Ag6d1Zqm/5tCkw0EWCgPuumB3
M2zM0sV/7KQxNpJWi7B1UEZMVGK8QmEWNGlmBlp3pEHDegaPl99uSZOfPHZ1jisg
lQF+Qq2s+HszxuX6cAvXKMQnpDSSn5hweEKuVj0yLTH0WFuRys+Ar5FRAzfmVvAe
uZ6pOw+QrX+Ok9LP31oCqUh4pZ3D8h20MjSmwWHZD6SPEDB8i/T9YracVjC7S+Ji
zA3gWuBzrKHu4/xilAnlVMRF8SXvEpMiNiCRrnwnWwISUlCbQqay+979DgEcMudS
hd69dPhyaBi3YKorChesf1RgTOeH6X0Jxaqi8jMr5Urqi3A3POBBi30LHOaoS1tp
Jklx63XYjl36SeU2bCFqUicg9FPlD/6b7xH7nlnBxzbDfjhfnBVK10lIuHC2zEqL
kAKhxV+0qj6XDf0ftlzxsD7Ij1IOfE0/TeWCcu+sRxwQkUrd2v7yy6K//TfFRBaS
y0mtzRkrQcB+xko6W++vcux6AI8enJvERn0G3RhQHF0MsAcMNoy/RO9RnRR6orVo
ExWYlDZ5hm7zTpPvTvBM
=gYL1
-----END PGP SIGNATURE-----

@narfbg
Copy link
Copy Markdown
Contributor

narfbg commented Oct 23, 2015

@ivantcholakov That could imply using unvalidated user input ... deffinately not one in favor of accepting the patch.

@j0inty You're not at all answering my question with that.

And it may be annoying to you for whatever reasons, but it's not a bug ... not compared to what you're suggesting. If you were suggesting to explicitly reject empty inputs, that would be another story.

You're not supposed to pass an empty array to that function and silently discarding such calls is not doing you a favor. Quite the opposite, if you unintentionally pass an empty array to where_in(), the error would help you find a bug in your code.

@ivantcholakov
Copy link
Copy Markdown
Contributor

@narfbg

"...could produce such an empty array eventually..." - an empty array could be valid in the described case. Of course, if numeric values are expected in a non-empty array, the received array should be filtered, etc., but this is an outside topic.

The query builder for its own purpose is going to make this check (for an empty array) for me, it would not be necessary insertion of additional code at the concrete place. The suggestion gives convenience, especially for those that like method chaining.

@j0inty
Copy link
Copy Markdown
Contributor Author

j0inty commented Oct 23, 2015

-----BEGIN PGP SIGNED MESSAGE-----
Hash: SHA256

Hi,

the error would help you find a bug in your code.

No Question that it would help you out to find the empty array(). ;)

But I think that the QueryBuilder has to check the Input, too. ;) And
an emppty array for IN() can not be valid.

I my case where I found this problem for me was that I work with admin
accounts which are NOT responsible as admin for domains. In this case
the array is empty through an earlier process where the session got
intialized. Yes I set an empty array as $admin_domains and now when I
want's to get the domain list from the database where the admin is
responsible for I do it on the following way.

https://github.com/j0inty/postfixadmin-ng/blob/develop/src/application/m
odels/Domains_model.php#L47

As you can see I could previously check the $this->auth->admin_domains
array for count() > 0 but I expected at this point the where_in()
method simply return. At time it produce an invalid DB query and then
run this against the database.

regards
steffen

PS: I know, some kind of CS Fixer is missing at time. ;) ....

Am 23.10.2015 um 13:35 schrieb Andrey Andreev:

@ivantcholakov https://github.com/ivantcholakov That could imply
using unvalidated user input ... deffinately not one in favor of
accepting the patch.

@j0inty https://github.com/j0inty You're not at all answering my
question with that.

And it may be annoying to you for whatever reasons, but it's not a
bug ... not compared to what you're suggesting. If you were
suggesting to explicitly reject empty inputs, that would be another
story.

You're not supposed to pass an empty array to that function and
silently discarding such calls is not doing you a favor. Quite the
opposite, if you /unintentionally/ pass an empty array to
|where_in()|, the error would help you find a bug in your code.

— Reply to this email directly or view it on GitHub
<#4186 (comment)
82>.

-----BEGIN PGP SIGNATURE-----
Version: GnuPG v2

iQIcBAEBCAAGBQJWKiJXAAoJEIyoWxU/fWMUHi0QAIJ6K0uT//OuCsM+sLsKWZky
Rg+jQdACR4BSR9QD+5BhmF+z9JRlDum5cQUg3HbGbc4gGvemCNsvSABk5ZZntqBp
VSpfPKyVU9cq3aqfKClS2iNP0q9Qtv2TmJDv13Zn/fXUQDmSkj35/85mOkkS2nzK
p25qh+QeFyYi+trcVg+h4LVukjXXHlMEJElbWqHr1ClKXV4VIFrXem5V1AFpUroV
H5rUkVBklf/kuDoWMat/wNfaKhxIqyMgfMau3CicHvEvIf0gnRLo4K0/z7XdAtCt
qG1Z0iandaRGFpjO8PA9cKbIE9jewkhcA0pFaMtTDvGEFMVkJEkEH4kncDXt0Xuv
+lOQt3bd2JJfuLZdxi4F4KwSfu5XhdHZpDjjRvmiy76GXFU9mocu239hqo4UE9EJ
VnDCOHP/rn4mC2W4dgnzLiQl6J3gT0lffS4N+yegn4wBnKNV/jUezVPBFK2fPQxc
kK2TvQ4gFMD8mOUCNEqRjVGJR86jkIJE46gpQkWLsY1ggvrbNHADzfTp428JDQFt
DKta6IAIfY7GcJ5MAXDP/csY5qNvY/DNPoZlnFotg6LMo3pswgca+ryMpFuuZYeM
yxBXdv8kcb/g6bBUiO/KK0VKAGJqe7bQFAhogwRMnNKkGjn1jC17EH9rE1l64rvR
xj/Q3N3rnmmQheFw4c4c
=cH4T
-----END PGP SIGNATURE-----

@narfbg
Copy link
Copy Markdown
Contributor

narfbg commented Oct 23, 2015

@ivantcholakov

"...could produce such an empty array eventually..." - an empty array could be valid in the described case. Of course, if numeric values are expected in a non-empty array, the received array should be filtered, etc., but this is an outside topic.

In order to know whether fo "filter" (and this should be "validate", not "filter") or not, you have to make a check anyway. And since you have to do that, you can skip the where_in() call in case of an empty input.
Also, this is not limited to numeric inputs - if it's an input and not a hard-coded value, you always have to validate it.

@j0inty Here's the thing ...

But I think that the QueryBuilder has to check the Input, too. ;) And an emppty array for IN() can not be valid.

Should the Query Builder check the input? I can agree that it should.

But if SQL's IN() triggers an error in case of an empty output, why should CI do a different thing and silently discard it? So far, I haven't seen a convincing argument for that.

In your particular case, a simple empty() check is all that you may use and I don't believe that it's that inconvenient:

empty($admin_domain_names) OR $this->db->where_in('domain', $admin_domain_names);

This explicitness even makes your code more readable, because now everybody who looks at it will know that there's a possible case where there are no domains to filter by.

@mwhitneysdsu
Copy link
Copy Markdown
Contributor

In the submitted code, while you check for an empty array, you don't check whether $values just contains an empty string or a string containing only white space, either of which would most likely lead to the same error the change was attempting to fix.

@narfbg while I agree that _where_in() probably shouldn't silently ignore empty input for $values, I do wonder why $key and $values are optional inputs in the first place. The method silently ignores the _where_in() call if either is omitted, yet the behavior is different if you pass empty, but non-NULL, values. I didn't do an exhaustive search, but I did search the system directory and didn't find any internal calls to _where_in() which deliberately omitted either $key or $value, so they would have to be omitted by the developer calling query builder (unless I missed something).

I can see both sides of the argument. While I like to write very forgiving code at times, I don't like the idea of my database getting hammered with an unfiltered query (or data being exposed to the wrong user) because I forgot to check a variable before passing it to one of query builder's where methods.

I can see more potential for harm in receiving an unfiltered result set than an error, so I would lean more towards making the arguments required and erroring out on invalid input, when the method is called, rather than waiting for the database to return an error. Since changing the behavior in this way would probably be considered a BC break, I don't really think that's likely to happen in CI3, either.

@narfbg
Copy link
Copy Markdown
Contributor

narfbg commented Oct 23, 2015

@mwhitneysdsu I too have no idea why it accepts nulls and I did notice that, but didn't mention it just to avoid arguing over two different problems at the same time. So far, I'm thinking of rejecting this one and removing the null defaults in CI3.1.

@ivantcholakov
Copy link
Copy Markdown
Contributor

@narfbg

"But if SQL's IN() triggers an error in case of an empty output, why should CI do a different thing and silently discard it?"

Hm..., probably this behavior has been chosen to prevent somebody from dumping lots of data by a mistake.

@ivantcholakov
Copy link
Copy Markdown
Contributor

A possible alternative, if you like it:

    // Class CI_DB_query_builder
    /**
     * An empty method that keeps chaining, the parameter does the desired operation as a side-effect.
     *
     * Sample usage if you want to build the query using one PHP sentence:
     *
     * $to_who = array('male', 'female', 'child'); // Let us allow this to be an empty array too.
     *
     * $found_products = $this->db
     *     ->select('id, name')
     *     ->from('products')
     *     ->where('in_stock', 1)
     *     ->that(empty($to_who) ? null : $this->db->where_in('to_who', $to_who)) // Edit: where -> where_in, my bad.
     *     ->limit(20)
     *     ->order_by('price', 'asc')
     *     ->get()
     *     ->result_array();
     *
     * var_dump($found_products);
     *
     * @param   mixed   $expression     An expression for nesting context/scope.
     * @return  object                  Returns a reference to the created model instance.
     */
    public function that($expression = NULL)
    {
        return $this;
    }

@narfbg narfbg added this to the 3.1.0 milestone Nov 2, 2015
@DaneelTrevize
Copy link
Copy Markdown

DaneelTrevize commented Mar 19, 2018

Today I've also encountered this inconsistency in CI's Query Builder, in that it will silently generate invalid SQL containing "IN ()".
This occurs in _where_in(), line 802+ in /system/database/DB_query_builder.php for CI 3.1.17
As per @mwhitneysdsu 's comment, this behaviour is inconsistent with how it handles $values===NULL.

In order to make it consistent while also not producing invalid SQL or changing the logic of the produced SQL statements, I propose the following solution:
Based on the $not and $type arguments to this function, we can know whether the valid logically equivalent SQL should match all or no rows for an empty IN() set. Assuming empty($values):

For $type ==='AND ':
If $not is true, we can simply return $this; as would at least be consistent with the ===NULL cases, (as everything is not in the empty set) and it filters no additional rows.
Else $not is false, we should filter all rows for this where_in's clause group, (as nothing is in the empty set), thus rather than cause a SQL error as is the current behaviour, instead emit the condition WHERE TRUE = FALSE (or equivalent efficient standard SQL that fails to match any rows).

For $type ==='OR ' (which might want to be an else catch-all to the above ==='AND ' condition), we do the reverse:
If $not is false, emit WHERE TRUE = FALSE.
Else $not is true, return $this;

This fix prevents CI from producing invalid SQL for empty($values) IN()s, which makes it self-consitent with handling $values===NULL. Being fixed in the protected _where_in() function fixes it for the 4 public where_in(), where_not_in(), or_where_in() and _or_where_not_in() entry points.

P.S. CI should probably not be silently dropping _where_in() variations for NULL arguments, I'm not sure why it doesn't first log_message( 'error', ... ) in these cases? Should that be added, we'd only need 1 log_message() for this empty IN() handling, upon first finding empty($values) and before the 4 logically-equivalent SQL variations.

@narfbg
Copy link
Copy Markdown
Contributor

narfbg commented Mar 19, 2018

@DaneelTrevize Sorry, but no.

The issue is still open just as a reminder to drop the $parameter = NULL defaults in CI 3.2. As far as the invalid SQL goes, the only question is whether we leave it like that or trigger a PHP error (throw exception?).

@DaneelTrevize
Copy link
Copy Markdown

Being as you know that bad SQL is going to error, why would you not throw an exception beforehand instead and more cleanly isolate the issue sooner? There is no purpose to querying the DB when you already know what the outcome will be, other than taking more time & resources.

@narfbg
Copy link
Copy Markdown
Contributor

narfbg commented Mar 20, 2018

That's kind of a loaded question ... Almost any question that asks "why not" is.

It assumes that we have to do anything about that. Truth is both we don't really have to, and noone really thought of the possibility in the first place. And honestly, I don't think it's a problem of any kind.

Again, there's no valid reason to pass an empty array as the input, under no circumstances. If it ever happens it's an oversight on the part of the developer, and it's not a common thing either, so why should we bother with it? Even the argument that we shouldn't unnecessarily hit the DB is kinda silly, because it means we have to add at least one more function call for all cases, so if we're talking about overhead, that doesn't net us any benefits.
The one positive thing I can think about is better error reporting, yet strangely enough nobody has mentioned this yet.

There's also another answer though: because CI hasn't been built to do exceptions, yet. That's something we plan for 3.2, but to be consistent with the currently used style of coding, it would be a simple return FALSE, which doesn't even tell you that there was an error, so yet again - no benefit.

We probably will indeed throw an exception for this in 3.2, but even if we didn't, that really wouldn't be an actual problem for anybody.

@DaneelTrevize
Copy link
Copy Markdown

DaneelTrevize commented Mar 20, 2018

I'll admit I fed you enough rope to hang yourself with, as a quick way to assess how worthwhile this framework is long term.

I already mentioned error logging

CI should probably not be silently dropping _where_in() variations for NULL arguments, I'm not sure why it doesn't first log_message( 'error', ... ) in these cases?

Quibbling the performance overhead of adding an if( empty( $x ) ) screams of premature optimization.

Expressing no desire to hold this framework to a scalable or sustainable standard, such as ensuring all calls to the QB return cleanly (via succeeding, or logging errors[, or silent ignoring of inputs]), seems shortsighted.
You'd rather compel every user to duplicate the same edge case checking, or risk forgetting to, rather than put an if-empty-input-handle-consistently-as-per-null-input 1-liner fix in?
You just don't care that CI doesn't apply some entry-level set theory logic to handling an empty array input to a where clause, but instead generates guaranteed garbage SQL and then does nothing to help the user recover from it?

If you can't see the difference in value between it at least doing the consistent silent return false; and bombing out via SQL, I don't know what to say.

@narfbg
Copy link
Copy Markdown
Contributor

narfbg commented Mar 21, 2018

I'll admit I fed you enough rope to hang yourself with, as a quick way to assess how worthwhile this framework is long term.

Eh? I don't know what that means or what you're talking about.

I already mentioned error logging

CI should probably not be silently dropping _where_in() variations for NULL arguments, I'm not sure why it doesn't first log_message( 'error', ... ) in these cases?

Fair enough, you did mention error logging.

But I'm not talking about logging ... I'm talking about error reporting, in the sense that an exception would give you a better indication of what's wrong than an SQL error. And really, you came here with an elaborate plan for how to translate an empty array into valid SQL; that's certainly not suggesting what I meant.

Quibbling the performance overhead of adding an if( empty($x) ) screams of premature optimization.

I'm sorry, but that's a load of bullshit.

  1. That has nothing to do with premature optimizations, or optimizations at all.
  2. You're intentionally taking out of context a comparison I made:

Even the argument that we shouldn't unnecessarily hit the DB is kinda silly, because it means we have to add at least one more function call for all cases, so if we're talking about overhead, that doesn't net us any benefits.

What I'm saying is that if there's "time and resources" (as you called it) that you should care about, it's during valid code execution paths. It is ridiculous to care about that in the one odd case where you made a programming mistake.

Expressing no desire to hold this framework to a scalable or sustainable standard, such as ensuring all calls to the QB return cleanly (via succeeding, or logging errors[, or silent ignoring of inputs]), seems shortsighted.

Those are some fancy, but snarky words that you've used here, and written in quite an accusatory way. Did I do something to you that I don't know of?
I've been nitpicking on every little detail of every little contribution to this framework for years, and that has probably annoyed quite a few people, but every single one of them will tell you - I'm the last person you should accuse of no desire to hold code to a standard.

But putting that aside, the "standard" you're talking about is something I see very often - people wanting to hide errors, as if that solves all the problems. And that's a fallacy.

I'll say this yet again: we're talking about an error condition, and not one where the user made a mistake, but one where we have a programming error.

It is not ok to be silent in those cases. I agree that the error is ugly, but at least it tells you that you did something wrong - it helps you to debug the problem.

You'd rather compel every user to duplicate the same edge case checking, or risk forgetting to, rather than put an if-empty-input-handle-consistently-as-per-null-input 1-liner fix in?

Another loaded question ... It's your obligation as a developer to validate all user input, and yes I sure do want to compel you to do that.

Also, the default NULL input is crap that shouldn't have existed in the first place. I don't know if backi in the day the EllisLab guys had an internal rule to always have default parameter values or something (because I've seen and removed that a lot), but it's absolutely the wrong thing to try to be consistent with.

You just don't care that CI doesn't apply some entry-level set theory logic to handling an empty array input to a where clause, but instead generates guaranteed garbage SQL and then does nothing to help the user recover from it?

And more accusations encapsulated into yet another loaded question ...

Literally in your last sentence you advocated against "duplication", and now you're talking about recovery? You know damn well that if you wouldn't do simple validation on the input, you wouldn't check for a return value or an exception either.

And I'm repeating myself here for the countless time, but we're talking about a programming error. The recovery is you fixing your code.

Also, entry-level set theory logic? Wat?

If you can't see the difference in value between it at least doing the consistent silent return false; and bombing out via SQL, I don't know what to say.

I do see the difference in value - a silent return false; ("consistent" with legacy bad programming) lets your erroneous program continue execution to an unknown outcome, and potentially remain unnoticed forever; an SQL error gives you feedback about your own error, so you can fix it.

See, at first I thought you wanted an exception and as I've already said, I'd agree with that - gives you the feedback from the SQL error in a nicer way and allows for recovery. But now you insist on silently returning false?! That would be the absolutely worst choice and if you're at a loss of words (though it hardly seems that way), I guarantee you it's not because you know it all.

@DaneelTrevize
Copy link
Copy Markdown

DaneelTrevize commented Mar 21, 2018

TL;DR:
Please just fix CI in 3.x to not Query error on empty $values to _where_in(). I can't see how this isn't a bug that needs fixing, and that can be done in a point release.


an elaborate plan for how to translate an empty array into valid SQL

if( empty($values) )

_where_in() matches x rows IN NOT IN
AND none all
OR all none

Elaborate? No, more like

entry-level set theory logic


CodeIgniter offers Query Builder as a feature, which has a family of where_in() functions.
There must be some design/specification as to how they're intended to behave. If I've missed where that's hosted online, please point me to it.

  • If it's that they "try to generate SQL consistent with the given input values where possible, and otherwise cleanly return or throw exception" then there's a bug that they don't handle empty($values) according to this design, and the fix would be to restore consistency with how they handle $values==null by not using the value in the SQL and eventually cleanly returning.

  • If instead it's that they "try to generate SQL consistent with the given input's equivalent Relational meaning, and otherwise cleanly return or throw exception" (as we're using SQL and an RDBMS), then there's 2 bugs:

    • that they don't correctly represent $values==null, because IN (NULL) means iterating through the set containing only NULL, and comparing the value(s) with the specified column/field of the selected rows will always be UNKNOWN and filter out all those rows because WHERE only returns on TRUE comparison results. Instead SQL that alway filters the rows should be generated, such as WHERE FALSE;
    • and that they don't correctly represent the empty set in equivalent valid SQL that matches all/none as per the simple truth table above. Again for this case, the generated SQL should be equiv. to:
empty() IN NOT IN
AND WHERE FALSE WHERE TRUE
OR WHERE TRUE WHERE FALSE

Whether the functions are designed to always just return silently, or additionally sometimes log errors, or throw exceptions, is a different issue than what they're actually trying/designed to do with the given inputs in the first place.
You must handle the input one way or the other (else the argument should be removed), thus you must make some simple assessment of it (.e.g. test if null/empty), or write code around some actually valid assumption w.r.t. SQL conformity if you generate SQL containing it for all possible values it could be.

And for the record, I'm of the opinion that an empty array should be handled as per the relational-equiv. SQL, and that null $values could either throw invalid argument exception, for not being a set/array (a array containing just 1 null value would be valid input), or would result in the SQL that filters all rows( WHERE FALSE).


W.r.t. code duplication by CI users, and the actual value of using CI/any framework at all vs simply reimplementing pieces of it every they time they're needed, I will reference Postel's law from the TCP specification:

the robustness principle: be conservative in what you send, be liberal in what you accept.

If a framework offer a guarantee of consistency, e.g. always success or fails but regardless does return from the function call & return control to the user, then the user has the choice & responsibility to handle inputs & outputs or not. They are not forced to check outputs & return values but the framework can make that clearly the user's fault if they do not.
Additionally, the obvious input checks can be built into the framework where they will be thoroughly tested.

But if the framework can't offer any guarantee that it won't blow up, emit arbitrary SQL & end the user's control via an emitted query error, then every sane user is forced to try duplicate the obvious input checks in each of their codebases (which some will omit/get wrong), probably by defining some wrapper layer that would be better suited being an official part of the framework specification in the first place & folded back into the input validation of these functions.
And without that, the users are still no better off w.r.t. any guarantees of output & work done, calling into question the point of using the framework at all.

@mwhitneysdsu
Copy link
Copy Markdown
Contributor

I don't want to jump in too far on this, since I haven't been around or using CI much, but I keep getting notified and wanted to drop a gentle reminder that just slapping an empty() check on there is probably going to cause more problems than it solves. From the PHP manual:

The following values are considered to be empty:
"" (an empty string)
0 (0 as an integer)
0.0 (0 as a float)
"0" (0 as a string)
NULL
FALSE
array() (an empty array)

So, you're probably looking at something more along the lines of:
if ($key === NULL OR $values === NULL OR (is_array($values) AND empty($values)))

If you want to argue that the other empty values shouldn't be permitted, either, you could save yourselves the extra code and just knock one of the equal signs off the $values === NULL comparison and add a comment to explain the variance from the code style guidelines.

The code is so far removed from the original design decisions at this point that it's primarily a question of what makes sense for continued maintenance and whether similar functionality should change in CI 4.

Finally, any appeal to Postel's Law in PHP-land needs a link to The Harmful Consequences of the Robustness Principle.

@DaneelTrevize
Copy link
Copy Markdown

DaneelTrevize commented Mar 21, 2018

Or just type hint the argument as array $values, then you're back to just needing to test empty().
Though I'd already said the proposed empty test was to follow the existing ===NULL and is_array() lines if they're kept.


Ok, Postel's was something of an appeal to authority, although that rebuttal seems to focus on related problems with divergence of actual network protocol implementations.

  1. An Alternative Conclusion
    The robustness principle is best suited to safeguarding against flaws
    in a specification that is intended to remain unchanged for an
    extended period of time. Indeed, in the face of divergent
    interpretations of an immutable specification, the only hope for an
    implementation to remain interoperable is to be tolerant of
    differences in interpretation and occasional outright implementation
    errors.

In our context of CI & QB, I'd say that means it should expect to handle an empty $values, even if that means silently ignoring it, rather than producing SQL that errors hard.

However:
If you're holding the position that the users should be the ones trusted with checking their inputs, then they should equally be trusted with making the functions call at all, such that if they have indeed called _where_in() with an empty $values, believe that's what they intended and build the valid query that represents it in SQL.
Then they can find value in the framework to keep all their code consistently styled with the succinct chaining pattern, and know that each part of it is mapped to the valid SQL equivalent. Rather than have to mentally manage that some parts are more abstract/robust than others, or even a single part varies depending upon input.

@mwhitneysdsu
Copy link
Copy Markdown
Contributor

Or just type hint the argument as array $values, then you're back to just needing to test empty().
Though I'd already said the proposed empty test was to follow the existing ===NULL and
is_array() lines if they're kept.

I guess I missed that part.

As an added bonus, if you declare the type of $values as an array, you can remove the creation of an array from $values as well. I wonder how many people are using the various where_in methods with non-array values...

Regarding Postel's, I was trying to avoid specifics and go to the generalized point that it can cause problems if extended far enough.

We probably all agree that it might be nice if something less bad happened, and maybe there could be something good that could happen, but the amount of change that might actually be permitted in this method is likely very different if you're talking about CI 3.1.x vs. 3.2 vs. 4.0.

It's also pretty pointless to talk about whether people can find value in the framework without a change when some of the code we're discussing is over 10 years old and the issue itself is a couple years old.

@narfbg
Copy link
Copy Markdown
Contributor

narfbg commented Mar 26, 2018

Sorry that I won't be addressing everything that was written, but it's not worth it for a minor issue like this. I'll just say this:

  • An empty array was never meant to be valid input and somehow translating it into valid SQL is out of the question.
  • Regardless of what we do, I would still expect (even demand) that everybody validates their inputs - it is simply wrong not to do that, even if you call it duplication.
  • return FALSE is a bad idea and adding an exception in 3.1.x would beg the question why a thousand other things don't trigger an exception, so 3.1.x stays as it is. Call it a bug if you wish; I would say it's a rare good one.
  • I like the idea of an array $values parameter type-hint and we'll do that in 3.2, along with removal of default parameter values and an exception in case of an empty array.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants