CRM_Utils_SQL - Add support for "INSERT INTO...SELECT...ON DUPLICATE UPDATE..."
authorTim Otten <totten@civicrm.org>
Thu, 9 Jul 2020 04:53:10 +0000 (21:53 -0700)
committerTim Otten <totten@civicrm.org>
Thu, 9 Jul 2020 06:19:22 +0000 (23:19 -0700)
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
tests/phpunit/CRM/Utils/SQL/SelectTest.php

index 4aa6b7e5166974cd334df51fb6b1f27c0dd986b5..44dcf49dbd870919350473167d927982de7c0203 100644 (file)
@@ -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);
     }
index c784919ec7c0d84055af89170fd7904753d25dd7..f28d4ccbcf32362e9c107396478c4574c3089850 100644 (file)
@@ -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