Repository navigation
A boolean is coerced between condition and value in the compiler - #5
Open
housejester wants to merge 16 commits into
Open
housejester wants to merge 16 commits into
housejester wants to merge 16 commits into
Conversation
The `none` filter, the empty filter and a contradictory string filter compile to a constant, and so does `in` over an empty or null given. The constants were the keywords true and false written as strings, which a dialect without boolean values cannot run; the literals true and false already go through sqlBoolean, and now these do too. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Signed-off-by: James Estes <james.estes@credibledata.com>
… wants SQL has two places for a boolean, a condition (WHERE, ON, CASE WHEN, the operands of AND, OR and NOT) and a value, and on a dialect with no boolean type they do not mix: a comparison is legal only as a condition, a bit only as a value. The compiler carries the wanted form down the expression tree and crosses the boundary through the dialect, spells a literal in the wanted form, and writes Malloy's NULL rules for not, != and !~ through the dialect too. The translator no longer wraps a declared boolean in CASE. On every dialect with a boolean type the generated SQL is unchanged. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Signed-off-by: James Estes <james.estes@credibledata.com>
resultBoolean returns 1/0 for every booleanType but 'supported'; the matcher converted a number back to a boolean only for 'simulated'. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Signed-off-by: James Estes <james.estes@credibledata.com>
…es needs a value On a dialect with booleanType 'none' a boolean is a condition and never a value. A boolean as a result column, a function argument, a null test, in ??, in a cast, compared with = or !=, matched with a filter expression or as the result of a pick is an error at that expression, naming the value to write instead; select: * leaves a boolean field out with a warning. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01T1vFteHTo61YyRRVVrJAn4 Signed-off-by: James Estes <james.estes@credibledata.com>
…t boolean values Nothing converts a condition into a 1/0 or back: a declared boolean is its comparison, and the translator refuses a boolean where a value is wanted. Malloy's not, != and !~ are true where SQL says NULL; Dialect.sqlNot and sqlNullIsTrue spell that, as a condition on a dialect with booleanType 'none' and as today's COALESCE everywhere else. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01T1vFteHTo61YyRRVVrJAn4 Signed-off-by: James Estes <james.estes@credibledata.com>
A comparison selected as a result column is a boolean value, which a dialect without boolean values refuses. The tests whose subject was the comparison now assert it through a pick on every dialect; the tests whose subject is a boolean column are gated on booleanType. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01T1vFteHTo61YyRRVVrJAn4 Signed-off-by: James Estes <james.estes@credibledata.com>
…iew or case subject The refusal returns an error-typed value which its caller returns, so an enclosing expression stays quiet and one mistake logs one error, at the expression which made it. Two booleans compared with any operator, a boolean field named as the whole view, and a boolean case subject are refused too. The wildcard warning names a joined field by its path and never a measure; the null-test hint says what to write. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01T1vFteHTo61YyRRVVrJAn4 Signed-off-by: James Estes <james.estes@credibledata.com>
…alue A pick cannot tell false from NULL, and starts_with, ends_with, is_inf and is_nan are COALESCEd to false on purpose. The concat of a boolean is its own gated test. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01T1vFteHTo61YyRRVVrJAn4 Signed-off-by: James Estes <james.estes@credibledata.com>
…t it Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01T1vFteHTo61YyRRVVrJAn4 Signed-off-by: James Estes <james.estes@credibledata.com>
MySQL hands a boolean back as 0 or 1, so the null-input tests compare through resultBoolean as the tests they replace did, and the concat of a boolean keeps the trino and presto gate the inline case had. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01T1vFteHTo61YyRRVVrJAn4 Signed-off-by: James Estes <james.estes@credibledata.com>
…a raw function Any comparison with a boolean operand, including a boolean among the choices of an in-list, is refused at the boolean side. The raw function path checks its arguments as the overloaded path does. Unary minus logs its type error, so a filter on it no longer goes quiet. The hints live in one module, and the refusal tests check the error code and where it landed. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01T1vFteHTo61YyRRVVrJAn4 Signed-off-by: James Estes <james.estes@credibledata.com>
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01T1vFteHTo61YyRRVVrJAn4 Signed-off-by: James Estes <james.estes@credibledata.com>
ExprMinus rebuilt an error-typed operand as a number, so whatever used the result complained again; it now returns the operand unchanged, and the error-type-inference table says so. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HXm9dCuXFseFX4FS2DK1My Signed-off-by: James Estes <james.estes@credibledata.com>
On a dialect without boolean values, the expression compiler tracks whether each edge wants a value or a condition and asks the dialect to coerce where they differ: a condition becomes CASE WHEN c THEN 1 WHEN NOT (c) THEN 0 END, a value becomes v = 1. In the boolean_coercion experiment the translator no longer refuses a boolean in a value position. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HXm9dCuXFseFX4FS2DK1My Signed-off-by: James Estes <james.estes@credibledata.com>
A CASE is a value, so the branches of a pick or case are compiled as values and the whole is tested with = 1 where a condition is wanted. SQL the author wrote in a sql_ function is left as written. A boolean filter expression on a dialect without boolean values is compiled in condition form. A cast to boolean stays refused, since there is no value to make. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HXm9dCuXFseFX4FS2DK1My Signed-off-by: James Estes <james.estes@credibledata.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
On a dialect with
booleanType: 'none', the expression compiler now tracks whether each edge of the compiled expression tree wants a value or a condition, and asks the dialect to coerce where they differ: a condition where a value is wanted becomesCASE WHEN c THEN 1 WHEN NOT (c) THEN 0 END, a value where a condition is wanted becomesv = 1. On a dialect with boolean values both hooks are the identity, so nothing changes there.In the
boolean_coercionexperiment the translator stops refusing a boolean in a value position, so a model written for DuckDB runs unchanged on SQL Server; the result reader already shows the1/0astrue/false. Without the experiment the refusals from malloydata#3147 stay as they are. The pass runs either way, which is what closes the two holes in malloydata#3171 that the translator could never see:all()of a boolean measure and a boolean primary key are expressions the compiler builds itself.What a boolean compiles to, with the experiment on:
group_by: bigCASE WHEN ai>5 THEN 1 WHEN NOT (ai>5) THEN 0 END as "big"where: bigWHERE (ai>5)having: all(anyn)HAVING (MAX(CASE WHEN … END) OVER () = 1)primary_key: big, thenpk.count()COUNT(DISTINCT (CASE WHEN … END))where: bigWHERE (base."big" = 1)where: pick big when ai > 1 else falseWHERE (CASE WHEN (ai>1) THEN (CASE … END) ELSE … END = 1)where: big ~ f'not true'WHERE (CASE WHEN (ai>5) THEN 1 ELSE 0 END = 0)sql_number("""CASE WHEN ${big} THEN 1 ELSE 0 END""")Two things stay refused because there is no value to make: a cast to boolean, and (unchanged from today) arithmetic or a time operation on a boolean.
having: all(x)compiles but SQL Server rejects a windowed function inHAVINGfor anyx; that needs its own compilation and is out of scope.Testing:
boolean-coercion.spec.tsinlang/testchecks the generated SQL for each shape on the test dialect, andtest/src/databases/sqlserver/boolean-coercion.spec.tsruns them on a live SQL Server. Every shape from the fuzz corpus behind malloydata#3171, plus every refusal case from malloydata#3147, was also executed against SQL Server 2022; everything boolean-specific runs and returns the DuckDB result, and what remains failing is either a generic type error the translator already gives or a SQL Server limitation unrelated to booleans.