Skip to content

Commit

Permalink
Refactor code. Fix problem when $data is not an array or array. More …
Browse files Browse the repository at this point in the history
…error checking.
  • Loading branch information
Yada Khov committed May 13, 2016
1 parent e0be9fb commit 8d6af9d
Show file tree
Hide file tree
Showing 3 changed files with 123 additions and 80 deletions.
11 changes: 8 additions & 3 deletions readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

[Insert Duplicate Key Update](http://dev.mysql.com/doc/refman/5.7/en/insert-on-duplicate.html) is a quick way to do mass insert.

It's a trait meant to be used with Laravel's Eloquent ORM.

### Code Example

```php
Expand Down Expand Up @@ -39,13 +41,16 @@ class UserTest extends Model

### created_at and updated_at fields.

created_at and updated_at will not be updated automatically. You can pass the database in the $data array as:
['id' => 1, 'email' => '[email protected]', 'name' => 'User One', 'created_at' => Carbon::now(), 'updated_at' => Carbon::now(), ],
created_at and updated_at will *not* be updated automatically. To update you can pass the fields in the insert array.

```php
['id' => 1, 'email' => '[email protected]', 'name' => 'User One', 'created_at' => Carbon::now(), 'updated_at' => Carbon::now()]
```

### Will this work on Postgresql?

No. On Duplicate Key Update is only available on MySQL. Postgresql 9.4 has a similar feature called [UPSERT](https://wiki.postgresql.org/wiki/UPSERT).

### Isn't this the same as updateOrCreate()?

It's similar but not the same. The updateOrCreate will only work on one row at a time. InsertOnDuplicateKey will on many rows.
It is similar but not the same. The updateOrCreate() will only work for one row. InsertOnDuplicateKey will work on many rows.
157 changes: 92 additions & 65 deletions src/InsertOnDuplicateKey.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,35 +6,16 @@

trait InsertOnDuplicateKey
{
/**
* Static function for getting table name.
*
* @return string
*/
public static function getTableName()
{
$class = get_called_class();

return (new $class())->getTable();
}

/**
* Static function for getting the primary key.
*
* @return string
*/
public static function getPrimaryKey()
{
$class = get_called_class();

return (new $class())->getKeyName();
}

/**
* Insert using mysql on duplicate key update.
* @link http://dev.mysql.com/doc/refman/5.7/en/insert-on-duplicate.html
*
* @param array $data Associative array. Must have the id column.
* Example: $data = [
* ['id' => 1, 'name' => 'John'],
* ['id' => 2, 'name' => 'Mike'],
* ];
*
* @param array $data is an array of array.
*
* @return bool
*/
Expand All @@ -44,30 +25,12 @@ public static function insertOnDuplicateKey(array $data)
return false;
}

if (count($data) === 1) {
// Case where $data is not an array of arrays.
if (!isset($data[0])) {
$data = [$data];
}

// Check to make sure $data contains the primary key
$primaryKey = static::getPrimaryKey();
$hasKey = false;

list($first) = $data;

if (!is_array($first)) {
throw new \InvalidArgumentException('Not an associative array.');
}

foreach (array_keys($first) as $key) {
if ($key === $primaryKey) {
$hasKey = true;
break;
}
}

if ($hasKey === false) {
throw new \InvalidArgumentException('Missing primary key in the data: ' . $primaryKey);
}
static::checkPrimaryKeyExists($data);

$sql = static::buildSql($data);

Expand All @@ -76,6 +39,29 @@ public static function insertOnDuplicateKey(array $data)
return DB::statement($sql, $data);
}

/**
* Static function for getting table name.
*
* @return string
*/
public static function getTableName()
{
$class = get_called_class();

return (new $class())->getTable();
}

/**
* Static function for getting the primary key.
*
* @return string
*/
public static function getPrimaryKey()
{
$class = get_called_class();

return (new $class())->getKeyName();
}

/**
* Build the question mark placeholder. Helper function for insertOnDuplicateKeyUpdate().
Expand All @@ -85,7 +71,7 @@ public static function insertOnDuplicateKey(array $data)
*
* @return string
*/
protected static function buildPlaceHolder($data)
protected static function buildQuestionMarks($data)
{
$lines = [];
foreach ($data as $row) {
Expand All @@ -101,13 +87,13 @@ protected static function buildPlaceHolder($data)
}

/**
* Build a value list.
* Get the first row of the $data array.
*
* @param array $data
*
* @return string
* @return mixed
*/
protected static function getColumnList(array $data)
protected static function getFirstRow(array $data)
{
if (empty($data)) {
throw new \InvalidArgumentException('Empty data.');
Expand All @@ -116,31 +102,63 @@ protected static function getColumnList(array $data)
list($first) = $data;

if (!is_array($first)) {
throw new \InvalidArgumentException('Not an associative array.');
throw new \InvalidArgumentException('$data is not an array of array.');
}

return '`' . implode('`,`', array_keys($first)) . '`';
return $first;
}

/**
* Build a value list.
* Check to make sure the first row as the primary key.
* Every row needs to have the primary key but we will only check the first row for efficiency.
*
* @param array $data
*
* @return string
*/
protected static function buildValuesList(array $data)
protected static function checkPrimaryKeyExists(array $data)
{
if (empty($data)) {
throw new \InvalidArgumentException('Empty data.');
// Check to make sure $data contains the primary key
$primaryKey = static::getPrimaryKey();
$hasKey = false;

$first = static::getFirstRow($data);

foreach (array_keys($first) as $key) {
if ($key === $primaryKey) {
$hasKey = true;
break;
}
}

list($first) = $data;
if ($hasKey === false) {
throw new \InvalidArgumentException(sprintf('Missing primary key %s.', $primaryKey));
}
}

if (!is_array($first)) {
throw new \InvalidArgumentException('Not an associative array.');
/**
* Build a value list.
*
* @param array $first
*
* @return string
*/
protected static function getColumnList(array $first)
{
if (empty($first)) {
throw new \InvalidArgumentException('Empty array.');
}

return '`' . implode('`,`', array_keys($first)) . '`';
}

/**
* Build a value list.
*
* @param array $first
*
* @return string
*/
protected static function buildValuesList(array $first)
{
$out = [];

foreach (array_keys($first) as $key) {
Expand All @@ -151,7 +169,7 @@ protected static function buildValuesList(array $data)
}

/**
* Inline a multiple dimension array. Helper function for insertOnDuplicateKeyUpdate().
* Inline a multiple dimensions array.
*
* @param $data
*
Expand All @@ -170,11 +188,20 @@ protected static function inLineArray(array $data)
return $out;
}

/**
* Build the INSERT ON DUPLICATE KEY sql statement.
*
* @param array $data
*
* @return string
*/
protected static function buildSql(array $data)
{
$sql = 'INSERT INTO `' . static::getTableName() . '`(' . static::getColumnList($data) . ') VALUES' . PHP_EOL;
$sql .= static::buildPlaceHolder($data) . PHP_EOL;
$sql .= 'ON DUPLICATE KEY UPDATE ' . static::buildValuesList($data);
$first = static::getFirstRow($data);

$sql = 'INSERT INTO `' . static::getTableName() . '`(' . static::getColumnList($first) . ') VALUES' . PHP_EOL;
$sql .= static::buildQuestionMarks($data) . PHP_EOL;
$sql .= 'ON DUPLICATE KEY UPDATE ' . static::buildValuesList($first);

return $sql;
}
Expand Down
35 changes: 23 additions & 12 deletions tests/MainTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -45,23 +45,13 @@ public function testGetColumnListEmptyDataException()
$this->invokeMethod($this->user, 'getColumnList', [$data]);
}

/**
* @expectedException \InvalidArgumentException
*/
public function testGetColumnListNotAssociativeArray()
{
$data = [1, '[email protected]', 'User One'];;

$result = $this->invokeMethod($this->user, 'getColumnList', [$data]);
}

public function testGetColumnList()
{
$data = $this->getDataForInsert();

$expected = '`id`,`email`,`name`';

$result = $this->invokeMethod($this->user, 'getColumnList', [$data]);
$result = $this->invokeMethod($this->user, 'getColumnList', [$data[0]]);

$this->assertEquals($expected, $result);
}
Expand All @@ -72,7 +62,7 @@ public function testBuildValuesList()

$expected = '`id` = VALUES(`id`), `email` = VALUES(`email`), `name` = VALUES(`name`)';

$result = $this->invokeMethod($this->user, 'buildValuesList', [$data]);
$result = $this->invokeMethod($this->user, 'buildValuesList', [$data[0]]);

$this->assertEquals($expected, $result);
}
Expand All @@ -90,6 +80,17 @@ public function testInLineArraySimple()
$this->assertEquals($expected, $result);
}

public function testBuildQuestionMarks()
{
$data = $this->getDataForInsert();

$expected = '(?,?,?), (?,?,?), (?,?,?)';

$result = $this->invokeMethod($this->user, 'buildQuestionMarks', [$data]);

$this->assertEquals($expected, $result);
}

public function testInLineArrayThreeRows()
{
$data = $this->getDataForInsert();
Expand Down Expand Up @@ -132,4 +133,14 @@ public function testBuildSqlMultiple()

$this->assertEquals($expected, $result);
}

/**
* @expectedException \InvalidArgumentException
*/
public function testInsertWithBadId()
{
$data = ['incorrect_id_field' => 1, 'email' => '[email protected]', 'name' => 'User One'];

$this->user->insertOnDuplicateKey($data);
}
}

0 comments on commit 8d6af9d

Please sign in to comment.