Make Connection::expandArguments() public to allow array argument expansion outside of Connection::query()

Created on 22 March 2023, over 1 year ago
Updated 4 April 2023, over 1 year ago

Problem/Motivation

Various database systems support many more features than DBTNG possibly could. For example, MySQL supports UPDATE with JOINs. Previously, it was possible -- even if it were documented not to do it -- to run these via query by setting RETURN_AFFECTED. With the removal of this constant, it is no longer possible to run rowCount on a piece of string.

Well, at least not in a nice way because one still can run prepareStatement with allow row count set to TRUE. But expandArguments is protected so things get very ugly very fast. Either one needs to run reflection to make it public or needs to replicate the functionality of it.

Steps to reproduce

Try to run any UPDATE ... JOIN queries with Drupal 10.

Proposed resolution

Make expandArguments public.

Remaining tasks

User interface changes

API changes

Data model changes

Release notes snippet

๐Ÿ“Œ Task
Status

Needs work

Version

10.1 โœจ

Component
Databaseย  โ†’

Last updated 2 days ago

  • Maintained by
  • ๐Ÿ‡ณ๐Ÿ‡ฑNetherlands @daffie
Created by

๐Ÿ‡จ๐Ÿ‡ฆCanada Charlie ChX Negyesi ๐ŸCanada

Live updates comments and jobs are added and updated live.
Sign in to follow issues

Comments & Activities

  • Issue created by @Charlie ChX Negyesi
  • ๐Ÿ‡ฎ๐Ÿ‡นItaly mondrake ๐Ÿ‡ฎ๐Ÿ‡น

    Maybe we could just move the call to expandArguments to within prepareStatement instead of within query. It's protected and it's not called anywhere else within Connection.

    Also, make it public looks a viable alternative.

    It was made difficult to make a generic query return AFFECTED_ROWS to keep up with https://git.drupalcode.org/project/drupal/-/blob/10.1.x/core/lib/Drupal/...

  • ๐Ÿ‡จ๐Ÿ‡ฆCanada Charlie ChX Negyesi ๐ŸCanada

    Thanks! That's a great suggestion. If I might add, maybe make it optional? Add an "expand_arguments" option to prepareStatement $options? This way no surprises can happen.

  • Status changed to Needs review over 1 year ago
  • ๐Ÿ‡จ๐Ÿ‡ฆCanada Charlie ChX Negyesi ๐ŸCanada

    It would look like this.

  • Status changed to Needs work over 1 year ago
  • ๐Ÿ‡ฎ๐Ÿ‡นItaly mondrake ๐Ÿ‡ฎ๐Ÿ‡น

    So back in #2345451: Introduce a Connection::prepareStatement method allowing to pass options to the Statement, deprecate Connection::prepareQuery() and Connection::prepare() โ†’ I was already proposing to pass $args to prepareStatement, see my comment at #67. Now it seems we may have a good case to revise the decision to leave it out.

    If we pass $args to prepareStatement with a NULL default in the signature, we wonโ€™t even need to bother with the $options.

  • Status changed to Active over 1 year ago
  • ๐Ÿ‡จ๐Ÿ‡ฆCanada Charlie ChX Negyesi ๐ŸCanada

    When I tried to code this up and write the comment for this it makes it clear it is a side effect of calling prepareStatement which is usually not desired. Maybe let's get back to making expandArguments public?

  • ๐Ÿ‡ฎ๐Ÿ‡นItaly mondrake ๐Ÿ‡ฎ๐Ÿ‡น

    Yes, probably #6 is the best option.

    Conceptually, one 'prepares a statement' independently from its 'arguments', than 'executes' one or more times the 'prepared argument' with one or more sets of 'arguments' to obtain a 'result' in each execution. So the idea in #2 of mixing the arguments with the statement preparation is a break in the separation of concerns and therefore not a good idea in the first place.

    That said, this probably opens up a question of how currently 'reusable' is a statement prepared by query(), that calls expandArguments() on the arguments and modifies them. Trying to re-execute that with a new set of similarly formed arguments should(?) require to run expandArguments() on the additional sets indpendently. But that is probably not a case in core, a test would be needed. That would be a separate issue probably though.

  • Status changed to Needs review over 1 year ago
  • ๐Ÿ‡จ๐Ÿ‡ฆCanada Charlie ChX Negyesi ๐ŸCanada

    Certainly not the most complex core patch I've ever written.

  • ๐Ÿ‡จ๐Ÿ‡ฆCanada Charlie ChX Negyesi ๐ŸCanada

    Unrelated random testing failure.

  • ๐Ÿ‡ฎ๐Ÿ‡นItaly mondrake ๐Ÿ‡ฎ๐Ÿ‡น

    RTBC for me; what do you think @daffie?

  • Status changed to Needs work over 1 year ago
  • ๐Ÿ‡ณ๐Ÿ‡ฑNetherlands daffie

    I get that we cannot support all queries that are possible with SQL in our DBTNG. What I am worried about is that those complicated SQL queries will only work on a specific type of database. For instance: it works on MariaDB, mostly on MySQL and not on PostgreSQL and SQLite.

    Another way to fix this is to split the complicated SQL query up into multiple "simpler" queries. I have seen the example given by @chx. In that case we could split into a select query with a join to get the data and use the result to do the update queries. His example was also for a cron job and then it does not matter if they take a bit more time to run.

    I do not want to give a hard no, but I can imagine that in some cases having the ability to run complicated SQL queries is important.

    If we are going to do this we are going to need a CR, we also need testing for the "new" public method and I think we should also update the file database.api.php with an example.

  • ๐Ÿ‡ฎ๐Ÿ‡นItaly mondrake ๐Ÿ‡ฎ๐Ÿ‡น

    Maybe issue title and summary here should be reworked like "Make Connection::expandArguments() public to allow array argument expansion outside of Connection::query()". This would abstract away from @Charlie ChX Negyesi use case which IMHO should not be part of the discussion here.

    And yes, we need tests to make this method part of the public API - actually I tried to find test coverage for it and could not find it; it may be indirect but then it's very hidden. Such tests here would also address #7.

  • Status changed to Needs review over 1 year ago
  • ๐Ÿ‡จ๐Ÿ‡ฆCanada Charlie ChX Negyesi ๐ŸCanada

    I made a striking discovery when trying to write a test: this is static. Could be anywhere else even, has nothing to do with Connection at all. Once that realization is made, testing is easy.

  • ๐Ÿ‡จ๐Ÿ‡ฆCanada Charlie ChX Negyesi ๐ŸCanada
  • ๐Ÿ‡จ๐Ÿ‡ฆCanada Charlie ChX Negyesi ๐ŸCanada
  • ๐Ÿ‡จ๐Ÿ‡ฆCanada Charlie ChX Negyesi ๐ŸCanada

    Sorry I can't resist properly fixing the failing line.

  • ๐Ÿ‡จ๐Ÿ‡ฆCanada Charlie ChX Negyesi ๐ŸCanada

    Except phpcs doesn't recognize that...

  • Status changed to RTBC over 1 year ago
  • ๐Ÿ‡ฎ๐Ÿ‡นItaly mondrake ๐Ÿ‡ฎ๐Ÿ‡น

    Looks good to me; we have now test coverage for the method we did not have before.

  • Status changed to Needs work over 1 year ago
  • ๐Ÿ‡ณ๐Ÿ‡ฑNetherlands daffie

    We are adding a new method to the public API, therefor we need to add a change record. Can we put a good example in the CR with how to use this method in custom code.

  • ๐Ÿ‡จ๐Ÿ‡ฆCanada Charlie ChX Negyesi ๐ŸCanada

    Can we put a good example in the CR with how to use this method in custom code.

    I will be honest with you: no. There are no good, clean examples for this. Anything neat is handled by DBTNG all fine. Any usage of this goes against the documented best practices of not using query for UPDATE/INSERT/DELETE.

    But it is a necessary evil. I see no way out.

    Except for undeprecating RETURN_AFFECTED and let it fly under the radar.

  • Status changed to Needs review over 1 year ago
  • ๐Ÿ‡จ๐Ÿ‡ฆCanada Charlie ChX Negyesi ๐ŸCanada
  • Status changed to Needs work over 1 year ago
  • ๐Ÿ‡บ๐Ÿ‡ธUnited States smustgrave

    The issue summary mentions two approaches. Can it be highlighted the approach taken.

    Appears to be the function was made pubic can that be mentioned in the CR too please.

  • ๐Ÿ‡บ๐Ÿ‡ธUnited States smustgrave
  • ๐Ÿ‡จ๐Ÿ‡ฆCanada Charlie ChX Negyesi ๐ŸCanada
  • Status changed to Needs review over 1 year ago
  • ๐Ÿ‡จ๐Ÿ‡ฆCanada Charlie ChX Negyesi ๐ŸCanada
  • Status changed to RTBC over 1 year ago
  • ๐Ÿ‡ฎ๐Ÿ‡นItaly mondrake ๐Ÿ‡ฎ๐Ÿ‡น

    Thank you. Besides addressing the need in the IS, this also adds explicit tests for expandArguments(), which is good.

  • Status changed to Needs work over 1 year ago
  • ๐Ÿ‡ฆ๐Ÿ‡บAustralia larowlan ๐Ÿ‡ฆ๐Ÿ‡บ๐Ÿ.au GMT+10

    Not supporting a particular use case doesn't necessarily make this a bug, I think now the solution/approach has changed this is better classified as a task

    One of the fails above on PHP 8.2 relates to the changes here (see the sqlite run)

    https://www.drupal.org/pift-ci-job/2634061 โ†’

    Fine to self RTBC after that change.

    I spent some time reviewing the security implications of this and I can't see any issue.

    I'd like to see an additional test case covering the drupalgeddon fix, i.e. a case where the arguments are an array, the placeholder uses [], but the keys to the argument array are not numerically indexed, something like this

    
    
      /**
       * Tests the sanitizing of placeholders.
       */
      public function testExpandKeyedArguments(): void {
        $query = '(:x[])';
        $args = [':x[]' => [' where 1=1 OR ' => 1, ' nefarious ' => 2]];
        $modified = Connection::expandArguments($query, $args);
        $this->assertSame(TRUE, $modified);
        $this->assertSame('(:x__0, :x__1)', $query);
        $this->assertSame([':x__0' => 1, ':x__1' => 2], $args);
    }
    

    That little array_values in there is one of the most significant in Drupal's history

  • last update about 1 year ago
    Patch Failed to Apply
Production build 0.71.5 2024