CodeIgniter4: Bug: QueryBuilder inconsistent behaviour with multiple `set()`s

PHP Version

8.1

CodeIgniter4 Version

4.3.1

CodeIgniter4 Installation Method

Composer (using codeigniter4/appstarter)

Which operating systems have you tested for this bug?

macOS

Which server did you use?

apache

Database

MySQL 8

What happened?

Using multiple set()s with RawSql doesn’t produce consistent sql statements.

Working SQL (1):
INSERT INTO `auth_bearer` (`jti`, `proctorID`, `token_type`, `expires`) VALUES ('jti', 12, 'handover', DATE_ADD(NOW(), INTERVAL 2 HOUR))
Working SQL (2):
INSERT INTO `auth_bearer` (`jti`, `proctorID`, `token_type`, `expires`) VALUES ('jti', 12, 'handover', DATE_ADD(NOW(), INTERVAL 2 HOUR))
Working SQL (3):
INSERT INTO `auth_bearer` (`jti`, `proctorID`, `token_type`, `expires`) VALUES ('jti', 12, 'handover', DATE_ADD(NOW(), INTERVAL 2 HOUR))
Generates bad SQL:
INSERT INTO `auth_bearer` (`jti`, `proctorID`, `token_type`, expires = DATE_ADD(NOW(), INTERVAL 2 HOUR)) VALUES ('jti', 12, 'handover', )
Silently failing SQL:
INSERT INTO `auth_bearer` (`jti`, `proctorID`, `token_type`) VALUES ('jti', '12', 'handover')
Failing with exception: You must use the "set" method to insert an entry.

Steps to Reproduce

The following code produce different sql statements, some working and some not working:

       $db = db_connect();

        CLI::write('Working SQL (1):', 'green');
        CLI::write($db->table('auth_bearer')
        ->set([
            'jti'        => 'jti',
            'proctorID'  => 12,
            'token_type' => 'handover',
        ])        
        ->set('expires', 'DATE_ADD(NOW(), INTERVAL 2 HOUR)', false)
        ->getCompiledInsert(true));

        CLI::write('Working SQL (2):', 'green');
        CLI::write($db->table('auth_bearer')
        ->set([
            'jti'        => 'jti',
            'proctorID'  => 12,
            'token_type' => 'handover',
            'expires' => new RawSql('DATE_ADD(NOW(), INTERVAL 2 HOUR)')
        ])        
        ->getCompiledInsert(true));

        CLI::write('Working SQL (3):', 'green');
        CLI::write($db->table('auth_bearer')
        ->set('jti', 'jti')
        ->set('proctorID', 12)
        ->set('token_type', 'handover')
        ->set('expires', 'DATE_ADD(NOW(), INTERVAL 2 HOUR)', false)
        ->getCompiledInsert(true));

        CLI::write('Generates bad SQL:', 'red');
        CLI::write($db->table('auth_bearer')
        ->set([
            'jti'        => 'jti',
            'proctorID'  => 12,
            'token_type' => 'handover',
        ])        
        ->set('expires = DATE_ADD(NOW(), INTERVAL 2 HOUR)', null, false)
        ->getCompiledInsert(true));


        CLI::write('Silently failing SQL:', 'red');
        CLI::write($db->table('auth_bearer')
        ->set([
            'jti'        => 'jti',
            'proctorID'  => '12',
            'token_type' => 'handover',
        ])        
        ->set(new RawSql('expires = DATE_ADD(NOW(), INTERVAL 2 HOUR)'))
        ->getCompiledInsert(true));


        try {
            CLI::write($db->table('auth_bearer')
            ->set(new RawSql('expires = DATE_ADD(NOW(), INTERVAL 2 HOUR)'))
            ->getCompiledInsert(true));
        }
        catch (DatabaseException $e) {
            CLI::write('Failing with exception: ' . $e->getMessage(), 'red');
        }

Expected Output

Same statements in all valid cases.

Anything else?

No response

About this issue

  • Original URL
  • State: closed
  • Created a year ago
  • Comments: 15 (15 by maintainers)

Most upvoted comments

@tangix I submitted a PR to fix the functionality of where() so that it behaves the same way set() does.

You can set both key and value to use RawSql.

        $sql = $this->db->table('auth_bearer')
            ->select('*')
            ->where(new RawSql('CURRENT_TIMESTAMP() >='), new RawSql('DATE_ADD(column, INTERVAL 2 HOUR)'))
            ->getCompiledSelect(true);

        $expected = <<<'SQL'
            SELECT *
            FROM "auth_bearer"
            WHERE CURRENT_TIMESTAMP() >= DATE_ADD(column, INTERVAL 2 HOUR)
            SQL;
        $this->assertSame($expected, $sql);

You can include an operator in the key or leave it out for =

You can also use only RawSql for key and leave value null.

If the PR is merged you can replace the whereHaving() in system/Database/BaseBuilder.php with the one from the PR to patch until next release.

https://github.com/codeigniter4/CodeIgniter4/pull/7147