TypeCheck now can preserve AnnotateTypeExpr to prevent invalid re-typing#164903
TypeCheck now can preserve AnnotateTypeExpr to prevent invalid re-typing#164903delphamk wants to merge 1 commit into
Conversation
|
Merging to
|
|
Thank you for contributing to CockroachDB. Please ensure you have followed the guidelines for creating a PR. Before a member of our team reviews your PR, I have some potential action items for you:
🦉 Hoot! I am a Blathers, a bot for CockroachDB. My owner is dev-inf. |
mw5h
left a comment
There was a problem hiding this comment.
Review
The core idea is sound — preserving type annotations on NULL to prevent downstream type inference from losing the user's intent. However, there are a few issues to address.
1. Hand-edited generated file (blocking)
pkg/sql/sem/tree/eval_expr_generated.go is explicitly marked:
// Code generated by eval_gen.go. DO NOT EDIT.
// Regenerate this file with the command:
// ./dev generate go
The PR hand-edits this file to add EvalAnnotateTypeExpr to the ExprEvaluator interface and the Eval method on *AnnotateTypeExpr. The correct approach is:
- Add the
typeAnnotationembedding toAnnotateTypeExprinexpr.go(which the PR does) - Run
./dev generate goto regenerateeval_expr_generated.go
The hand-edits will likely cause CI failures or be overwritten.
2. Logic condition in TypeCheck may be fragile
The new condition at type_check.go:817:
if subExpr.ResolvedType() != types.Unknown || annotateType.IsWildcardType() || cast.ValidCast(annotateType, desired, cast.ContextImplicit) {
return subExpr, nil
}The ValidCast(annotateType, desired, ...) check uses the desired type, which is the type the caller wants (e.g., what an aggregate function expects). This means the behavior of ::: depends on surrounding context, which seems fragile. Consider: what if desired is types.Any? ValidCast to Any may always succeed, stripping the annotation even when it should be preserved.
A clearer condition might be: "preserve the annotation only when the inner expression is Unknown and the annotated type differs from Unknown."
3. buildScalar handling may be too narrow
In scalar.go, the new case only preserves type for DNull:
case *tree.AnnotateTypeExpr:
texpr := t.TypedInnerExpr()
if texpr == tree.DNull {
out = b.factory.ConstructConstVal(tree.DNull, t.ResolvedType())
} else {
return b.buildScalar(texpr, inScope, outScope, outCol, colRefs)
}Could there be cases where the wrapper is preserved but the inner expression isn't literally DNull? For example, an expression that resolves to Unknown type but isn't the DNull constant. In that case, the else branch would silently discard the annotation.
4. Missing broader test coverage
The tests cover the optimizer builder (build testdata), but there are no end-to-end SQL tests verifying the actual query result. For instance, a logic test confirming:
SELECT pg_typeof(min(NULL:::FLOAT)) -- should return 'double precision'This would catch regressions at the execution level, not just the plan level.
5. nit: comment style
// return subExpr if the AnnotateTypeExpr is redundantShould be a complete sentence per Go conventions: // Return subExpr if the AnnotateTypeExpr is redundant.
|
I appreciate your comments. I’ve provided an explanation of my reasoning for the TypeCheck change and hope it’s understandable. 1) Fixed and caused by a formatting issue in
Simply, this change is looking to fix cases like 3) buildScalar logic 4) Added some more cases but can add more if needed. 5) Updated the comment but can make it more verbose if needed. Regarding my comments on RECORD casting, I have an example of what this might look like here. With this change, the IsAmbiguous should not be needed. |
18d634f to
3d14d65
Compare
|
Thank you for updating your pull request. Before a member of our team reviews your PR, I have some potential action items for you:
🦉 Hoot! I am a Blathers, a bot for CockroachDB. My owner is dev-inf. |
3d14d65 to
b611bb4
Compare
|
Thank you for updating your pull request. Before a member of our team reviews your PR, I have some potential action items for you:
🦉 Hoot! I am a Blathers, a bot for CockroachDB. My owner is dev-inf. |
Only discard AnnotateTypeExpr when its type is redundant. Example: 'min(NULL:::FLOAT)' now resolves to type FLOAT instead of STRING. Release note (sql change): Preserve type annotations on NULL expressions so aggregate functions like min() resolve to the annotated type rather than STRING. Epic: none
b611bb4 to
f3fed65
Compare
|
Thank you for updating your pull request. My owl senses detect your PR is good for review. Please keep an eye out for any test failures in CI. 🦉 Hoot! I am a Blathers, a bot for CockroachDB. My owner is dev-inf. |
|
Pushed an updated revision with rebase. Please let me know if there are any other comments. I plan on using this commit in another PR and will reference this for context. I agree that the check for 2) does not cover the full condition but it still tightens NULL annotations. As I mentioned, NULL:::RECORD is not covered yet. This change instead covers most missing NULL annotations without reducing previous cases. |
Only discard AnnotateTypeExpr when its type is redundant.
Example: 'min(NULL:::FLOAT)' now resolves to type FLOAT instead of STRING.
Epic: none