Skip to content

Instantly share code, notes, and snippets.

@VinceG
Created October 19, 2016 14:16
Show Gist options
  • Star 19 You must be signed in to star a gist
  • Fork 2 You must be signed in to fork a gist
  • Save VinceG/0fb570925748ab35bc53f2a798cb517c to your computer and use it in GitHub Desktop.
Save VinceG/0fb570925748ab35bc53f2a798cb517c to your computer and use it in GitHub Desktop.
Laravel support for replace into / insert ignore / insert on duplicate key update
<?php
namespace App\Library\Database\Query;
use Illuminate\Database\Query\Builder as QueryBuilder;
class Builder extends QueryBuilder
{
/**
* Insert a new record into the database.
*
* @param array $values
* @return bool
*/
public function replace(array $values)
{
if (empty($values)) {
return true;
}
// Since every insert gets treated like a batch insert, we will make sure the
// bindings are structured in a way that is convenient for building these
// inserts statements by verifying the elements are actually an array.
if (! is_array(reset($values))) {
$values = [$values];
}
// Since every insert gets treated like a batch insert, we will make sure the
// bindings are structured in a way that is convenient for building these
// inserts statements by verifying the elements are actually an array.
else {
foreach ($values as $key => $value) {
ksort($value);
$values[$key] = $value;
}
}
// We'll treat every insert like a batch insert so we can easily insert each
// of the records into the database consistently. This will make it much
// easier on the grammars to just handle one type of record insertion.
$bindings = [];
foreach ($values as $record) {
foreach ($record as $value) {
$bindings[] = $value;
}
}
$sql = $this->grammar->compileReplace($this, $values);
// Once we have compiled the insert statement's SQL we can execute it on the
// connection and return a result as a boolean success indicator as that
// is the same type of result returned by the raw connection instance.
$bindings = $this->cleanBindings($bindings);
return $this->connection->insert($sql, $bindings);
}
/**
* Insert a new record into the database.
*
* @param array $values
* @return bool
*/
public function insertUpdate(array $values)
{
if (empty($values)) {
return true;
}
// Since every insert gets treated like a batch insert, we will make sure the
// bindings are structured in a way that is convenient for building these
// inserts statements by verifying the elements are actually an array.
if (! is_array(reset($values))) {
$values = [$values];
}
// Since every insert gets treated like a batch insert, we will make sure the
// bindings are structured in a way that is convenient for building these
// inserts statements by verifying the elements are actually an array.
else {
foreach ($values as $key => $value) {
ksort($value);
$values[$key] = $value;
}
}
// We'll treat every insert like a batch insert so we can easily insert each
// of the records into the database consistently. This will make it much
// easier on the grammars to just handle one type of record insertion.
$bindings = [];
foreach ($values as $record) {
foreach ($record as $value) {
$bindings[] = $value;
}
}
$sql = $this->grammar->compileInsertUpdate($this, $values);
// Once we have compiled the insert statement's SQL we can execute it on the
// connection and return a result as a boolean success indicator as that
// is the same type of result returned by the raw connection instance.
$bindings = $this->cleanBindings($bindings);
return $this->connection->insert($sql, array_merge($bindings, $bindings));
}
/**
* Insert a new record into the database.
*
* @param array $values
* @return bool
*/
public function insertIgnore(array $values)
{
if (empty($values)) {
return true;
}
// Since every insert gets treated like a batch insert, we will make sure the
// bindings are structured in a way that is convenient for building these
// inserts statements by verifying the elements are actually an array.
if (! is_array(reset($values))) {
$values = [$values];
}
// Since every insert gets treated like a batch insert, we will make sure the
// bindings are structured in a way that is convenient for building these
// inserts statements by verifying the elements are actually an array.
else {
foreach ($values as $key => $value) {
ksort($value);
$values[$key] = $value;
}
}
// We'll treat every insert like a batch insert so we can easily insert each
// of the records into the database consistently. This will make it much
// easier on the grammars to just handle one type of record insertion.
$bindings = [];
foreach ($values as $record) {
foreach ($record as $value) {
$bindings[] = $value;
}
}
$sql = $this->grammar->compileInsertIgnore($this, $values);
// Once we have compiled the insert statement's SQL we can execute it on the
// connection and return a result as a boolean success indicator as that
// is the same type of result returned by the raw connection instance.
$bindings = $this->cleanBindings($bindings);
return $this->connection->insert($sql, $bindings);
}
}
<?php
namespace App\Library\Database;
use Illuminate\Database\Connection as BaseConnection;
use App\Library\Database\Query\Builder;
class Connection extends BaseConnection
{
/**
* Get a new query builder instance.
*
* @return \Illuminate\Database\Query\Builder
*/
public function query()
{
return new Builder(
$this, $this->getQueryGrammar(), $this->getPostProcessor()
);
}
}
<?php
DB::table('table')->replace([
'dashboard_id' => $this->model->id,
'date' => Carbon::now()->format('Y-m-d'),
'views' => DB::raw('views + 1')
]);
DB::table('table')->insertIgnore([
'dashboard_id' => $this->model->id,
'date' => Carbon::now()->format('Y-m-d'),
'views' => DB::raw('views + 1')
]);
DB::table('table')->insertUpdate([
'dashboard_id' => $this->model->id,
'date' => Carbon::now()->format('Y-m-d'),
'views' => DB::raw('views + 1')
]);
<?php
namespace App\Library\Database;
use Illuminate\Database\Query\Processors\MySqlProcessor;
use App\Library\Database\Query\Grammars\MySqlGrammar as QueryGrammar;
use Illuminate\Database\MySqlConnection as Connection;
class MySqlConnection extends Connection
{
/**
* Get the default query grammar instance.
*
* @return \Illuminate\Database\Query\Grammars\MySqlGrammar
*/
protected function getDefaultQueryGrammar()
{
return $this->withTablePrefix(new QueryGrammar);
}
/**
* Get the default post processor instance.
*
* @return \Illuminate\Database\Query\Processors\MySqlProcessor
*/
protected function getDefaultPostProcessor()
{
return new MySqlProcessor;
}
}
<?php
namespace App\Library\Database\Query\Grammars;
use Illuminate\Database\Query\Grammars\MySqlGrammar as Grammar;
use App\Library\Database\Query\Builder;
class MySqlGrammar extends Grammar
{
/**
* Compile an replace into statement into SQL.
*
* @param \Illuminate\Database\Query\Builder $query
* @param array $values
* @return string
*/
public function compileReplace(Builder $query, array $values)
{
// Essentially we will force every insert to be treated as a batch insert which
// simply makes creating the SQL easier for us since we can utilize the same
// basic routine regardless of an amount of records given to us to insert.
$table = $this->wrapTable($query->from);
if (! is_array(reset($values))) {
$values = [$values];
}
$columns = $this->columnize(array_keys(reset($values)));
// We need to build a list of parameter place-holders of values that are bound
// to the query. Each insert should have the exact same amount of parameter
// bindings so we will loop through the record and parameterize them all.
$parameters = [];
foreach ($values as $record) {
$parameters[] = '('.$this->parameterize($record).')';
}
$parameters = implode(', ', $parameters);
return "replace into $table ($columns) values $parameters";
}
/**
* Compile an insert ignore statement into SQL.
*
* @param \Illuminate\Database\Query\Builder $query
* @param array $values
* @return string
*/
public function compileInsertIgnore(Builder $query, array $values)
{
// Essentially we will force every insert to be treated as a batch insert which
// simply makes creating the SQL easier for us since we can utilize the same
// basic routine regardless of an amount of records given to us to insert.
$table = $this->wrapTable($query->from);
if (! is_array(reset($values))) {
$values = [$values];
}
$columns = $this->columnize(array_keys(reset($values)));
// We need to build a list of parameter place-holders of values that are bound
// to the query. Each insert should have the exact same amount of parameter
// bindings so we will loop through the record and parameterize them all.
$parameters = [];
foreach ($values as $record) {
$parameters[] = '('.$this->parameterize($record).')';
}
$parameters = implode(', ', $parameters);
return "insert ignore into $table ($columns) values $parameters";
}
/**
* Compile an insert ignore statement into SQL.
*
* @param \Illuminate\Database\Query\Builder $query
* @param array $values
* @return string
*/
public function compileInsertUpdate(Builder $query, array $values)
{
// Essentially we will force every insert to be treated as a batch insert which
// simply makes creating the SQL easier for us since we can utilize the same
// basic routine regardless of an amount of records given to us to insert.
$table = $this->wrapTable($query->from);
// Each one of the columns in the update statements needs to be wrapped in the
// keyword identifiers, also a place-holder needs to be created for each of
// the values in the list of bindings so we can make the sets statements.
$columns = [];
$values = reset($values);
foreach ($values as $key => $value) {
$columns[] = $this->wrap($key).' = '.$this->parameter($value);
}
$columns = implode(', ', $columns);
return "insert into $table set $columns ON DUPLICATE KEY UPDATE $columns";
}
}
@IllyaMoskvin
Copy link

The insertUpdate command here is malformed. I fixed it for our project. See commit for details:

art-institute-of-chicago/data-service-images@0802b89

Requires a couple minor changes in Builder::insertUpdate(), and a rewrite of MySqlGrammar::compileInsertUpdate().

Resources:

@CloudyCity
Copy link

CloudyCity commented Apr 2, 2019

@IllyaMoskvin thx for your comment! I followed your way but it still use Illuminate\Database\Query\Builder anyway.

My laravel version is 5.2, and App\Library\Database\ is 100% same as your project [0].

I tried these way:

<?php

namespace App\Providers;

use App\Library\Database\MySqlConnection;
use Illuminate\Database\Connection;
use Illuminate\Support\ServiceProvider;

class DatabaseServiceProvider extends ServiceProvider
{
    /**
     * Override the default connection for MySQL. This allows us to use `replace` etc.
     *
     * @link https://stidges.com/extending-the-connection-class-in-laravel
     * @link https://gist.github.com/VinceG/0fb570925748ab35bc53f2a798cb517c
     *
     * @return void
     */
    public function boot()
    {
        Connection::resolverFor('mysql', function ($connection, $database, $prefix, $config) {
            return new MySqlConnection($connection, $database, $prefix, $config);
        });
    }

     /**
     * Register the service provider.
     *
     * @return void
     */
    public function register()
    {
        // Or this way
        // $this->app->singleton('db.connection.mysql', function ($app, $parameters) {
        //     list($connection, $database, $prefix, $config) = $parameters;
        //     return new MySqlConnection($connection, $database, $prefix, $config);
        // });

        // Or this way
        // $this->app->bind('db.connection.mysql', \App\Library\Database\MySqlConnection::class);
    }
}
    'providers' => [
        App\Providers\DatabaseServiceProvider::class,
        ...
        Illuminate\Database\DatabaseServiceProvider::class,
    ],
    // or
    // 'providers' => [
    //    Illuminate\Database\DatabaseServiceProvider::class,
    //    ...
    //    App\Providers\DatabaseServiceProvider::class,
    // ],

@IllyaMoskvin It is my mistake, i am using lumen 5.4 not laravel 5.2. I should register provider in bootstrap/app.php:

<?php
...
$app->register(App\Providers\DatabaseServiceProvider::class);
return $app;

Now it working!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment