Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add BaseBuilder::upsert() and BaseBuilder::upsertBatch() #6600

Merged
merged 33 commits into from
Oct 14, 2022

Conversation

sclubricants
Copy link
Member

@sclubricants sclubricants commented Sep 27, 2022

This PR adds BaseBuilder::upsert() and BaseBuilder::upsertBatch() methods.

There is some more functionality to add but I'll hold off on that for now so we can hopefully get this into 4.3.

Related: #6407

Checklist:

  • Securely signed commits
  • Component(s) with PHPDoc blocks, only if necessary or adds value
  • Unit testing, with >80% coverage
  • User guide updated
  • Conforms to style guide

@sclubricants sclubricants added enhancement PRs that improve existing functionalities database Issues or pull requests that affect the database layer 4.3 labels Sep 27, 2022
@sclubricants
Copy link
Member Author

@codeigniter4/database-team Have at it!

@kenjis
Copy link
Member

kenjis commented Sep 28, 2022

The code coverage is down. Can you add tests for uncovered code?

Screenshot 2022-09-28 12 05 03

@sclubricants
Copy link
Member Author

There is a few lines of code uncovered because its there for a feature not yet fully implemented.

I never really get the code coverage tests.. they don't make a lot of sense at least on the database. From my experience it seems anytime you change anything in the database these coverage tests complain. I think its because what changes in one driver affects the other coverage tests. Its odd because you look at the mysql test and it shows coverage for say sqlsrv.

Ill try and run some coverage tests locally and make sure we are covered.

@kenjis
Copy link
Member

kenjis commented Sep 28, 2022

Yeah, something wrong with coverall results.
So if the results are wrong, I don't mind the coverage down.

@sclubricants
Copy link
Member Author

The coverage was really good. Just a couple of lines missed. I added a test and changed one to cover the missing lines. All the upsert and related methods had > 95% coverage.

I think what happens in a case like this is that when the coverage test for MySql runs, all the code that was added to the other drivers is not visible. So basically you increase the denominator and the coverage drops. One thing that would help would be to exclude the other drivers for each coverage test. This isn't perfect either though because sometimes a driver's code is implemented in BaseBuilder.

@kenjis kenjis added the stale Pull requests with conflicts label Sep 30, 2022
@kenjis
Copy link
Member

kenjis commented Sep 30, 2022

The docs have conflicts now. Please resolve.

@kenjis
Copy link
Member

kenjis commented Sep 30, 2022

The following tests fails on my local.

$ vendor/bin/phpunit tests/system/Database/Live/UpdateTest.php
PHPUnit 9.5.25 #StandWithUkraine

Runtime:       PHP 8.1.10
Configuration: /Users/kenji/work/codeigniter/official/CodeIgniter4/phpunit.xml

...........EEFEE.                                                 17 / 17 (100%)

Time: 00:00.532, Memory: 16.00 MB

There were 4 errors:

1) CodeIgniter\Database\Live\UpdateTest::testUpdateBatchTwoConstraints
CodeIgniter\Database\Exceptions\DatabaseException: You are trying to use a feature which requires SQLite version 3.33 or higher.

/Users/kenji/work/codeigniter/official/CodeIgniter4/system/Database/SQLite3/Builder.php:96
/Users/kenji/work/codeigniter/official/CodeIgniter4/system/Database/BaseBuilder.php:1771
/Users/kenji/work/codeigniter/official/CodeIgniter4/system/Database/BaseBuilder.php:2411
/Users/kenji/work/codeigniter/official/CodeIgniter4/tests/system/Database/Live/UpdateTest.php:262

2) CodeIgniter\Database\Live\UpdateTest::testUpdateBatchConstraintsRawSqlAndAlias
CodeIgniter\Database\Exceptions\DatabaseException: You are trying to use a feature which requires SQLite version 3.33 or higher.

/Users/kenji/work/codeigniter/official/CodeIgniter4/system/Database/SQLite3/Builder.php:96
/Users/kenji/work/codeigniter/official/CodeIgniter4/system/Database/BaseBuilder.php:1771
/Users/kenji/work/codeigniter/official/CodeIgniter4/system/Database/BaseBuilder.php:2411
/Users/kenji/work/codeigniter/official/CodeIgniter4/tests/system/Database/Live/UpdateTest.php:307

3) CodeIgniter\Database\Live\UpdateTest::testUpdateBatchWithoutOnConstraint
CodeIgniter\Database\Exceptions\DatabaseException: You are trying to use a feature which requires SQLite version 3.33 or higher.

/Users/kenji/work/codeigniter/official/CodeIgniter4/system/Database/SQLite3/Builder.php:96
/Users/kenji/work/codeigniter/official/CodeIgniter4/system/Database/BaseBuilder.php:1771
/Users/kenji/work/codeigniter/official/CodeIgniter4/system/Database/BaseBuilder.php:2411
/Users/kenji/work/codeigniter/official/CodeIgniter4/tests/system/Database/Live/UpdateTest.php:449

4) CodeIgniter\Database\Live\UpdateTest::testRawSqlConstraint
CodeIgniter\Database\Exceptions\DatabaseException: You are trying to use a feature which requires SQLite version 3.33 or higher.

/Users/kenji/work/codeigniter/official/CodeIgniter4/system/Database/SQLite3/Builder.php:96
/Users/kenji/work/codeigniter/official/CodeIgniter4/system/Database/BaseBuilder.php:1771
/Users/kenji/work/codeigniter/official/CodeIgniter4/system/Database/BaseBuilder.php:2411
/Users/kenji/work/codeigniter/official/CodeIgniter4/tests/system/Database/Live/UpdateTest.php:481

--

There was 1 failure:

1) CodeIgniter\Database\Live\UpdateTest::testUpdateBatchUpdateFieldsAndAlias
Failed asserting that null is not null.

/Users/kenji/work/codeigniter/official/CodeIgniter4/tests/system/Database/Live/UpdateTest.php:361

ERRORS!
Tests: 17, Assertions: 19, Errors: 4, Failures: 1.

@sclubricants
Copy link
Member Author

The following tests fails on my local.

This failed because I forgot to remove the comment in SQLite Builder to return parent. The github actions runs an old version of SQLite so it didn't cause an error when uploaded here. This should be fixed now.

@sclubricants sclubricants removed the stale Pull requests with conflicts label Sep 30, 2022
Copy link
Member

@michalsn michalsn left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't see any problems... Really nice feature. Even the user guide part is polished. No complains from me this time 😅

Thanks, @sclubricants!

system/Database/BaseBuilder.php Outdated Show resolved Hide resolved
@sclubricants sclubricants force-pushed the UpsertUpsertBatch branch 2 times, most recently from ff1ac63 to 207b6a1 Compare October 6, 2022 17:42
@kenjis
Copy link
Member

kenjis commented Oct 7, 2022

Please rebase to fix PHPStan errors.

@kenjis
Copy link
Member

kenjis commented Oct 7, 2022

The following test causes an error. Is this misuse?

--- a/tests/system/Database/Live/UpsertTest.php
+++ b/tests/system/Database/Live/UpsertTest.php
@@ -234,11 +234,11 @@ final class UpsertTest extends CIUnitTestCase
         $userData = [
             'email'   => 'ahmadinejad@world.com',
             'name'    => 'Ahmadinejad',
-            'country' => 'Iran',
+            'country' => 'now()',
         ];
 
         $this->assertSame($expected, $this->db->table('user')
-            ->set($userData)
+            ->set($userData, '', false)
             ->getCompiledUpsert());
     }
 
CodeIgniter\Database\Exceptions\DatabaseException : setData() has no data.
 /Users/kenji/work/codeigniter/official/CodeIgniter4/system/Database/BaseBuilder.php:1805
 /Users/kenji/work/codeigniter/official/CodeIgniter4/system/Database/BaseBuilder.php:1900
 /Users/kenji/work/codeigniter/official/CodeIgniter4/system/Database/BaseBuilder.php:1869
 /Users/kenji/work/codeigniter/official/CodeIgniter4/tests/system/Database/Live/UpsertTest.php:242

@sclubricants
Copy link
Member Author

I have fixed your issue. It appears that when calling set() without escape binds is not used. I made some adjustments that allow for this.

I also added the ability to use setData() with upsert(). I'd like to eventually (maybe version 5) move all the calls to use setData(). set() might be more useful of a command for the SQL command SET. But by adding setData() this allows calling getCompiledUpsert() on a batch of data. I added a test for this too.

@kenjis
Copy link
Member

kenjis commented Oct 11, 2022

set() works fine:

        $data = [
            'email'   => 'ahmadinejad@example.com',
            'name'    => 'Ahmadinejad',
            'country' => 'Iran',
        ];
        $sql = $this->db->table('user')->set($data)->getCompiledUpsert();
        echo $sql;
INSERT INTO `db_user` (`country`, `email`, `name`)
VALUES ('Iran','ahmadinejad@example.com','Ahmadinejad')
ON DUPLICATE KEY UPDATE
`db_user`.`country` = VALUES(`country`),
`db_user`.`email` = VALUES(`email`),
`db_user`.`name` = VALUES(`name`)

setData() does not work:

        $data = [
            'email'   => 'ahmadinejad@example.com',
            'name'    => 'Ahmadinejad',
            'country' => 'Iran',
        ];
        $sql = $this->db->table('user')->setData($data)->getCompiledUpsert();
        echo $sql;
INSERT INTO `db_user` (0, 1, 2)
VALUES ('\'Iran\'','\'ahmadinejad@example.com\'','\'Ahmadinejad\'')
ON DUPLICATE KEY UPDATE
`db_user`.0 = VALUES(0),
`db_user`.1 = VALUES(1),
`db_user`.2 = VALUES(2)

@kenjis
Copy link
Member

kenjis commented Oct 11, 2022

        $this->db->table('user')->truncate();
        $builder = $this->db->table('user');

        $builder->set('email', 'ahmadinejad@example.com');
        $builder->set('name', 'Ahmadinejad');
        $builder->set('country', 'now()', false);
        $builder->upsert();

        echo $this->db->getLastQuery();
INSERT INTO `db_user` (`email`, `name`)
VALUES ('ahmadinejad@example.com','Ahmadinejad')
ON DUPLICATE KEY UPDATE
`db_user`.`email` = VALUES(`email`),
`db_user`.`name` = VALUES(`name`)

The country is gone.

Was using setData() where setData() had already been run. Added conditions where set() has been run setting QBSet and converted to setData().
Ok, I think I finally understand everything that is going on with the use of set(). This fixes issues with using set() with and without escape.
@sclubricants
Copy link
Member Author

The country is gone.

This helped me figure out what was going on. set() without escape saves data to QBSet. set() with escape saves data to binds. So I changed the code to merge the two sources. setData() doesn't allow using escape with one item and then not with the next. So I merged the two from set() and used RawSql for the items to remain unescaped and then call setData().

@michalsn
Copy link
Member

Can we add the examples that kenjis provided to our test cases? I think that would be very helpful.

@sclubricants
Copy link
Member Author

Can we add the examples that kenjis provided to our test cases? I think that would be very helpful.

I previously adjusted a test to cover one scenario and also added a test for the use of multiple set() calls.

@kenjis
Copy link
Member

kenjis commented Oct 14, 2022

set() without escape saves data to QBSet. set() with escape saves data to binds. So I changed the code to merge the two sources. setData() doesn't allow using escape with one item and then not with the next. So I merged the two from set() and used RawSql for the items to remain unescaped and then call setData().

I am not a fan of this process of retrieving and changing data once it has been set,
but it seems to work.

@sclubricants
Copy link
Member Author

I am not a fan of this process of retrieving and changing data once it has been set, but it seems to work.

Id like to maybe refactor the set() method sometime and the methods that use it and make the data structure the same as with setData(). Then maybe rework binds a bit perhaps having it as an optional setting. It would just be nice to have all setting of data funnel though the same method. But this is for another day.

@kenjis kenjis merged commit 447cab6 into codeigniter4:4.3 Oct 14, 2022
@kenjis
Copy link
Member

kenjis commented Oct 14, 2022

Thank you for this big contribution!

@kenjis
Copy link
Member

kenjis commented Oct 14, 2022

After I've merged, I created two issues.

@kenjis
Copy link
Member

kenjis commented Oct 15, 2022

UpsertTest::testUpsertWithMultipleSet failed.
Created #6691

@MGatner
Copy link
Member

MGatner commented Oct 15, 2022

🥳🕺👍

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
4.3 database Issues or pull requests that affect the database layer enhancement PRs that improve existing functionalities
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants