Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
68 changes: 68 additions & 0 deletions pkg/sql/logictest/testdata/logic_test/generic
Original file line number Diff line number Diff line change
Expand Up @@ -622,3 +622,71 @@ quality of service: regular

statement ok
DEALLOCATE p

# Regression test for #158945. Placeholder scans should correctly handle NULL
# placeholder values.
statement ok
CREATE TABLE t158945 (
k INT PRIMARY KEY,
a INT,
b INT,
UNIQUE (a),
INDEX (b)
)

statement ok
INSERT INTO t158945 VALUES (1, NULL, NULL)

statement ok
PREPARE p158945a AS SELECT k FROM t158945 WHERE a = $1

query empty
EXECUTE p158945a(NULL)

query T
EXPLAIN ANALYZE EXECUTE p158945a(NULL)
----
planning time: 10µs
execution time: 100µs
distribution: <hidden>
vectorized: <hidden>
plan type: generic, reused
maximum memory usage: <hidden>
DistSQL network usage: <hidden>
regions: <hidden>
isolation level: serializable
priority: normal
quality of service: regular
·
• norows
sql nodes: <hidden>
regions: <hidden>
actual row count: 0
execution time: 0µs

statement ok
PREPARE p158945b AS SELECT k FROM t158945 WHERE a = $1

query empty
EXECUTE p158945b(NULL)

query T
EXPLAIN ANALYZE EXECUTE p158945b(NULL)
----
planning time: 10µs
execution time: 100µs
distribution: <hidden>
vectorized: <hidden>
plan type: generic, reused
maximum memory usage: <hidden>
DistSQL network usage: <hidden>
regions: <hidden>
isolation level: serializable
priority: normal
quality of service: regular
·
• norows
sql nodes: <hidden>
regions: <hidden>
actual row count: 0
execution time: 0µs
20 changes: 16 additions & 4 deletions pkg/sql/opt/exec/execbuilder/relational.go
Original file line number Diff line number Diff line change
Expand Up @@ -996,15 +996,27 @@ func (b *Builder) buildPlaceholderScan(
values := make([]tree.Datum, len(scan.Span))
for i, expr := range scan.Span {
// The expression is either a placeholder or a constant.
var val tree.Datum
if p, ok := expr.(*memo.PlaceholderExpr); ok {
val, err := eval.Expr(b.ctx, b.evalCtx, p.Value)
val, err = eval.Expr(b.ctx, b.evalCtx, p.Value)
if err != nil {
return execPlan{}, colOrdMap{}, err
}
values[i] = val
} else {
values[i] = memo.ExtractConstDatum(expr)
}
val = memo.ExtractConstDatum(expr)
}
if val == tree.DNull {
// If any value is NULL, then build an empty values operator instead
// of a scan. No row can satisfy the equality filter that was used
// to build this placeholder scan, because of SQL NULL-equality
// semantics.
return b.buildValues(&memo.ValuesExpr{
ValuesPrivate: memo.ValuesPrivate{
Cols: scan.Cols.ToList(),
},
})
}
values[i] = val
}

key := constraint.MakeCompositeKey(values...)
Expand Down
8 changes: 5 additions & 3 deletions pkg/sql/opt/ops/relational.opt
Original file line number Diff line number Diff line change
Expand Up @@ -107,9 +107,11 @@ define ScanPrivate {
}

# PlaceholderScan is a special variant of Scan. It scans exactly one span of a
# non-inverted index, and the span has the same start and end key. The values
# for the span key are child scalar operators which are either constants or
# placeholders. This operator evaluates the placeholders at execbuild time.
# non-inverted index, and the span has the same start and end key. The span key
# is built at execbuild-time. Each scalar expression in the Span list
# corresponds to a column in the index's key prefix, assuming SQL-equality
# semantics (e.g., NULL is not equal to NULL). The scalar expressions are either
# constants or placeholders. Placeholders are evaluated at execbuild-time.
#
# PlaceholderScan cannot have a Constraint or InvertedConstraint.
[Relational]
Expand Down