bitemyapp / bitemyapp/esqueleto
`except_` drops explicit right-associated grouping
- Dominant language
- Haskell
- Stars
- 399
- Forks
- 107
- Avg merge
- 1d 17h
- Merged PRs (30d)
- 1
Description
## Description
`SqlSetOperation` loses explicit right-associated grouping for `except_`.
Because SQL `EXCEPT` is not associative, this changes query results.
The Haskell functions currently have no fixity declarations, so an
unparenthesized chain defaults to left-associative parsing. More importantly,
even when the caller explicitly constructs a right-associated expression, the
renderer flattens the set-operation tree and omits the required SQL
parentheses.
## Behavioral reproduction
```haskell
itDb "preserves right-associated EXCEPT nesting" $ do
let singleton :: Int -> SqlQuery (SqlExpr (Value Int))
singleton n = pure $ val n
(result :: [Value Int]) <- select $ Experimental.from $
singleton 1 `except_` (singleton 1 `except_` singleton 1)
asserting $ result `shouldBe` [Value 1]
```
Expected result:
```haskell
[Value 1]
```
Actual result:
```haskell
[]
```
Semantically, the inner `1 EXCEPT 1` is empty, so the outer operation should
return `1`. The generated SQL is effectively evaluated as the left-associated
expression instead:
```sql
(SELECT 1 EXCEPT SELECT 1) EXCEPT SELECT 1
```
rather than:
```sql
SELECT 1 EXCEPT (SELECT 1 EXCEPT SELECT 1)
```
## Likely cause
`mkSetOperation` recursively renders both operands with the enclosing
`NeedParens` value and concatenates their clauses. A nested set operation does
not retain its position in the Haskell expression tree, so explicit grouping
is lost.
The fix should preserve nested set-operation grouping, particularly on the
right side of `except_` and in mixed-operator trees. It may also be worth
declaring/documenting the Haskell fixity of `except_` as non-associative so
chained uses require explicit grouping.
Found while reviewing #436; this issue is separate from that PR's identifier
allocation change and is reproducible on current `master`.
Contributor guide
No contributing guide indexed for this repository
Research direction
Start at mkSetOperation and the except_ entry point, then run the provided itDb reproduction on current master. Trace how nested operands are rendered and add or update coverage for right-associated EXCEPT and mixed-operator trees; done means explicit grouping is retained and the reproduction returns [Value 1].
Written by the indexing model from the issue text.
Assessment
- Tech stack
- haskell, sql
- Domain
- databases
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Quiet
- Clarity
- Mostly clear
- Newbie friendliness
- 64/100