-
Notifications
You must be signed in to change notification settings - Fork 1.9k
insertBatch updateBatch upsertBatch deleteBatch from query #6689
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
insertBatch updateBatch upsertBatch deleteBatch from query #6689
Conversation
@codeigniter4/database-team This is the last major piece for now. Please review. |
eb61658
to
9eeefad
Compare
Perhaps it might be better to not pass a query through the $set parameter of th *Batch() methods and rely on using the fromQuery() method only. The $set parameter is really the same as in setData(). |
It should be noted that this design works on the assumption that the columns in the query are aliased to match those of the target table. While SQL usually works based on position and doesn't require the column names to match it is usually good practice as queries are much more readable. It also makes it much simpler to handle an upsert scenario where the target and source share the same name.. otherwise the developer would have to map them separately. |
ab4e5eb
to
3768583
Compare
3768583
to
0ed47a5
Compare
Example: This is valid SQL: INSERT INTO pageview (user_id, user_name, last_view)
SELECT id, name, created_date
FROM user
ON DUPLICATE KEY UPDATE
SET user_id = VALUES(id),
user_name = VALUES(name),
last_view = VALUES(created_date) The problem with this is that the fields would have to be mapped: This PR will assume this is done like: INSERT INTO pageview (user_id, user_name, last_view)
SELECT id AS user_id, name AS user_name, created_date AS last_view
FROM user
ON DUPLICATE KEY UPDATE
SET user_id = VALUES(user_id ),
user_name = VALUES(user_name ),
last_view = VALUES(last_view ) This way the mapping is done in SQL and allows to easily generate the SQL statements. The code will parse the field names from the select statement and use them to generate the other parts of the statement. |
Sorry, we reorganized the changelog v4.3.0. |
d5b087d
to
71bd85c
Compare
system/Database/BaseBuilder.php
Outdated
* | ||
* @param BaseBuilder|RawSql|string $query | ||
*/ | ||
public function fromQuery($query): BaseBuilder |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do we really need this public method?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I prefer to call the method directly rather than pass it in the *Batch function. I think it is easier to read the code when you do it this way.
$this->db->table('user')->insertBatch($query);
$this->db->table('user')->fromQuery($query)->insertBatch();
$this->db->table('user')->setData($data)->insertBatch();
I also debated weather or not I should accept queries in the $set parameter of the *Batch() methods. $set was really intended as the parameter for setData(). But it works passing query in *Batch() methods.
At some point It might be useful for other methods as well. We could always change it to public later I suppose though.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This may be a dumb question, but do we need this new method? I mean.. we have fromSubquery()
- shouldn't this be handled by this method somehow? It doesn't look good from the API perspective.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
fromSubquery()
works a bit differently. It sets the query to QBFrom[0] . fromQuery()
sets the query to QBOptions['fromQuery']
and QBKeys
to the columns.
One option might be to move the fromQuery()
logic to setData()
. This kind of makes sense programming wise.. the query is an alternate to a dataset. The only problem might be is that people might try and use it where it is not supported.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The following API seems better if it is possible, because we have fromSubquery()
already:
$this->db->table('user')->fromSubquery($query)->insertBatch();
fromSubquery() works a bit differently. It sets the query to QBFrom[0] . fromQuery() sets the query to QBOptions['fromQuery'] and QBKeys to the columns.
It is just internal implementation. We can change the implementation.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
setDataAsQuery()
This is what setData()
actually does.
fromQuery()
was ok but it might get confused with fromSubQuery()
In the future I'd like to have a method that is something like queryAsTable()
This would be similar to what fromSubQuery()
does in that it puts the query in parenthesis and adds an alias. Basically the results of this function could go anywhere a table name could go. This method could be used instead of fromSubQuery()
by just setting table()
. It could also go in join()
.
setQueryAsData()
is the only other option I see that might make sense.
One thing to keep in mind is that the API language doesn't have to necessarily follow the SQL. I mean for the most part yes it is nice but with some of these more advanced SQL statements they vary with each DBMS. I liked fromQuery() because it seemed clear that the data was coming from the query.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Both fromQuery()
and setQueryAsData()
seem right to me because they describe well what will happen.
The only disadvantage of fromQuery()
is that it has a very similar name to an already existing method, and people may be confused about why they're working differently.
For that reason, I would go with setQueryAsData()
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For that reason, I would go with setQueryAsData().
I agree. I think this clearly presents itself as an alternative to setData().
I changed everything to this.
@kenjis how do you feel about this?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
setQueryAsData()
is much better than setData()
and setQuery()
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Normally with *Batch you use setData(). setQueryAsData() is a fitting alternative. It is self explanatory.
@codeigniter4/database-team My opinion is: |
If I specify two queries as follows, how should QueryBuilder behave? $builder->setQueryAsData($query1)->onConstraint('email')
->updateFields($additionalUpdateField, true)->upsertBatch($query2) |
I think this is pretty straightforward - this would be the equivalent of:
So only The same story is with But... from the other side, *Batch() methods would be the only ones that allow this type of data, leading us to inconsistency. From that point of view, I think I agree with @kenjis. |
Yes, with, let's say But with a |
Co-authored-by: kenjis <[email protected]>
Co-authored-by: kenjis <[email protected]>
I think that the singularity of the method's name suggests that it can only be used once. Its not I have removed the |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM!
🥳🎉🎊🕺 |
This PR adds the final piece of some changes to BaseBuilder.
This adds the ability to insert, update, upsert from a query rather than a set of data.
Much of the required pieces of this PR were already in place.
Checklist: