-
-
Notifications
You must be signed in to change notification settings - Fork 195
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
Issue #674: Fix issues with semantics of parenthesis removal #675
Conversation
Very cool! I'll look into it closely tomorrow |
I am thinking that the (notational, not necessarily mathematical) (left) associativity of a binary operator could be encoded on the binary operator in types.rs instead as a function: So that:
Becomes:
Where notationally_left_associative(&self) -> bool returns true if it is known to be notationally left associative in all the db backends (and people know about it so the parentheses would be distracting), and false otherwise, e.g. does not have notational associativity such as when chaining the comparison operators. Notational left associativity is mostly the case. Edit: I think the name should be well_known_notational_left_associativity(), since it is crucial that people find it easier to read expressions with parentheses dropped by this property. |
@tyt2y3: I will fix those failing checks once I know if the approach is in principle ok. |
Thank you. I am not sure I fully understood the notion of notational, so may be we can decide by examples? e.g. (("character" LIKE 'D') AND ("character" LIKE 'E')) # then
("character" LIKE 'D' AND "character" LIKE 'E') # now
A AND B AND C AND D # no change I am trying to form a mental model for the existing behaviour, i.e. why did we want to have parens surrounding |
Let me try to explain notational left associativity. I am using the term notational associativity, since there is also mathematical associativity which is about the results of such expressions when they are evaluated when you move parentheses around. If we zoom out a bit, we see that in the whole expression, there is actually a bit of inconsistent behaviour:
Why did sea-query remove the parentheses here? Now:
The reason the parentheses are dropped now around the binary LIKE-expressions in I could not quite see why the solution behaved inconsistently in the past. After your question I decided to look into the technical reason why. The reason sea-query dropped the parentheses earlier was the fact that the first OR is encoded as a condition, and not as a binary OR.
Having two code paths to writing the same kind of expression is not a good idea, since this opens the door to inconsistent behaviour which is hard to maintain over time (as is now becoming evident). IMHO, conditions, which are syntactic sugar should be rewritten as nested binary expressions before being written to the SQL string. Doing so can delete the whole function below, and more, which has subtle inconsistencies compared to how binary_expr works now and worked previously. I think this should be done before merging the PR.
|
Thank you for your detailed explanation. You convinced me that a revamp is needed.
This is a great idea if we can get the logic right. However, I'm concerned there is a loss of information. Consider My concern is, there are many testcases e.g. in SeaORM that we directly assert the SQL output. So I am in a position to want to minimize the impact. If we can find a point of incremental improvement, we'd be able to ship it sooner. |
Excellent!
Yes, this information would definitely be lost, and the output would be like you describe.
I will do a run of the tests in sea-orm and set the dependency to the git branch, listing any broken tests and an explanation why here. Perhaps the impact is small, and then we can have a think about the revamp. The revamp will not take much time - we are talking ETA a day or two in any case. |
Okay cool, then may be we make the changes first and see. |
in Cargo.toml: in sea-orm-codegen/Cargo.toml: All tests pass. Doing the revamp tomorrow morning. |
…ndled as SimpleExpr:Binary
Hope this didn't break too much - have tuned it so as to break as little as possible. |
It seems to be very brilliant. Allow me more time to read through the logic in details. |
Nice refactoring! |
This is indeed very brilliant! I was looking through the test cases, and trying to find an example that the old way was better. But so far, it seems like it made more sense the new way. |
@nitnelave we are working on a very interesting change that unify the Condition and BinOper query building backend. Can you take a look and review the changes? |
Great! Interested to hear what he thinks :-) |
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.
Looks nice! A couple of comments here and there, but I like the precedence-based approach. There's just one case that I commented on that seems dubious, but overall it's a great change.
Thank you @nitnelave for reviewing! |
@tyt2y3 Did you want custom expressions to always have parentheses no matter where they occur, even as the sole expr in a WHERE? At present they get parentheses whenever they are included in a different expression, this was a consequence of the changes I made, but we could play it even safer? |
I would say always have parentheses when combined with any operator. i.e. For sole expression in where may be we can leave them naked?
|
That is how it works now :-) |
Oh right! I might have confused with the change set. Should be all good now |
Here is a case of unnecessary parentheses: #[test]
fn test_sea_query_case_when_eq() {
let case_statement: SimpleExpr = Expr::case(
Expr::col(Alias::new("col")).lt(5),
Expr::col(Alias::new("othercol"))
)
.finally(Expr::col(Alias::new("finalcol")))
.into();
let result = Query::select()
.column(sea_query::Asterisk)
.from(Alias::new("tbl"))
.and_where(
case_statement.eq(10)
).to_string(PostgresQueryBuilder);
assert_eq!(result, r#"SELECT * FROM "tbl" WHERE (CASE WHEN ("col" < 5) THEN "othercol" ELSE "finalcol" END) = 10"#);
} The test fails because SELECT * FROM "tbl" WHERE ((CASE WHEN ("col" < 5) THEN "othercol" ELSE "finalcol" END)) = 10 The parentheses around the case statement got doubled (this was not the case previously) and I'm not sure why. Still, if it's not a simple fix, I'd prefer to leave it like this and be more conservative with parentheses rather than try to fix this and introduce a soundness bug. |
@prakol16 is your test case based on the latest code in this PR? |
Merged into a new branch. @prakol16 can you open a PR on |
PR Info
This is my attempt at fixing #674
Bug Fixes
Parenthesis placement is now done based on positive matches with known associativity and precedence rules, and otherwise defaults to placing parentheses.
Changes
(`character` LIKE 'D') AND (`character` LIKE 'E')
since LIKE has stronger precedence than the logical ops. Previously this happened only some times.