From 9569c7453cf2a283d25acae3ca5fba79904ac474 Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Tue, 14 Jun 2016 11:09:19 +0200 Subject: [PATCH] [HttpFoundation] Use UPSERT for sessions stored in PgSql >= 9.5 --- .../Doctrine/HttpFoundation/DbalSessionHandler.php | 13 ++++++++----- .../Session/Storage/Handler/PdoSessionHandler.php | 13 ++++++++----- 2 files changed, 16 insertions(+), 10 deletions(-) diff --git a/src/Symfony/Bridge/Doctrine/HttpFoundation/DbalSessionHandler.php b/src/Symfony/Bridge/Doctrine/HttpFoundation/DbalSessionHandler.php index fd7dcff62c..c3570da7da 100644 --- a/src/Symfony/Bridge/Doctrine/HttpFoundation/DbalSessionHandler.php +++ b/src/Symfony/Bridge/Doctrine/HttpFoundation/DbalSessionHandler.php @@ -180,7 +180,7 @@ class DbalSessionHandler implements \SessionHandlerInterface $updateStmt->bindValue(':time', time(), \PDO::PARAM_INT); $updateStmt->execute(); - // When MERGE is not supported, like in Postgres, we have to use this approach that can result in + // When MERGE is not supported, like in Postgres < 9.5, we have to use this approach that can result in // duplicate key errors when the same session is written simultaneously. We can just catch such an // error and re-execute the update. This is similar to a serializable transaction with retry logic // on serialization failures but without the overhead and without possible false positives due to @@ -224,11 +224,11 @@ class DbalSessionHandler implements \SessionHandlerInterface { $platform = $this->con->getDatabasePlatform()->getName(); - switch ($platform) { - case 'mysql': + switch (true) { + case 'mysql' === $platform: return "INSERT INTO $this->table ($this->idCol, $this->dataCol, $this->timeCol) VALUES (:id, :data, :time) ". "ON DUPLICATE KEY UPDATE $this->dataCol = VALUES($this->dataCol), $this->timeCol = VALUES($this->timeCol)"; - case 'oracle': + case 'oracle' === $platform: // DUAL is Oracle specific dummy table return "MERGE INTO $this->table USING DUAL ON ($this->idCol = :id) ". "WHEN NOT MATCHED THEN INSERT ($this->idCol, $this->dataCol, $this->timeCol) VALUES (:id, :data, :time) ". @@ -239,8 +239,11 @@ class DbalSessionHandler implements \SessionHandlerInterface return "MERGE INTO $this->table WITH (HOLDLOCK) USING (SELECT 1 AS dummy) AS src ON ($this->idCol = :id) ". "WHEN NOT MATCHED THEN INSERT ($this->idCol, $this->dataCol, $this->timeCol) VALUES (:id, :data, :time) ". "WHEN MATCHED THEN UPDATE SET $this->dataCol = :data, $this->timeCol = :time;"; - case 'sqlite': + case 'sqlite' === $platform: return "INSERT OR REPLACE INTO $this->table ($this->idCol, $this->dataCol, $this->timeCol) VALUES (:id, :data, :time)"; + case 'postgresql' === $platform && version_compare($this->con->getServerVersion(), '9.5', '>='): + return "INSERT INTO $this->table ($this->idCol, $this->dataCol, $this->timeCol) VALUES (:id, :data, :time) ". + "ON CONFLICT ($this->idCol) DO UPDATE SET ($this->dataCol, $this->timeCol) = (:data, :time) WHERE $this->idCol = :id"; } } } diff --git a/src/Symfony/Component/HttpFoundation/Session/Storage/Handler/PdoSessionHandler.php b/src/Symfony/Component/HttpFoundation/Session/Storage/Handler/PdoSessionHandler.php index 48e81ee0f1..1aad67960a 100644 --- a/src/Symfony/Component/HttpFoundation/Session/Storage/Handler/PdoSessionHandler.php +++ b/src/Symfony/Component/HttpFoundation/Session/Storage/Handler/PdoSessionHandler.php @@ -347,7 +347,7 @@ class PdoSessionHandler implements \SessionHandlerInterface $updateStmt->bindValue(':time', time(), \PDO::PARAM_INT); $updateStmt->execute(); - // When MERGE is not supported, like in Postgres, we have to use this approach that can result in + // When MERGE is not supported, like in Postgres < 9.5, we have to use this approach that can result in // duplicate key errors when the same session is written simultaneously (given the LOCK_NONE behavior). // We can just catch such an error and re-execute the update. This is similar to a serializable // transaction with retry logic on serialization failures but without the overhead and without possible @@ -659,11 +659,11 @@ class PdoSessionHandler implements \SessionHandlerInterface */ private function getMergeSql() { - switch ($this->driver) { - case 'mysql': + switch (true) { + case 'mysql' === $this->driver: return "INSERT INTO $this->table ($this->idCol, $this->dataCol, $this->lifetimeCol, $this->timeCol) VALUES (:id, :data, :lifetime, :time) ". "ON DUPLICATE KEY UPDATE $this->dataCol = VALUES($this->dataCol), $this->lifetimeCol = VALUES($this->lifetimeCol), $this->timeCol = VALUES($this->timeCol)"; - case 'oci': + case 'oci' === $this->driver: // DUAL is Oracle specific dummy table return "MERGE INTO $this->table USING DUAL ON ($this->idCol = :id) ". "WHEN NOT MATCHED THEN INSERT ($this->idCol, $this->dataCol, $this->lifetimeCol, $this->timeCol) VALUES (:id, :data, :lifetime, :time) ". @@ -674,8 +674,11 @@ class PdoSessionHandler implements \SessionHandlerInterface return "MERGE INTO $this->table WITH (HOLDLOCK) USING (SELECT 1 AS dummy) AS src ON ($this->idCol = :id) ". "WHEN NOT MATCHED THEN INSERT ($this->idCol, $this->dataCol, $this->lifetimeCol, $this->timeCol) VALUES (:id, :data, :lifetime, :time) ". "WHEN MATCHED THEN UPDATE SET $this->dataCol = :data, $this->lifetimeCol = :lifetime, $this->timeCol = :time;"; - case 'sqlite': + case 'sqlite' === $this->driver: return "INSERT OR REPLACE INTO $this->table ($this->idCol, $this->dataCol, $this->lifetimeCol, $this->timeCol) VALUES (:id, :data, :lifetime, :time)"; + case 'pgsql' === $this->driver && version_compare($this->pdo->getAttribute(\PDO::ATTR_SERVER_VERSION), '9.5', '>='): + return "INSERT INTO $this->table ($this->idCol, $this->dataCol, $this->lifetimeCol, $this->timeCol) VALUES (:id, :data, :lifetime, :time) ". + "ON CONFLICT ($this->idCol) DO UPDATE SET ($this->dataCol, $this->lifetimeCol, $this->timeCol) = (:data, :lifetime, :time) WHERE $this->idCol = :id"; } }