CRM_Utils_SQL_Insert
authorTim Otten <totten@civicrm.org>
Fri, 30 May 2014 22:51:43 +0000 (15:51 -0700)
committerTim Otten <totten@civicrm.org>
Wed, 4 Jun 2014 21:37:36 +0000 (14:37 -0700)
CRM/Utils/SQL/Insert.php [new file with mode: 0644]
tests/phpunit/CRM/Utils/SQL/InsertTest.php [new file with mode: 0644]

diff --git a/CRM/Utils/SQL/Insert.php b/CRM/Utils/SQL/Insert.php
new file mode 100644 (file)
index 0000000..e67457c
--- /dev/null
@@ -0,0 +1,120 @@
+<?php
+
+/**
+ * Dear God Why Do I Have To Write This (Dumb SQL Builder)
+ *
+ * Usage:
+ * $insert = CRM_Utils_SQL_Insert::into('mytable')
+ *  ->row(array('col1' => '1', 'col2' => '2' ))
+ *  ->row(array('col1' => '2b', 'col2' => '1b'));
+ * echo $insert->toSQL();
+ *
+ * Note: In MySQL, numeric values may be escaped. Except for NULL values,
+ * it's reasonable for us to simply escape all values by default -- without
+ * any knowledge of the underlying schema.
+ *
+ * Design principles:
+ *  - Portable
+ *    - No knowledge of the underlying SQL API (except for escaping -- CRM_Core_DAO::escapeString)
+ *    - No knowledge of the underlying data model
+ *    - Single file
+ *  - SQL clauses correspond to PHP functions ($select->where("foo_id=123"))
+ */
+class CRM_Utils_SQL_Insert {
+
+  /**
+   * @var string
+   */
+  private $table;
+
+  /**
+   * @var array
+   */
+  private $rows;
+
+  /**
+   * array<string> list of column names
+   */
+  private $columns;
+
+  /**
+   * Create a new INSERT query
+   *
+   * @param string $table table-name and optional alias
+   * @return CRM_Utils_SQL_Insert
+   */
+  public static function into($table) {
+    return new self($table);
+  }
+
+  /**
+   * Create a new SELECT query
+   *
+   * @param string $from table-name and optional alias
+   */
+  public function __construct($table) {
+    $this->table = $table;
+    $this->rows = array();
+  }
+
+  /**
+   * @param array $rows
+   * @return CRM_Utils_SQL_Insert
+   */
+  public function rows($rows) {
+    foreach ($rows as $row) {
+      $this->row($row);
+    }
+    return $this;
+  }
+
+  /**
+   * @param array $row
+   * @return CRM_Utils_SQL_Insert
+   * @throws CRM_Core_Exception
+   */
+  public function row($row) {
+    $columns = array_keys($row);
+    sort($columns);
+
+    if ($this->columns === NULL) {
+      $this->columns = $columns;
+    }
+    elseif ($this->columns != $columns) {
+      throw new CRM_Core_Exception("Inconsistent column names");
+    }
+
+    $escapedRow = array();
+    foreach ($columns as $column) {
+      $escapedRow[$column] = $this->escapeString($row[$column]);
+    }
+    $this->rows[] = $escapedRow;
+
+    return $this;
+  }
+
+  /**
+   * @param string|NULL $value
+   * @return string SQL expression, e.g. "it\'s great" (with-quotes) or NULL (without-quotes)
+   */
+  protected function escapeString($value) {
+    return $value === NULL ? 'NULL' : '"' . CRM_Core_DAO::escapeString($value) . '"';
+  }
+
+  /**
+   * @return string SQL statement
+   */
+  public function toSQL() {
+    $columns = "`" . implode('`,`', $this->columns) . "`";
+    $sql = "INSERT INTO {$this->table} ({$columns}) VALUES";
+
+    $nextDelim = '';
+    foreach ($this->rows as $row) {
+      $sql .= "{$nextDelim}\n(" . implode(',', $row) . ")";
+      $nextDelim = ',';
+    }
+    $sql .= "\n";
+
+    return $sql;
+  }
+}
diff --git a/tests/phpunit/CRM/Utils/SQL/InsertTest.php b/tests/phpunit/CRM/Utils/SQL/InsertTest.php
new file mode 100644 (file)
index 0000000..2231fb2
--- /dev/null
@@ -0,0 +1,50 @@
+<?php
+  require_once 'CiviTest/CiviUnitTestCase.php';
+
+  /**
+   * Class CRM_Utils_SQL_SelectTest
+   */
+class CRM_Utils_SQL_InsertTest extends CiviUnitTestCase {
+  function testRow_twice() {
+    $insert = CRM_Utils_SQL_Insert::into('foo')
+      ->row(array('first' => '1', 'second' => '2' ))
+      ->row(array('second' => '2b', 'first' => '1b'))
+      ;
+    $expected = '
+      INSERT INTO foo (`first`,`second`) VALUES
+      ("1","2"),
+      ("1b","2b")
+    ';
+    $this->assertLike($expected, $insert->toSQL());
+  }
+
+  function testRows() {
+    $insert = CRM_Utils_SQL_Insert::into('foo')
+      ->row(array('first' => '1', 'second' => '2' ))
+      ->rows(array(
+      array('second' => '2b', 'first' => '1b'),
+      array('first' => '1c', 'second' => '2c')
+      ))
+      ->row(array('second' => '2d', 'first' => '1d'))
+    ;
+    $expected = '
+      INSERT INTO foo (`first`,`second`) VALUES
+      ("1","2"),
+      ("1b","2b"),
+      ("1c","2c"),
+      ("1d","2d")
+    ';
+    $this->assertLike($expected, $insert->toSQL());
+  }
+
+  /**
+   * @param $expected
+   * @param $actual
+   * @param string $message
+   */
+  function assertLike($expected, $actual, $message = '') {
+    $expected = trim((preg_replace('/[ \r\n\t]+/', ' ', $expected)));
+    $actual = trim((preg_replace('/[ \r\n\t]+/', ' ', $actual)));
+    $this->assertEquals($expected, $actual, $message);
+  }
+}
\ No newline at end of file