CRM_Utils_SQL - Add "syncInto()" helper
authorTim Otten <totten@civicrm.org>
Thu, 9 Jul 2020 05:52:29 +0000 (22:52 -0700)
committerTim Otten <totten@civicrm.org>
Thu, 9 Jul 2020 06:19:22 +0000 (23:19 -0700)
This is slightly more sugary variant of "INSERT INTO...SELECT...ON DUPLICATE UPDATE..." which requires
less boilerplate/duplication.

Before
------

```php
CRM_Utils_SQL_Select::from('foo_table')
  ->select(['foo_name', 'foo_value1', 'foo_value2'])
  ->insertInto('bar_table', ['bar_name', 'bar_output1', 'bar_output2'])
  ->onDuplicate(['bar_output1 = foo_value1', 'bar_output2 = foo_value2'])
```

After
------

```php
CRM_Utils_SQL_Select::from('foo_table')
  ->syncInto('bar_table', 'bar_name', [
    'bar_name' => 'foo_name',
    'bar_output1' => 'foo_value1',
    'bar_output2' => 'foo_value1',
  ])
```

CRM/Utils/SQL/Select.php
tests/phpunit/CRM/Utils/SQL/SelectTest.php

index 44dcf49dbd870919350473167d927982de7c0203..642d98ff2b8b7d82c4a53c086186c05632be7b7b 100644 (file)
@@ -374,6 +374,45 @@ class CRM_Utils_SQL_Select extends CRM_Utils_SQL_BaseParamQuery {
     return $this->insertInto($table, $fields);
   }
 
+  /**
+   * Take the results of the SELECT query and copy them into another
+   * table.
+   *
+   * If the same record already exists in the other table (based on
+   * primary-key or unique-key), then update the corresponding record.
+   *
+   * @param string $table
+   *   The table to write data into.
+   * @param array|string $keys
+   *   List of PK/unique fields
+   *   NOTE: This must match the unique-key that was declared in the schema.
+   * @param array $mapping
+   *   List of values to select and where to send them.
+   *
+   *   For example, consider:
+   *     ['relationship_id' => 'rel.id']
+   *
+   *   This would select the value of 'rel.id' and write to 'relationship_id'.
+   *
+   * @param null|array $args
+   *   Use NULL to skip interpolation; use an array of variables to enable.
+   * @return $this
+   */
+  public function syncInto($table, $keys, $mapping, $args = NULL) {
+    $keys = (array) $keys;
+
+    $this->select(array_values($mapping), $args);
+    $this->insertInto($table, array_keys($mapping));
+
+    foreach ($mapping as $intoColumn => $fromValue) {
+      if (!in_array($intoColumn, $keys)) {
+        $this->onDuplicate("$intoColumn = $fromValue", $args);
+      }
+    }
+
+    return $this;
+  }
+
   /**
    * @param array $fields
    *   The fields to fill in the other table (in order).
index f28d4ccbcf32362e9c107396478c4574c3089850..74270a94684f927854c3fa1f0c258745ae72edad 100644 (file)
@@ -334,6 +334,31 @@ class CRM_Utils_SQL_SelectTest extends CiviUnitTestCase {
     $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());
   }
 
+  public function testSyncInto_EarlyInterpolate() {
+    $select = CRM_Utils_SQL_Select::from('foo')
+      ->where('foo.whiz = 100')
+      ->syncInto('bar', ['name'], [
+        'name' => 'foo.name',
+        'first' => 'concat(foo.one, @suffix)',
+        'second' => 'foo.two',
+      ], [
+        '@suffix' => ' and more',
+      ]);
+    $this->assertLike('INSERT INTO bar (name, first, second) SELECT foo.name, concat(foo.one, " and more"), foo.two FROM foo WHERE (foo.whiz = 100) ON DUPLICATE KEY UPDATE first = concat(foo.one, " and more"), second = foo.two', $select->toSQL());
+  }
+
+  public function testSyncInto_LateInterpolate() {
+    $select = CRM_Utils_SQL_Select::from('foo')
+      ->where('foo.whiz = 100')
+      ->syncInto('bar', ['name'], [
+        'name' => 'foo.name',
+        'first' => 'concat(foo.one, @suffix)',
+        'second' => 'foo.two',
+      ])
+      ->param(['@suffix' => ' and on and on']);
+    $this->assertLike('INSERT INTO bar (name, first, second) SELECT foo.name, concat(foo.one, " and on and on"), foo.two FROM foo WHERE (foo.whiz = 100) ON DUPLICATE KEY UPDATE first = concat(foo.one, " and on and on"), second = foo.two', $select->toSQL());
+  }
+
   /**
    * @param $expected
    * @param $actual