Skip to content

Commit

Permalink
Add 1.0
Browse files Browse the repository at this point in the history
  • Loading branch information
staudenmeir committed May 22, 2019
1 parent 89b781a commit ef8ca95
Show file tree
Hide file tree
Showing 28 changed files with 1,271 additions and 0 deletions.
6 changes: 6 additions & 0 deletions .gitattributes
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
/tests export-ignore
.gitattributes export-ignore
.gitignore export-ignore
.scrutinizer.yml export-ignore
.travis.yml export-ignore
phpunit.xml.dist export-ignore
9 changes: 9 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
/.idea
/.vagrant
/vendor
.phpunit.result.cache
after.sh
aliases
composer.lock
Homestead.yaml
Vagrantfile
4 changes: 4 additions & 0 deletions .scrutinizer.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
tools:
external_code_coverage:
runs: 3
timeout: 600
53 changes: 53 additions & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
language: php

dist: xenial

services:
- mysql
- postgresql

env:
global:
- COVERAGE=no
- DB=mysql
- RELEASE=stable

matrix:
include:
- php: 7.0
- php: 7.0
env: RELEASE=lowest
- php: 7.1
- php: 7.2
- php: 7.3
env: COVERAGE=yes
- php: 7.3
env: COVERAGE=yes DB=pgsql
- php: 7.3
env: COVERAGE=yes DB=sqlite

cache:
directories:
- $HOME/.composer/cache

before_install:
- COMPOSER_FLAGS=$([ $RELEASE == "lowest" ] && echo "--prefer-lowest" || echo "")
- PHPUNIT_FLAGS=$([ $COVERAGE == "yes" ] && echo "--coverage-clover=coverage.xml" || echo "")

install:
- travis_retry composer update --no-interaction --no-suggest --prefer-dist --prefer-stable $COMPOSER_FLAGS

before_script:
- cp tests/config/database.travis.php tests/config/database.php
- mysql -e 'create database `test`;'
- psql -c 'create database "test";' -U postgres

script:
- vendor/bin/phpunit $PHPUNIT_FLAGS

after_script:
- |
if [ $COVERAGE == "yes" ]; then
travis_retry wget https://scrutinizer-ci.com/ocular.phar
travis_retry php ocular.phar code-coverage:upload --format=php-clover coverage.xml
fi
137 changes: 137 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
[![Build Status](https://travis-ci.org/staudenmeir/laravel-upsert.svg?branch=master)](https://travis-ci.org/staudenmeir/laravel-upsert)
[![Code Coverage](https://scrutinizer-ci.com/g/staudenmeir/laravel-upsert/badges/coverage.png?b=master)](https://scrutinizer-ci.com/g/staudenmeir/laravel-upsert/?branch=master)
[![Scrutinizer Code Quality](https://scrutinizer-ci.com/g/staudenmeir/laravel-upsert/badges/quality-score.png?b=master)](https://scrutinizer-ci.com/g/staudenmeir/laravel-upsert/?branch=master)
[![Latest Stable Version](https://poser.pugx.org/staudenmeir/laravel-upsert/v/stable)](https://packagist.org/packages/staudenmeir/laravel-upsert)
[![Total Downloads](https://poser.pugx.org/staudenmeir/laravel-upsert/downloads)](https://packagist.org/packages/staudenmeir/laravel-upsert)
[![License](https://poser.pugx.org/staudenmeir/laravel-upsert/license)](https://packagist.org/packages/staudenmeir/laravel-upsert)

## Introduction
This Laravel extension adds support for INSERT & UPDATE (UPSERT) and INSERT IGNORE to the query builder and Eloquent.

Supports Laravel 5.5+.

## Compatibility

- MySQL 5.1+: [INSERT ON DUPLICATE KEY UPDATE](https://dev.mysql.com/doc/refman/en/insert-on-duplicate.html)
- MariaDB 5.1+: [INSERT ON DUPLICATE KEY UPDATE](https://mariadb.com/kb/en/library/insert-on-duplicate-key-update/)
- PostgreSQL 9.5+: [INSERT ON CONFLICT](https://www.postgresql.org/docs/current/sql-insert.html#SQL-ON-CONFLICT)
- SQLite 3.24.0+: [INSERT ON CONFLICT](https://www.sqlite.org/lang_UPSERT.html)
- SQL Server 2008+: [MERGE](https://docs.microsoft.com/sql/t-sql/statements/merge-transact-sql)

## Installation

composer require staudenmeir/laravel-upsert:"^1.0"

## Usage

- [INSERT & UPDATE (UPSERT)](#insert--update-upsert)
- [INSERT IGNORE](#insert-ignore)
- [Eloquent](#eloquent)
- [Lumen](#lumen)

### INSERT & UPDATE (UPSERT)

Consider this `users` table with a unique `username` column:

```php
Schema::create('users', function (Blueprint $table) {
$table->increments('id');
$table->string('username')->unique();
$table->boolean('active');
$table->timestamps();
});
```

Use `upsert()` to insert a new user or update the existing one. In this example, an inactive user will be reactivated and the `updated_at` timestamp will be updated:

```php
DB::table('users')->upsert(
['username' => 'foo', 'active' => true, 'created_at' => now(), 'updated_at' => now()],
'username',
['active', 'updated_at']
);
```

Provide the values to be inserted as the first argument. This can be a single record or multiple records.

The second argument is the column(s) that uniquely identify records. All databases except SQL Server require these columns to have a `PRIMARY` or `UNIQUE` index.

Provide the columns to be the updated as the third argument (optional). By default, all columns will be updated.
You can provide column names and key-value pairs with literals or raw expressions (see below).

As an example with a composite key and a raw expression, consider this table that counts visitors per post and day:

```php
Schema::create('stats', function (Blueprint $table) {
$table->unsignedInteger('post_id');
$table->date('date');
$table->unsignedInteger('views');
$table->unique(['post_id', 'date']);
});
```

Use `upsert()` to log visits. The query will create a new record per post and day or increment the existing view counter:

```php
DB::table('stats')->upsert(
[
['post_id' => 1, 'date' => now()->toDateString(), 'views' => 1],
['post_id' => 2, 'date' => now()->toDateString(), 'views' => 1],
],
['post_id', 'date'],
['views' => DB::raw('stats.views + 1')]
);
```

### INSERT IGNORE

You can also insert records while ignoring duplicate-key errors:

```php
Schema::create('users', function (Blueprint $table) {
$table->increments('id');
$table->string('username')->unique();
$table->timestamps();
});

DB::table('users')->insertIgnore([
['username' => 'foo', 'created_at' => now(), 'updated_at' => now()],
['username' => 'bar', 'created_at' => now(), 'updated_at' => now()],
]);
```

SQL Server requires a second argument with the column(s) that uniquely identify records:

```php
DB::table('users')->insertIgnore(
['username' => 'foo', 'created_at' => now(), 'updated_at' => now()],
'username'
);
```

### Eloquent

You can use UPSERT and INSERT IGNORE queries in Eloquent with the `HasUpsertQueries` trait:

```php
class User extends Model
{
use \Staudenmeir\LaravelUpsert\Eloquent\HasUpsertQueries;
}

User::upsert(['username' => 'foo', 'active' => true], 'username', ['active']);

User::insertIgnore(['username' => 'foo']);
```

If the model uses timestamps, `upsert()` and `insertIgnore()` will automatically add timestamps to the inserted values. `upsert()` will also add `updated_at` to the updated columns.

### Lumen

If you are using Lumen, you have to instantiate the query builder manually:

```php
$builder = new \Staudenmeir\LaravelUpsert\Query\Builder(app('db')->connection());

$builder->from(...)->upsert(...);
```
36 changes: 36 additions & 0 deletions composer.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
{
"name": "staudenmeir/laravel-upsert",
"description": "Laravel UPSERT and INSERT IGNORE queries",
"license": "MIT",
"authors": [
{
"name": "Jonas Staudenmeir",
"email": "[email protected]"
}
],
"require": {
"php": ">=7.0",
"illuminate/database": "5.5.*|5.6.*|5.7.*"
},
"require-dev": {
"laravel/homestead": "^7.0",
"orchestra/testbench": "^3.5"
},
"autoload": {
"psr-4": {
"Staudenmeir\\LaravelUpsert\\": "src/"
}
},
"autoload-dev": {
"psr-4": {
"Tests\\": "tests/"
}
},
"extra": {
"laravel": {
"providers": [
"Staudenmeir\\LaravelUpsert\\DatabaseServiceProvider"
]
}
}
}
23 changes: 23 additions & 0 deletions phpunit.xml.dist
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
<?xml version="1.0" encoding="UTF-8"?>
<phpunit backupGlobals="false"
backupStaticAttributes="false"
colors="true"
convertErrorsToExceptions="true"
convertNoticesToExceptions="true"
convertWarningsToExceptions="true"
processIsolation="false"
stopOnError="false"
stopOnFailure="false"
verbose="true"
>
<testsuites>
<testsuite name="LaravelUpsert Test Suite">
<directory suffix="Test.php">./tests</directory>
</testsuite>
</testsuites>
<filter>
<whitelist processUncoveredFilesFromWhitelist="true">
<directory suffix=".php">./src</directory>
</whitelist>
</filter>
</phpunit>
18 changes: 18 additions & 0 deletions src/Connections/CreatesQueryBuilder.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
<?php

namespace Staudenmeir\LaravelUpsert\Connections;

use Staudenmeir\LaravelUpsert\Query\Builder;

trait CreatesQueryBuilder
{
/**
* Get a new query builder instance.
*
* @return \Illuminate\Database\Query\Builder
*/
public function query()
{
return new Builder($this);
}
}
10 changes: 10 additions & 0 deletions src/Connections/MySqlConnection.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<?php

namespace Staudenmeir\LaravelUpsert\Connections;

use Illuminate\Database\MySqlConnection as Base;

class MySqlConnection extends Base
{
use CreatesQueryBuilder;
}
10 changes: 10 additions & 0 deletions src/Connections/PostgresConnection.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<?php

namespace Staudenmeir\LaravelUpsert\Connections;

use Illuminate\Database\PostgresConnection as Base;

class PostgresConnection extends Base
{
use CreatesQueryBuilder;
}
10 changes: 10 additions & 0 deletions src/Connections/SQLiteConnection.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<?php

namespace Staudenmeir\LaravelUpsert\Connections;

use Illuminate\Database\SQLiteConnection as Base;

class SQLiteConnection extends Base
{
use CreatesQueryBuilder;
}
10 changes: 10 additions & 0 deletions src/Connections/SqlServerConnection.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<?php

namespace Staudenmeir\LaravelUpsert\Connections;

use Illuminate\Database\SqlServerConnection as Base;

class SqlServerConnection extends Base
{
use CreatesQueryBuilder;
}
46 changes: 46 additions & 0 deletions src/Connectors/ConnectionFactory.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
<?php

namespace Staudenmeir\LaravelUpsert\Connectors;

use Illuminate\Database\Connection;
use Illuminate\Database\Connectors\ConnectionFactory as Base;
use InvalidArgumentException;
use Staudenmeir\LaravelUpsert\Connections\MySqlConnection;
use Staudenmeir\LaravelUpsert\Connections\PostgresConnection;
use Staudenmeir\LaravelUpsert\Connections\SQLiteConnection;
use Staudenmeir\LaravelUpsert\Connections\SqlServerConnection;

class ConnectionFactory extends Base
{
/**
* Create a new connection instance.
*
* @param string $driver
* @param \PDO|\Closure $connection
* @param string $database
* @param string $prefix
* @param array $config
* @return \Illuminate\Database\Connection
*
* @throws \InvalidArgumentException
*/
protected function createConnection($driver, $connection, $database, $prefix = '', array $config = [])
{
if ($resolver = Connection::getResolver($driver)) {
return $resolver($connection, $database, $prefix, $config); // @codeCoverageIgnore
}

switch ($driver) {
case 'mysql':
return new MySqlConnection($connection, $database, $prefix, $config);
case 'pgsql':
return new PostgresConnection($connection, $database, $prefix, $config);
case 'sqlite':
return new SQLiteConnection($connection, $database, $prefix, $config);
case 'sqlsrv':
return new SqlServerConnection($connection, $database, $prefix, $config);
}

throw new InvalidArgumentException("Unsupported driver [{$driver}]"); // @codeCoverageIgnore
}
}
Loading

0 comments on commit ef8ca95

Please sign in to comment.