From ffecc0bccfc1c27ae5e3784c1430f6e5077a161e Mon Sep 17 00:00:00 2001
From: Dave Long <dave@longwaveconsulting.com>
Date: Mon, 7 Oct 2024 12:45:42 +0100
Subject: [PATCH] Issue #2005626 by sukr_s, gold, jhedstrom, neelam_wadhwani,
 neslee canil pinto, jweowu, Mixologic, quietone, daffie, smustgrave,
 longwave, mondrake: Implement \Drupal\Core\Database\Query\Update::arguments

(cherry picked from commit ea261a9d1a58b736c08adf51f0c1306962acc396)
---
 .../lib/Drupal/Core/Database/Query/Update.php | 68 +++++++++++++------
 .../KernelTests/Core/Database/UpdateTest.php  | 26 +++++++
 2 files changed, 73 insertions(+), 21 deletions(-)

diff --git a/core/lib/Drupal/Core/Database/Query/Update.php b/core/lib/Drupal/Core/Database/Query/Update.php
index b10514c5b21b..6d8b924e19ed 100644
--- a/core/lib/Drupal/Core/Database/Query/Update.php
+++ b/core/lib/Drupal/Core/Database/Query/Update.php
@@ -120,27 +120,9 @@ public function expression($field, $expression, ?array $arguments = NULL) {
    *   actually didn't have to be updated because the values didn't change.
    */
   public function execute() {
-    // Expressions take priority over literal fields, so we process those first
-    // and remove any literal fields that conflict.
-    $fields = $this->fields;
-    $update_values = [];
-    foreach ($this->expressionFields as $field => $data) {
-      if (!empty($data['arguments'])) {
-        $update_values += $data['arguments'];
-      }
-      if ($data['expression'] instanceof SelectInterface) {
-        $data['expression']->compile($this->connection, $this);
-        $update_values += $data['expression']->arguments();
-      }
-      unset($fields[$field]);
-    }
 
-    // Because we filter $fields the same way here and in __toString(), the
-    // placeholders will all match up properly.
-    $max_placeholder = 0;
-    foreach ($fields as $value) {
-      $update_values[':db_update_placeholder_' . ($max_placeholder++)] = $value;
-    }
+    [$args, $update_values] = $this->getQueryArguments();
+    $update_values += $args;
 
     if (count($this->condition)) {
       $this->condition->compile($this->connection, $this);
@@ -182,8 +164,10 @@ public function __toString() {
     }
 
     $max_placeholder = 0;
+    [$args] = $this->getQueryArguments();
+    $placeholders = array_keys($args);
     foreach ($fields as $field => $value) {
-      $update_fields[] = $this->connection->escapeField($field) . '=:db_update_placeholder_' . ($max_placeholder++);
+      $update_fields[] = $this->connection->escapeField($field) . '=' . $placeholders[$max_placeholder++];
     }
 
     $query = $comments . 'UPDATE {' . $this->connection->escapeTable($this->table) . '} SET ' . implode(', ', $update_fields);
@@ -197,4 +181,46 @@ public function __toString() {
     return $query;
   }
 
+  /**
+   * {@inheritdoc}
+   */
+  public function arguments() {
+    [$args] = $this->getQueryArguments();
+    return $this->condition->arguments() + $args;
+  }
+
+  /**
+   * Returns the query arguments with placeholders mapped to their values.
+   *
+   * @return array
+   *   An array containing arguments and update values.
+   *   Both arguments and update values are associative array where the keys
+   *   are the placeholder names and the values are the placeholder values.
+   */
+  protected function getQueryArguments(): array {
+    // Expressions take priority over literal fields, so we process those first
+    // and remove any literal fields that conflict.
+    $fields = $this->fields;
+    $update_values = [];
+    foreach ($this->expressionFields as $field => $data) {
+      if (!empty($data['arguments'])) {
+        $update_values += $data['arguments'];
+      }
+      if ($data['expression'] instanceof SelectInterface) {
+        $data['expression']->compile($this->connection, $this);
+        $update_values += $data['expression']->arguments();
+      }
+      unset($fields[$field]);
+    }
+
+    // Because we filter $fields the same way here and in __toString(), the
+    // placeholders will all match up properly.
+    $max_placeholder = 0;
+    $args = [];
+    foreach ($fields as $value) {
+      $args[':db_update_placeholder_' . ($max_placeholder++)] = $value;
+    }
+    return [$args, $update_values];
+  }
+
 }
diff --git a/core/tests/Drupal/KernelTests/Core/Database/UpdateTest.php b/core/tests/Drupal/KernelTests/Core/Database/UpdateTest.php
index 260237c06b3c..52d74dbb02d4 100644
--- a/core/tests/Drupal/KernelTests/Core/Database/UpdateTest.php
+++ b/core/tests/Drupal/KernelTests/Core/Database/UpdateTest.php
@@ -172,4 +172,30 @@ public function testUpdateValueInSerial(): void {
       ->execute();
   }
 
+  /**
+   * Tests the Update::__toString() method.
+   */
+  public function testToString(): void {
+    // Prepare query for testing.
+    $query = $this->connection->update('test')
+      ->fields(['a' => 27, 'b' => 42])
+      ->condition('c', [1, 2], 'IN');
+
+    // Confirm placeholders are present.
+    $query_string = (string) $query;
+    $this->assertStringContainsString(':db_update_placeholder_0', $query_string);
+    $this->assertStringContainsString(':db_update_placeholder_1', $query_string);
+    $this->assertStringContainsString(':db_condition_placeholder_0', $query_string);
+    $this->assertStringContainsString(':db_condition_placeholder_1', $query_string);
+
+    // Test arguments.
+    $expected = [
+      ':db_update_placeholder_0' => 27,
+      ':db_update_placeholder_1' => 42,
+      ':db_condition_placeholder_0' => 1,
+      ':db_condition_placeholder_1' => 2,
+    ];
+    $this->assertEquals($expected, $query->arguments());
+  }
+
 }
-- 
GitLab