From fbf5eca62154026a4546acb249de53afb6ae6b95 Mon Sep 17 00:00:00 2001 From: Tim Otten Date: Wed, 8 Jul 2020 21:53:10 -0700 Subject: [PATCH] CRM_Utils_SQL - Add support for "INSERT INTO...SELECT...ON DUPLICATE UPDATE..." This is a query structure in which you want to build a SELECT query - and, using the results, perform a mix of INSERTs and UPDATEs. --- CRM/Utils/SQL/Select.php | 26 ++++++++++++++++++++++ tests/phpunit/CRM/Utils/SQL/SelectTest.php | 9 ++++++++ 2 files changed, 35 insertions(+) diff --git a/CRM/Utils/SQL/Select.php b/CRM/Utils/SQL/Select.php index 4aa6b7e516..44dcf49dbd 100644 --- a/CRM/Utils/SQL/Select.php +++ b/CRM/Utils/SQL/Select.php @@ -69,6 +69,7 @@ class CRM_Utils_SQL_Select extends CRM_Utils_SQL_BaseParamQuery { private $insertInto = NULL; private $insertVerb = 'INSERT INTO '; private $insertIntoFields = []; + private $onDuplicates = []; private $selects = []; private $from; private $joins = []; @@ -386,6 +387,22 @@ class CRM_Utils_SQL_Select extends CRM_Utils_SQL_BaseParamQuery { return $this; } + /** + * For INSERT INTO...SELECT...' queries, you may give an "ON DUPLICATE UPDATE" clause. + * + * @param string|array $exprs list of SQL expressions + * @param null|array $args use NULL to disable interpolation; use an array of variables to enable + * @return CRM_Utils_SQL_Select + */ + public function onDuplicate($exprs, $args = NULL) { + $exprs = (array) $exprs; + foreach ($exprs as $expr) { + $evaluatedExpr = $this->interpolate($expr, $args); + $this->onDuplicates[$evaluatedExpr] = $evaluatedExpr; + } + return $this; + } + /** * @param array|NULL $parts * List of fields to check (e.g. 'selects', 'joins'). @@ -463,6 +480,15 @@ class CRM_Utils_SQL_Select extends CRM_Utils_SQL_BaseParamQuery { $sql .= 'OFFSET ' . $this->offset . "\n"; } } + if ($this->onDuplicates) { + if ($this->insertVerb === 'INSERT INTO ') { + $sql .= ' ON DUPLICATE KEY UPDATE ' . implode(", ", $this->onDuplicates) . "\n"; + } + else { + throw new \Exception("The ON DUPLICATE clause and only be used with INSERT INTO queries."); + } + } + if ($this->mode === self::INTERPOLATE_OUTPUT) { $sql = $this->interpolate($sql, $this->params, self::INTERPOLATE_OUTPUT); } diff --git a/tests/phpunit/CRM/Utils/SQL/SelectTest.php b/tests/phpunit/CRM/Utils/SQL/SelectTest.php index c784919ec7..f28d4ccbcf 100644 --- a/tests/phpunit/CRM/Utils/SQL/SelectTest.php +++ b/tests/phpunit/CRM/Utils/SQL/SelectTest.php @@ -325,6 +325,15 @@ class CRM_Utils_SQL_SelectTest extends CiviUnitTestCase { $this->assertLike('INSERT INTO bar (first, second, third, fourth) SELECT fid, 1, fid, 1 FROM foo WHERE (zoo = 3) AND (aviary = 3) GROUP BY noodle, sauce', $select->toSQL()); } + public function testInsertInto_OnDuplicateUpdate() { + $select = CRM_Utils_SQL_Select::from('foo') + ->insertInto('bar', ['first', 'second', 'third']) + ->select(['foo.one', 'foo.two', 'foo.three']) + ->onDuplicate('second = twiddle(foo.two)') + ->onDuplicate('third = twiddle(foo.three)'); + $this->assertLike('INSERT INTO bar (first, second, third) SELECT foo.one, foo.two, foo.three FROM foo ON DUPLICATE KEY UPDATE second = twiddle(foo.two), third = twiddle(foo.three)', $select->toSQL()); + } + /** * @param $expected * @param $actual -- 2.25.1