From 201fd312a9c822df6fb8ecb6205cc77047d17b43 Mon Sep 17 00:00:00 2001
From: catch <catch@35733.no-reply.drupal.org>
Date: Mon, 27 Nov 2023 11:37:40 +0000
Subject: [PATCH] Issue #3181013 by Driskell, alexpott, ericgsmith, Pan Lee,
 smustgrave, Kristen Pol, mxr576: Faulty permanent config cache has been set
 to the cache backend on failed sql server connection

---
 .../Drupal/Core/Config/DatabaseStorage.php    | 36 ++++++--
 .../Config/Storage/DatabaseStorageTest.php    | 84 +++++++++++++++++++
 2 files changed, 114 insertions(+), 6 deletions(-)

diff --git a/core/lib/Drupal/Core/Config/DatabaseStorage.php b/core/lib/Drupal/Core/Config/DatabaseStorage.php
index c40e70ee224c..4430298da39e 100644
--- a/core/lib/Drupal/Core/Config/DatabaseStorage.php
+++ b/core/lib/Drupal/Core/Config/DatabaseStorage.php
@@ -72,8 +72,11 @@ public function exists($name) {
       ], $this->options)->fetchField();
     }
     catch (\Exception $e) {
-      // If we attempt a read without actually having the database or the table
-      // available, just return FALSE so the caller can handle it.
+      if ($this->connection->schema()->tableExists($this->table)) {
+        throw $e;
+      }
+      // If we attempt a read without actually having the table available,
+      // return false so the caller can handle it.
       return FALSE;
     }
   }
@@ -90,8 +93,11 @@ public function read($name) {
       }
     }
     catch (\Exception $e) {
-      // If we attempt a read without actually having the database or the table
-      // available, just return FALSE so the caller can handle it.
+      if ($this->connection->schema()->tableExists($this->table)) {
+        throw $e;
+      }
+      // If we attempt a read without actually having the table available,
+      // return false so the caller can handle it.
     }
     return $data;
   }
@@ -108,8 +114,11 @@ public function readMultiple(array $names) {
       }
     }
     catch (\Exception $e) {
-      // If we attempt a read without actually having the database or the table
-      // available, just return an empty array so the caller can handle it.
+      if ($this->connection->schema()->tableExists($this->table)) {
+        throw $e;
+      }
+      // If we attempt a read without actually having the table available,
+      // return an empty array so the caller can handle it.
     }
     return $list;
   }
@@ -277,6 +286,11 @@ public function listAll($prefix = '') {
       return $query->execute()->fetchCol();
     }
     catch (\Exception $e) {
+      if ($this->connection->schema()->tableExists($this->table)) {
+        throw $e;
+      }
+      // If we attempt a read without actually having the table available,
+      // return an empty array so the caller can handle it.
       return [];
     }
   }
@@ -295,6 +309,11 @@ public function deleteAll($prefix = '') {
         ->execute();
     }
     catch (\Exception $e) {
+      if ($this->connection->schema()->tableExists($this->table)) {
+        throw $e;
+      }
+      // If we attempt a delete without actually having the table available,
+      // return false so the caller can handle it.
       return FALSE;
     }
   }
@@ -328,6 +347,11 @@ public function getAllCollectionNames() {
       ])->fetchCol();
     }
     catch (\Exception $e) {
+      if ($this->connection->schema()->tableExists($this->table)) {
+        throw $e;
+      }
+      // If we attempt a read without actually having the table available,
+      // return an empty array so the caller can handle it.
       return [];
     }
   }
diff --git a/core/tests/Drupal/KernelTests/Core/Config/Storage/DatabaseStorageTest.php b/core/tests/Drupal/KernelTests/Core/Config/Storage/DatabaseStorageTest.php
index 11e827bf73a4..5f6e787c41d0 100644
--- a/core/tests/Drupal/KernelTests/Core/Config/Storage/DatabaseStorageTest.php
+++ b/core/tests/Drupal/KernelTests/Core/Config/Storage/DatabaseStorageTest.php
@@ -4,6 +4,7 @@
 
 use Drupal\Core\Config\DatabaseStorage;
 use Drupal\Core\Database\Database;
+use Drupal\Core\Database\DatabaseExceptionWrapper;
 
 /**
  * Tests DatabaseStorage operations.
@@ -39,4 +40,87 @@ protected function delete($name) {
     Database::getConnection()->delete('config')->condition('name', $name)->execute();
   }
 
+  /**
+   * Tests that operations throw exceptions if the query fails.
+   */
+  public function testExceptionIsThrownIfQueryFails() {
+    $connection = Database::getConnection();
+    if ($connection->databaseType() === 'sqlite') {
+      // See: https://www.drupal.org/project/drupal/issues/3349286
+      $this->markTestSkipped('SQLite cannot allow detection of exceptions due to double quoting.');
+      return;
+    }
+
+    Database::getConnection()->schema()->dropTable('config');
+    // In order to simulate database issue create a table with an incorrect
+    // specification.
+    $table_specification = [
+      'fields' => [
+        'id'  => [
+          'type' => 'int',
+          'default' => NULL,
+        ],
+      ],
+    ];
+    Database::getConnection()->schema()->createTable('config', $table_specification);
+
+    try {
+      $this->storage->exists('config.settings');
+      $this->fail('Expected exception not thrown from exists()');
+    }
+    catch (DatabaseExceptionWrapper $e) {
+      // Exception was expected
+    }
+
+    try {
+      $this->storage->read('config.settings');
+      $this->fail('Expected exception not thrown from read()');
+    }
+    catch (DatabaseExceptionWrapper $e) {
+      // Exception was expected
+    }
+
+    try {
+      $this->storage->readMultiple(['config.settings', 'config.settings2']);
+      $this->fail('Expected exception not thrown from readMultiple()');
+    }
+    catch (DatabaseExceptionWrapper $e) {
+      // Exception was expected
+    }
+
+    try {
+      $this->storage->write('config.settings', ['data' => '']);
+      $this->fail('Expected exception not thrown from deleteAll()');
+    }
+    catch (DatabaseExceptionWrapper $e) {
+      // Exception was expected
+    }
+
+    try {
+      $this->storage->listAll();
+      $this->fail('Expected exception not thrown from listAll()');
+    }
+    catch (DatabaseExceptionWrapper $e) {
+      // Exception was expected
+    }
+
+    try {
+      $this->storage->deleteAll();
+      $this->fail('Expected exception not thrown from deleteAll()');
+    }
+    catch (DatabaseExceptionWrapper $e) {
+      // Exception was expected
+    }
+
+    try {
+      $this->storage->getAllCollectionNames();
+      $this->fail('Expected exception not thrown from getAllCollectionNames()');
+    }
+    catch (DatabaseExceptionWrapper $e) {
+      // Exception was expected
+    }
+
+    $this->assertTrue(TRUE);
+  }
+
 }
-- 
GitLab