Skip to content

Bug Report: Raw object bindings incorrectly added as nested array in INSERT statements #163

@mean-cj

Description

@mean-cj

Description
When using DB::raw() with MySQL functions in INSERT statements, the bindings array is incorrectly populated with nested arrays instead of a flat array. This causes parameter binding to be misaligned, resulting in incorrect values being inserted into the database.

Environment

  • Package: pecee/pixie
  • Version: (check your composer.lock)
  • PHP Version: 8.x
  • Database: MySQL

Steps to Reproduce

<?php
use Models\DB;

$data = [
    'queue_status' => 'new',
    'queue_name' => 'Test Queue',
    'created_at' => DB::raw('NOW()'),
    'CustomerID' => 5501,
    'OrderID' => 123,
    'queue_provider' => 'TestProvider',
    'schedule_at' => '2026-04-16 00:31:46',
];

$db = DB::table('queues');
$queue_id = $db->insert($data);

// Debug the query
$lastQuery = $db->getLastQuery();
echo $lastQuery->getSql() . "\n";
print_r($lastQuery->getBindings());
echo $lastQuery->getRawSql() . "\n";

Expected Behavior
Bindings should be a flat array:

<?php
Array (
    [0] => 'new'
    [1] => 'Test Queue'
    [2] => 5501
    [3] => 123
    [4] => 'TestProvider'
    [5] => '2026-04-16 00:31:46'
)

SQL with correct values:

INSERT INTO `queues` (`queue_status`, `queue_name`, `created_at`, `CustomerID`, `OrderID`, `queue_provider`, `schedule_at`) 
VALUES ('new', 'Test Queue', NOW(), 5501, 123, 'TestProvider', '2026-04-16 00:31:46')

Actual Behavior
Bindings contain nested empty array:

<?php
Array (
    [0] => 'new'
    [1] => 'Test Queue'
    [2] => Array ()  // ← Empty array inserted here!
    [3] => 5501
    [4] => 123
    [5] => 'TestProvider'
    [6] => '2026-04-16 00:31:46'
)

This causes values to be shifted and incorrectly bound:

-- Wrong! Values are misaligned
INSERT INTO `queues` (...) VALUES ('new', 'Test Queue', NOW(), '', 5501, 123, 'TestProvider')
-- CustomerID gets empty string, OrderID gets CustomerID value, etc.

Root Cause
The issue is in BaseAdapter::doInsert() at line 445:
File: src/Pecee/Pixie/QueryBuilder/Adapters/BaseAdapter.php

<?php
private function doInsert(array $statements, array $data, $type): array
{
    $table = end($statements['tables']);
    
    $bindings = $keys = $values = [];
    
    foreach ($data as $key => $value) {
        $keys[] = $key;
        if ($value instanceof Raw) {
            $values[] = (string)$value;
            $bindings[] = $value->getBindings();  // ← BUG HERE!
            // This adds an array (even empty) as an element instead of merging
        } else {
            $values[] = '?';
            $bindings[] = $value;
        }
    }
    // ...
}

When $value->getBindings() returns an empty array [], it gets added as a nested element instead of being merged into the flat bindings array.

Proposed Fix
Change line 445 from:
$bindings[] = $value->getBindings();

To:
$bindings = array_merge($bindings, (array)$value->getBindings());

Complete fixed code:

<?php
private function doInsert(array $statements, array $data, $type): array
{
    $table = end($statements['tables']);
    
    $bindings = $keys = $values = [];
    
    foreach ($data as $key => $value) {
        $keys[] = $key;
        if ($value instanceof Raw) {
            $values[] = (string)$value;
            $bindings = array_merge($bindings, (array)$value->getBindings());  // ✅ FIXED
        } else {
            $values[] = '?';
            $bindings[] = $value;
        }
    }
    
    $sqlArray = [
        $type . ' INTO',
        $this->wrapSanitizer($table),
        '(' . $this->arrayStr($keys) . ')',
        'VALUES',
        '(' . $this->arrayStr($values, ', ', false) . ')',
    ];

    if (isset($statements['onduplicate']) === true) {
        if (\count($statements['onduplicate']) < 1) {
            throw new Exception('No data given.', 4);
        }

        [$updateStatement, $updateBindings] = $this->getUpdateStatement($statements['onduplicate']);
        $sqlArray[] = 'ON DUPLICATE KEY UPDATE ' . $updateStatement;
        $bindings = array_merge($bindings, $updateBindings);
    }

    $sql = $this->concatenateQuery($sqlArray);

    return compact('sql', 'bindings');
}

Impact
This bug affects any INSERT, INSERT IGNORE, or REPLACE query that uses Raw objects with MySQL functions like:

  • DB::raw('NOW()')
  • DB::raw('UUID()')
  • DB::raw('CURRENT_TIMESTAMP')
  • Any custom SQL expressions

The misaligned bindings cause data corruption as values are inserted into wrong columns.

Workaround
Until this is fixed, avoid using Raw objects in INSERT statements. Use PHP values instead:

<?php
// Instead of:
'created_at' => DB::raw('NOW()')

// Use:
'created_at' => date('Y-m-d H:i:s')
// or with Carbon:
'created_at' => Carbon::now()->toDateTimeString()

Additional Context
The same pattern exists in getUpdateStatement() method (line 492) which might have similar issues:

<?php
if ($value instanceof Raw) {
    $statements[] = $statement . $value;
    $bindings += $value->getBindings();  // Uses += which might work differently
}

to
$bindings = array_merge($bindings, (array)$value->getBindings());

This should be reviewed as well to ensure consistency.

Would you like me to submit a pull request with the fix?

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions