feat: Expression builder (WIP)#7282
Conversation
|
This is about 90% complete now and I have identified some very very significant benefits from introducing this system and its related changes:
I have also identified countless issues that can directly be solved with this: #7280, #7256, #7248, #7247, #7226, #7210, from the first 2 pages of issues alone 🎉 Amazingly I believe it may even be possible to achieve all of this without introducing any breaking changes relative to the current version! That said, a bunch of features remain that would effectively become obsolete, and would only make it more difficult to implement:
Considering this, it might be better to target the Is there a possibility that we could work towards the release of |
49ce176 to
9c04ac5
Compare
4177377 to
e421232
Compare
|
you love to see it!
Ever since looking into #4760 I was hoping someone would tackle this -- those regexes in replacePropertyNames dominate performance above a certain query rate, but the generic/blunt regex approach was necessary given the current QueryBuilder. |
e421232 to
49c0e5c
Compare
2f5e0c8 to
824115a
Compare
|
All problems you have listed are super important and it will be great if we solve them. I'm not sure I can review this PR, however I read your description. cities.find({
name: Not("Paris")
})Here |
3ea7c79 to
c69b7fc
Compare
…rm#6977) Searches embedded entity columns for relation ID column if relation column is in embedded entity. If not found, creates new relation ID with embedded metadata set to match the relation column. fixes: typeorm#2254 fixes: typeorm#3132 fixes: typeorm#3226 fixes: typeorm#6977
… identifier for oracle Problem introduced with typeorm#6981
Moves QueryBuilder and its subclasses to their own directory.
In preparation for "virtual"/computed columns, call internally generated columns "internal" rather than "virtual" to avoid confusion.
* Introduces new type to describe the fields parameter of an @Index or @unique. * Allows (type) => [type.column] syntax inside embedded
…yMetadatas() * Removes unnecessary array pushing/slicing * Matches buildMigrations()/buildSubscribers() more closely
…elete
* Rename ModificationQueryBuilder to AbstractModifyQueryBuilder (UPDATE/DELETE)
* New AbstractPersistQueryBuilder for INSERT/UPDATE/DELETE
* Common code for RETURNING expressions
* Common code for INSERT/UPDATE value expressions
* Move UpdateDateColumn, VersionDateColumn, etc to value expression calculation
* Matches InsertQueryBuilder behavior
refactor: driver: Move database specific EntityManagers to driver Accidentally ammended during rebase
…lags and generators Introduces `DriverConfig` and `DriverQueryGenerators` interfaces that include various flags that drivers can use to customize behavior of the built in functionality. This decouples these built in features from the drivers themselves, moving towards the possibility of having each driver in a separate package.
* InsertQueryBuilder.orUpdate() now takes 3 parameters, overwrite columns, column values and conflict condition
* Overwrite either specific property paths in array or `true` to overwrite all
* Values same as UPDATE query values, replaces overwritten columns
* Conflict see onConflict()
* InsertQueryBuilder.orIgnore() now takes 2 parameters, ignore and conflict condition
* Ignore boolean whether to add IGNORE / DO NOTHING, no longer accepts statement
* Conflict see onConflict()
* InsertQueryBuilder.onConflict() now accepts either raw expression or property paths array
* For Postgres/Sqlite ON CONFLICT ...
* EntityManager.upsert(), Repository.upsert(), BaseEntity.upsert()
Fixes: typeorm#1090
67d196a to
fc0ce38
Compare
|
Just chiming in here to say that this looks really great and would solve some particularly tricky issues I am dealing with. Has any decision been made about whether this will go in If there is any outside help you need which might expedite the merging of this work, please let me know! |
|
@michaelbromley I'm currently experimenting on how I can make it completely type-safe on my own branch. |
4ba92f0 to
d60e131
Compare
Depends on #7058 (refactor: Query builders and entity/column/relation metadata)
Necessary due to large amount of duplicate code before refactor.
Only last commit is relevant to this PR! See #7058 for rest.
Description of change
This introduces the concept of "expressions" in TypeORM that represent SQL expressions, as well as an accompanying "expression builder" system. SQL expressions are found in a variety of places, but most importantly in the
WHEREclause,INSERT VALUESandUPDATE SET. Currently onlyQueryBuilder.where()has basic support for constructing expressions usingFindOperators.Whenever "raw" expressions are used a number of major problems arise, all of which can be solved by the expression builder:
Implementation
Slightly simplified from actual implementation for better readability
Base Types
Build Context
Certain tasks of the expression builder are left to be implemented by the query builder, via an abstract
ExpressionBuildContext.Builders
Every part of an expression that isn't a literal will be constructed using builders. These are functional building blocks that combine other expressions to form the final expression in raw string form.
Builders extend the
ExpressionBuilderclass and have a function to initialize them (i.e.XYZ()instead ofnew XYZBuilder()). Some builder functions may even have tagged template literal equivalents for conciseness.Expressions will be built as the final step in constructing queries in the query builder, in the order that they appear in the query. Builders themselves will also build their sub-expressions in the order that they appear. This way, the order of native parameters will always be preserved. (#7256, #7104, #6607).
Raw
Raw unedited value that is passed as provided.
This is precisely what the expression builder is trying to avoid but it remains for users that would like to still have raw SQL access. It will also replace the
() => "RAW"syntax, which should become deprecated.Column
Explicit reference to a column name or property name/path.
If
Col()is used, it becomes a reference to the current column within a where conditions object or inset/update values object.replacePropertyNames()can be used accurately and efficiently because we know the value we are dealing with is some kind of column, as opposed to having to search the entireWHEREclause for matching property names using regular expressions.Function
Function within a query such as
IF,LENGTH, orCURRENT_TIMESTAMP. Known functions have initializers with fixed parameters, but user can also manually call an arbitrary function with arbitrary parameters.Operator
All operators in the form of
operand *operator* operand.And, Or, Xor
Find Conditions
Normally when we are dealing with entities and the
WHEREclause, we define the find conditions using an object.When we write:
It is the equivalent of
ANDing everykey = valueof the object i.e.:Comparators
However there is an exception to this rule if we are using comparison builders:
We don't want to check if
population = > 10000 AND country = = "France", butpopulation > 10000 AND country = "France". Note also that where previously there were two arguments toMoreThanandEqualthere is now only one.This is a special variant of the comparator where only one argument is given instead of two. In this variant, the first argument becomes
Col(), so that it references the current column based on the key in the object, and a flag is set to achieve the desired behavior of not checking for equality. We update the object mapping code:Which gives us the distinction between:
And, Or, Xor
One remaining special case is the ability to use
And,Or, andXorin combination with a find object (long been requested #2100).When used as part of a find conditions object,
And,Or, andXorare always distributed over their column.To avoid this behavior, there is a wrapper function:
Examples
When used with query builder all values get replaced with parameters (
?,$1)Find All
Basic Conditions
OR Conditions
Not
Nested With Column
Case Expression
Join Conditions
Update Increment
Update Reference
Column Default
createDate DATETIME DEFAULT CURRENT_TIMESTAMPPull-Request Checklist
masterbranchnpm run lintpasses with this changenpm run testpasses with this changeFixes #0000