Browse Source

Add OUTPUT clause to SQLServer queries.

This functions like RETURNING in Postgres and gives us easy access to
sequence values on inserted rows. This helps address concurrency issues
that can arise when transaction isolation level is not high enough.

Refs #5104
Mark Story 11 years ago
parent
commit
015865e0fd

+ 18 - 0
src/Database/SqlserverCompiler.php

@@ -47,6 +47,24 @@ class SqlserverCompiler extends QueryCompiler {
 	];
 
 /**
+ * Generates the INSERT part of a SQL query
+ *
+ * To better handle concurrency and low transaction isolation levels,
+ * we also include an OUTPUT clause so we can ensure we get the inserted
+ * row's data back.
+ *
+ * @param array $parts The parts to build
+ * @param \Cake\Database\Query $query The query that is being compiled
+ * @param \Cake\Database\ValueBinder $generator the placeholder generator to be used in expressions
+ * @return string
+ */
+	protected function _buildInsertPart($parts, $query, $generator) {
+		$table = $parts[0];
+		$columns = $this->_stringifyExpressions($parts[1], $generator);
+		return sprintf('INSERT INTO %s (%s) OUTPUT INSERTED.*', $table, implode(', ', $columns));
+	}
+
+/**
  * Generates the LIMIT part of a SQL query
  *
  * @param int $limit the limit clause

+ 28 - 0
tests/TestCase/Database/Driver/SqlserverTest.php

@@ -210,4 +210,32 @@ class SqlserverTest extends TestCase {
 		$this->assertEquals($expected, $query->sql());
 	}
 
+/**
+ * Test that insert queries have results available to them.
+ *
+ * @return void
+ */
+	public function testInsertUsesOutput() {
+		$driver = $this->getMock(
+			'Cake\Database\Driver\Sqlserver',
+			['_connect', 'connection'],
+			[[]]
+		);
+		$connection = $this->getMock(
+			'\Cake\Database\Connection',
+			['connect', 'driver'],
+			[['log' => false]]
+		);
+		$connection
+			->expects($this->any())
+			->method('driver')
+			->will($this->returnValue($driver));
+		$query = new \Cake\Database\Query($connection);
+		$query->insert(['title'])
+			->into('articles')
+			->values(['title' => 'A new article']);
+		$expected = 'INSERT INTO articles (title) OUTPUT INSERTED.* VALUES (:c0)';
+		$this->assertEquals($expected, $query->sql());
+	}
+
 }