Skip to content

Commit

Permalink
builtins: mark some pg_.* builtins as strict
Browse files Browse the repository at this point in the history
Builtins defined using the UDF `Body` field will be wrapped in a `CASE`
expression if they are strict, i.e., `CalledOnNullInput=false`. When the
builtin is inlined, the `CASE` expression prevents decorrelation,
leaving a slow apply-join in the query plan. This caused a significant
regression of some ORM introspection queries.

Some of these builtins have filters that cause the SQL body to return no rows
if any of the arguments is NULL. In this case, the builtin will have the same
behavior whether or not it is defined as being strict. We can safely optimize
these builtins by setting `CalledOnNullInput=true`.

The following conditions are sufficient to prove that `CalledOnNullInput` can
be set for a builtin function with a SQL body:

  1. The WHERE clause of the SQL query *null-rejects* every argument of the
     builtin. Operators like `=` and `<` *null-reject* their operands because
     they filter rows for which an operand is NULL.

  2. The arguments are not used elsewhere in the query. This is not strictly
     necessary, but simplifies the proof because it ensures NULL arguments will
     not cause the builtin to error.

Examples of SQL statements that would allow `CalledOnNullInput` to be set:
```
SELECT * FROM tab WHERE $1=1 AND $2='two';

SELECT * FROM tab WHERE $1 > 0;
```

Fixes #96218
Fixes #95569

Epic: None

Release note: None
  • Loading branch information
mgartner authored and DrewKimball committed Feb 24, 2023
1 parent 0d3393b commit 649f219
Show file tree
Hide file tree
Showing 7 changed files with 481 additions and 386 deletions.
4 changes: 2 additions & 2 deletions pkg/bench/rttanalysis/testdata/benchmark_expectations
Original file line number Diff line number Diff line change
Expand Up @@ -64,8 +64,8 @@ exp,benchmark
4,ORMQueries/django_column_introspection_1_table
4,ORMQueries/django_column_introspection_4_tables
4,ORMQueries/django_column_introspection_8_tables
3,ORMQueries/django_table_introspection_1_table
3,ORMQueries/django_table_introspection_8_tables
5,ORMQueries/django_table_introspection_1_table
5,ORMQueries/django_table_introspection_8_tables
0,ORMQueries/has_column_privilege_using_attnum
0,ORMQueries/has_column_privilege_using_column_name
0,ORMQueries/has_schema_privilege
Expand Down
173 changes: 92 additions & 81 deletions pkg/sql/opt/xform/testdata/external/activerecord
Original file line number Diff line number Diff line change
Expand Up @@ -118,98 +118,109 @@ sort
├── columns: attname:2!null format_type:75 pg_get_expr:76 attnotnull:13!null atttypid:3!null atttypmod:9!null collname:77 comment:85 [hidden: attnum:6!null]
├── stable
├── key: (6)
├── fd: (6)-->(2,3,9,13,75-77,85), (2)-->(3,6,9,13,75-77), (3,9)-->(75)
├── fd: (6)-->(2,3,9,13,75-77,85), (2)-->(3,6,9,13,75-77,85), (3,9)-->(75)
├── ordering: +6
└── project
├── columns: format_type:75 pg_get_expr:76 collname:77 comment:85 attname:2!null atttypid:3!null attnum:6!null atttypmod:9!null attnotnull:13!null
├── stable
├── key: (6)
├── fd: (6)-->(2,3,9,13,75-77,85), (2)-->(3,6,9,13,75-77), (3,9)-->(75)
├── right-join (hash)
│ ├── columns: attrelid:1!null attname:2!null atttypid:3!null attnum:6!null atttypmod:9!null attnotnull:13!null attisdropped:17!null attcollation:20!null adrelid:27 adnum:28 adbin:29 c.oid:33 c.collname:34 t.oid:42 typcollation:69
├── fd: (6)-->(2,3,9,13,75-77,85), (2)-->(3,6,9,13,75-77,85), (3,9)-->(75)
├── distinct-on
│ ├── columns: attname:2!null atttypid:3!null attnum:6!null atttypmod:9!null attnotnull:13!null adrelid:27 adbin:29 c.collname:34 c.comment:84
│ ├── grouping columns: attnum:6!null
│ ├── immutable
│ ├── key: (6)
│ ├── fd: ()-->(1,17), (6)-->(2,3,9,13,20,27-29,33,34,42,69), (2)-->(3,6,9,13,20), (28)-->(29), (33)-->(34), (42)-->(69)
│ ├── inner-join (cross)
│ │ ├── columns: c.oid:33!null c.collname:34!null t.oid:42!null typcollation:69!null
│ │ ├── key: (33,42)
│ │ ├── fd: (33)-->(34), (42)-->(69)
│ │ ├── scan pg_collation@pg_collation_name_enc_nsp_index [as=c]
│ │ │ ├── columns: c.oid:33!null c.collname:34!null
│ │ │ ├── key: (33)
│ │ │ └── fd: (33)-->(34)
│ │ ├── scan pg_type [as=t]
│ │ │ ├── columns: t.oid:42!null typcollation:69!null
│ │ │ ├── key: (42)
│ │ │ └── fd: (42)-->(69)
│ │ └── filters
│ │ └── c.oid:33 != typcollation:69 [outer=(33,69), constraints=(/33: (/NULL - ]; /69: (/NULL - ])]
│ ├── left-join (lookup pg_attrdef [as=d])
│ │ ├── columns: attrelid:1!null attname:2!null atttypid:3!null attnum:6!null atttypmod:9!null attnotnull:13!null attisdropped:17!null attcollation:20!null adrelid:27 adnum:28 adbin:29
│ │ ├── key columns: [26] = [26]
│ │ ├── lookup columns are key
│ │ ├── key: (6)
│ │ ├── fd: ()-->(1,17), (6)-->(2,3,9,13,20,27-29), (2)-->(3,6,9,13,20), (28)-->(29)
│ │ ├── left-join (lookup pg_attrdef@pg_attrdef_adrelid_adnum_index [as=d])
│ │ │ ├── columns: attrelid:1!null attname:2!null atttypid:3!null attnum:6!null atttypmod:9!null attnotnull:13!null attisdropped:17!null attcollation:20!null d.oid:26 adrelid:27 adnum:28
│ │ │ ├── key columns: [1 6] = [27 28]
│ │ │ ├── lookup columns are key
│ ├── fd: (6)-->(2,3,9,13,27,29,34,84), (2)-->(3,6,9,13,27,29,34)
│ ├── right-join (hash)
│ │ ├── columns: attrelid:1!null attname:2!null atttypid:3!null attnum:6!null atttypmod:9!null attnotnull:13!null adrelid:27 adbin:29 c.collname:34 type:81 object_id:82 sub_id:83 c.comment:84 column86:86!null
│ │ ├── immutable
│ │ ├── fd: ()-->(1,86), (6)-->(2,3,9,13,27,29,34), (2)-->(3,6,9,13,27,29,34)
│ │ ├── select
│ │ │ ├── columns: type:81!null object_id:82!null sub_id:83!null c.comment:84!null
│ │ │ ├── fd: ()-->(81)
│ │ │ ├── scan comments [as=c]
│ │ │ │ └── columns: type:81!null object_id:82!null sub_id:83!null c.comment:84!null
│ │ │ └── filters
│ │ │ ├── type:81 = 2 [outer=(81), constraints=(/81: [/2 - /2]; tight), fd=()-->(81)]
│ │ │ └── sub_id:83 != 0 [outer=(83), constraints=(/83: (/NULL - /-1] [/1 - ]; tight)]
│ │ ├── project
│ │ │ ├── columns: column86:86!null attrelid:1!null attname:2!null atttypid:3!null attnum:6!null atttypmod:9!null attnotnull:13!null adrelid:27 adbin:29 c.collname:34
│ │ │ ├── immutable
│ │ │ ├── key: (6)
│ │ │ ├── fd: ()-->(1,17), (6)-->(2,3,9,13,20,26-28), (2)-->(3,6,9,13,20), (26)-->(27,28), (27,28)-->(26)
│ │ │ ├── select
│ │ │ │ ├── columns: attrelid:1!null attname:2!null atttypid:3!null attnum:6!null atttypmod:9!null attnotnull:13!null attisdropped:17!null attcollation:20!null
│ │ │ ├── fd: ()-->(1,86), (6)-->(2,3,9,13,27,29,34), (2)-->(3,6,9,13,27,29,34)
│ │ │ ├── right-join (hash)
│ │ │ │ ├── columns: attrelid:1!null attname:2!null atttypid:3!null attnum:6!null atttypmod:9!null attnotnull:13!null attisdropped:17!null attcollation:20!null adrelid:27 adnum:28 adbin:29 c.oid:33 c.collname:34 t.oid:42 typcollation:69
│ │ │ │ ├── key: (6)
│ │ │ │ ├── fd: ()-->(1,17), (6)-->(2,3,9,13,20), (2)-->(3,6,9,13,20)
│ │ │ │ ├── scan pg_attribute [as=a]
│ │ │ │ │ ├── columns: attrelid:1!null attname:2!null atttypid:3!null attnum:6!null atttypmod:9!null attnotnull:13!null attisdropped:17!null attcollation:20!null
│ │ │ │ │ ├── constraint: /1/6: [/numbers/1 - /numbers]
│ │ │ │ ├── fd: ()-->(1,17), (6)-->(2,3,9,13,20,27-29,33,34,42,69), (2)-->(3,6,9,13,20), (28)-->(29), (33)-->(34), (42)-->(69)
│ │ │ │ ├── inner-join (cross)
│ │ │ │ │ ├── columns: c.oid:33!null c.collname:34!null t.oid:42!null typcollation:69!null
│ │ │ │ │ ├── key: (33,42)
│ │ │ │ │ ├── fd: (33)-->(34), (42)-->(69)
│ │ │ │ │ ├── scan pg_collation@pg_collation_name_enc_nsp_index [as=c]
│ │ │ │ │ │ ├── columns: c.oid:33!null c.collname:34!null
│ │ │ │ │ │ ├── key: (33)
│ │ │ │ │ │ └── fd: (33)-->(34)
│ │ │ │ │ ├── scan pg_type [as=t]
│ │ │ │ │ │ ├── columns: t.oid:42!null typcollation:69!null
│ │ │ │ │ │ ├── key: (42)
│ │ │ │ │ │ └── fd: (42)-->(69)
│ │ │ │ │ └── filters
│ │ │ │ │ └── c.oid:33 != typcollation:69 [outer=(33,69), constraints=(/33: (/NULL - ]; /69: (/NULL - ])]
│ │ │ │ ├── left-join (lookup pg_attrdef [as=d])
│ │ │ │ │ ├── columns: attrelid:1!null attname:2!null atttypid:3!null attnum:6!null atttypmod:9!null attnotnull:13!null attisdropped:17!null attcollation:20!null adrelid:27 adnum:28 adbin:29
│ │ │ │ │ ├── key columns: [26] = [26]
│ │ │ │ │ ├── lookup columns are key
│ │ │ │ │ ├── key: (6)
│ │ │ │ │ └── fd: ()-->(1), (6)-->(2,3,9,13,17,20), (2)-->(3,6,9,13,17,20)
│ │ │ │ │ ├── fd: ()-->(1,17), (6)-->(2,3,9,13,20,27-29), (2)-->(3,6,9,13,20), (28)-->(29)
│ │ │ │ │ ├── left-join (lookup pg_attrdef@pg_attrdef_adrelid_adnum_index [as=d])
│ │ │ │ │ │ ├── columns: attrelid:1!null attname:2!null atttypid:3!null attnum:6!null atttypmod:9!null attnotnull:13!null attisdropped:17!null attcollation:20!null d.oid:26 adrelid:27 adnum:28
│ │ │ │ │ │ ├── key columns: [1 6] = [27 28]
│ │ │ │ │ │ ├── lookup columns are key
│ │ │ │ │ │ ├── key: (6)
│ │ │ │ │ │ ├── fd: ()-->(1,17), (6)-->(2,3,9,13,20,26-28), (2)-->(3,6,9,13,20), (26)-->(27,28), (27,28)-->(26)
│ │ │ │ │ │ ├── select
│ │ │ │ │ │ │ ├── columns: attrelid:1!null attname:2!null atttypid:3!null attnum:6!null atttypmod:9!null attnotnull:13!null attisdropped:17!null attcollation:20!null
│ │ │ │ │ │ │ ├── key: (6)
│ │ │ │ │ │ │ ├── fd: ()-->(1,17), (6)-->(2,3,9,13,20), (2)-->(3,6,9,13,20)
│ │ │ │ │ │ │ ├── scan pg_attribute [as=a]
│ │ │ │ │ │ │ │ ├── columns: attrelid:1!null attname:2!null atttypid:3!null attnum:6!null atttypmod:9!null attnotnull:13!null attisdropped:17!null attcollation:20!null
│ │ │ │ │ │ │ │ ├── constraint: /1/6: [/numbers/1 - /numbers]
│ │ │ │ │ │ │ │ ├── key: (6)
│ │ │ │ │ │ │ │ └── fd: ()-->(1), (6)-->(2,3,9,13,17,20), (2)-->(3,6,9,13,17,20)
│ │ │ │ │ │ │ └── filters
│ │ │ │ │ │ │ └── NOT attisdropped:17 [outer=(17), constraints=(/17: [/false - /false]; tight), fd=()-->(17)]
│ │ │ │ │ │ └── filters
│ │ │ │ │ │ ├── adrelid:27 = numbers [outer=(27), constraints=(/27: [/numbers - /numbers]; tight), fd=()-->(27)]
│ │ │ │ │ │ └── adnum:28 > 0 [outer=(28), constraints=(/28: [/1 - ]; tight)]
│ │ │ │ │ └── filters (true)
│ │ │ │ └── filters
│ │ │ │ └── NOT attisdropped:17 [outer=(17), constraints=(/17: [/false - /false]; tight), fd=()-->(17)]
│ │ │ └── filters
│ │ │ ├── adrelid:27 = numbers [outer=(27), constraints=(/27: [/numbers - /numbers]; tight), fd=()-->(27)]
│ │ │ └── adnum:28 > 0 [outer=(28), constraints=(/28: [/1 - ]; tight)]
│ │ └── filters (true)
│ └── filters
│ ├── c.oid:33 = attcollation:20 [outer=(20,33), constraints=(/20: (/NULL - ]; /33: (/NULL - ]), fd=(20)==(33), (33)==(20)]
│ └── t.oid:42 = atttypid:3 [outer=(3,42), constraints=(/3: (/NULL - ]; /42: (/NULL - ]), fd=(3)==(42), (42)==(3)]
│ │ │ │ ├── c.oid:33 = attcollation:20 [outer=(20,33), constraints=(/20: (/NULL - ]; /33: (/NULL - ]), fd=(20)==(33), (33)==(20)]
│ │ │ │ └── t.oid:42 = atttypid:3 [outer=(3,42), constraints=(/3: (/NULL - ]; /42: (/NULL - ]), fd=(3)==(42), (42)==(3)]
│ │ │ └── projections
│ │ │ └── attrelid:1::INT8 [as=column86:86, outer=(1), immutable]
│ │ └── filters
│ │ ├── column86:86 = object_id:82 [outer=(82,86), constraints=(/82: (/NULL - ]; /86: (/NULL - ]), fd=(82)==(86), (86)==(82)]
│ │ ├── sub_id:83 = attnum:6 [outer=(6,83), constraints=(/6: (/NULL - ]; /83: (/NULL - ]), fd=(6)==(83), (83)==(6)]
│ │ └── attrelid:1 < 4294966993 [outer=(1), constraints=(/1: (/NULL - /4294966992]; tight)]
│ └── aggregations
│ ├── const-agg [as=attname:2, outer=(2)]
│ │ └── attname:2
│ ├── const-agg [as=atttypid:3, outer=(3)]
│ │ └── atttypid:3
│ ├── const-agg [as=atttypmod:9, outer=(9)]
│ │ └── atttypmod:9
│ ├── const-agg [as=attnotnull:13, outer=(13)]
│ │ └── attnotnull:13
│ ├── const-agg [as=adrelid:27, outer=(27)]
│ │ └── adrelid:27
│ ├── const-agg [as=adbin:29, outer=(29)]
│ │ └── adbin:29
│ ├── const-agg [as=c.collname:34, outer=(34)]
│ │ └── c.collname:34
│ └── first-agg [as=c.comment:84, outer=(84)]
│ └── c.comment:84
└── projections
├── format_type(atttypid:3, atttypmod:9) [as=format_type:75, outer=(3,9), stable]
├── pg_get_expr(adbin:29, adrelid:27) [as=pg_get_expr:76, outer=(27,29), stable]
├── c.collname:34 [as=collname:77, outer=(34)]
└── case [as=comment:85, outer=(1,6), immutable, correlated-subquery]
├── true
├── when
│ ├── (attnum:6 IS NULL) OR (attrelid:1 IS NULL)
│ └── CAST(NULL AS STRING)
└── subquery
└── project
├── columns: c.comment:84!null
├── outer: (1,6)
├── cardinality: [0 - 1]
├── immutable
├── key: ()
├── fd: ()-->(84)
└── limit
├── columns: type:81!null object_id:82!null sub_id:83!null c.comment:84!null
├── outer: (1,6)
├── cardinality: [0 - 1]
├── immutable
├── key: ()
├── fd: ()-->(81-84)
├── select
│ ├── columns: type:81!null object_id:82!null sub_id:83!null c.comment:84!null
│ ├── outer: (1,6)
│ ├── immutable
│ ├── fd: ()-->(81-83)
│ ├── limit hint: 1.00
│ ├── scan comments [as=c]
│ │ ├── columns: type:81!null object_id:82!null sub_id:83!null c.comment:84!null
│ │ └── limit hint: 300.00
│ └── filters
│ ├── type:81 = 2 [outer=(81), constraints=(/81: [/2 - /2]; tight), fd=()-->(81)]
│ ├── object_id:82 = attrelid:1::INT8 [outer=(1,82), immutable, constraints=(/82: (/NULL - ]), fd=(1)-->(82)]
│ ├── sub_id:83 = attnum:6 [outer=(6,83), constraints=(/6: (/NULL - ]; /83: (/NULL - ]), fd=(6)==(83), (83)==(6)]
│ ├── attrelid:1 < 4294966993 [outer=(1), constraints=(/1: (/NULL - /4294966992]; tight)]
│ └── attnum:6 != 0 [outer=(6), constraints=(/6: (/NULL - /-1] [/1 - ]; tight)]
└── 1
└── c.comment:84 [as=comment:85, outer=(84)]
Loading

0 comments on commit 649f219

Please sign in to comment.