From fbed84c84b56d6b79dafc9ff01d62785f2483fa5 Mon Sep 17 00:00:00 2001
From: Takayasu Oyama <t-oyama@colopl.co.jp>
Date: Mon, 3 Jun 2024 14:39:34 +0900
Subject: [PATCH] fix: Timestamp bound queries were not applied when in
 transaction (#213)

fix/staleness-read-on-tx
---
 CHANGELOG.md             |  5 +++++
 src/Connection.php       | 10 +++++++++-
 tests/ConnectionTest.php | 35 ++++++++++++++++++++++++++++++-----
 3 files changed, 44 insertions(+), 6 deletions(-)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index de0a9a7..180b646 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,3 +1,8 @@
+# v8.1.1 (2024-06-03)
+
+Fixed
+- Timestamp bound queries were not applied when in transaction (#213)
+
 # v8.1.0 (2024-05-21)
 
 Added
diff --git a/src/Connection.php b/src/Connection.php
index ed885cf..2f8da17 100644
--- a/src/Connection.php
+++ b/src/Connection.php
@@ -578,9 +578,17 @@ protected function executeQuery(string $query, array $bindings, array $options):
             $options['requestOptions']['requestTag'] = $tag;
         }
 
-        if ($transaction = $this->getCurrentTransaction()) {
+        $forceReadOnlyTransaction =
+            ($options['exactStaleness'] ?? false) ||
+            ($options['maxStaleness'] ?? false) ||
+            ($options['minReadTimestamp'] ?? false) ||
+            ($options['readTimestamp'] ?? false) ||
+            ($options['strong'] ?? false);
+
+        if (!$forceReadOnlyTransaction && $transaction = $this->getCurrentTransaction()) {
             return $transaction->execute($query, $options)->rows();
         }
+
         return $this->getSpannerDatabase()->execute($query, $options)->rows();
     }
 
diff --git a/tests/ConnectionTest.php b/tests/ConnectionTest.php
index 4e71ebb..16bd183 100644
--- a/tests/ConnectionTest.php
+++ b/tests/ConnectionTest.php
@@ -478,7 +478,7 @@ public function test_stale_reads(): void
         $this->assertNotEmpty($timestamp);
 
         $timestampBound = new StrongRead();
-        $rows = $conn->selectWithOptions("SELECT * FROM {$tableName} WHERE userID = ?", [$uuid], $timestampBound->transactionOptions());
+        $rows = $conn->selectWithOptions("SELECT * FROM {$tableName} WHERE userId = ?", [$uuid], $timestampBound->transactionOptions());
         $this->assertCount(1, $rows);
         $this->assertSame($uuid, $rows[0]['userId']);
         $this->assertSame('first', $rows[0]['name']);
@@ -486,26 +486,51 @@ public function test_stale_reads(): void
         $oldDatetime = Carbon::instance($timestamp->get())->subSecond();
 
         $timestampBound = new ReadTimestamp($oldDatetime);
-        $rows = $conn->selectWithOptions("SELECT * FROM {$tableName} WHERE userID = ?", [$uuid], $timestampBound->transactionOptions());
+        $rows = $conn->selectWithOptions("SELECT * FROM {$tableName} WHERE userId = ?", [$uuid], $timestampBound->transactionOptions());
         $this->assertEmpty($rows);
 
         $timestampBound = new ExactStaleness(10);
-        $rows = $conn->selectWithOptions("SELECT * FROM {$tableName} WHERE userID = ?", [$uuid], $timestampBound->transactionOptions());
+        $rows = $conn->selectWithOptions("SELECT * FROM {$tableName} WHERE userId = ?", [$uuid], $timestampBound->transactionOptions());
         $this->assertEmpty($rows);
 
         $timestampBound = new MaxStaleness(10);
-        $rows = $conn->selectWithOptions("SELECT * FROM {$tableName} WHERE userID = ?", [$uuid], $timestampBound->transactionOptions());
+        $rows = $conn->selectWithOptions("SELECT * FROM {$tableName} WHERE userId = ?", [$uuid], $timestampBound->transactionOptions());
         $this->assertCount(1, $rows);
         $this->assertSame($uuid, $rows[0]['userId']);
         $this->assertSame('first', $rows[0]['name']);
 
         $timestampBound = new MinReadTimestamp($oldDatetime);
-        $rows = $conn->selectWithOptions("SELECT * FROM {$tableName} WHERE userID = ?", [$uuid], $timestampBound->transactionOptions());
+        $rows = $conn->selectWithOptions("SELECT * FROM {$tableName} WHERE userId = ?", [$uuid], $timestampBound->transactionOptions());
         $this->assertCount(1, $rows);
         $this->assertSame($uuid, $rows[0]['userId']);
         $this->assertSame('first', $rows[0]['name']);
     }
 
+    public function test_stale_reads_in_transaction(): void
+    {
+        $conn = $this->getDefaultConnection();
+        $tableName = self::TABLE_NAME_USER;
+        $uuid = $this->generateUuid();
+
+        $conn->transaction(function(Connection $conn) use ($tableName, $uuid) {
+            $conn->insert("INSERT INTO {$tableName} (`userId`, `name`) VALUES ('{$uuid}', 'first')");
+
+            $oldDatetime = now()->subSecond();
+
+            $bounds = [
+                new StrongRead(),
+                new ExactStaleness(10),
+                new MaxStaleness(10),
+                new ReadTimestamp($oldDatetime),
+                new MinReadTimestamp($oldDatetime),
+            ];
+            foreach ($bounds as $timestampBound) {
+                $rows = $conn->selectWithOptions("SELECT * FROM {$tableName} WHERE userId = ?", [$uuid], $timestampBound->transactionOptions());
+                $this->assertEmpty($rows);
+            }
+        });
+    }
+
     public function testEventListenOrder(): void
     {
         $receivedEventClasses = [];