Skip to content

Commit a947e19

Browse files
committed
Enable indexing and json->scalar calls to be inlined by postgres (#8804)
Functions that are marked immutable but make calls to stable functions can't be inlined, and we have a lot of functions like that. (Because error raising is stable in postgres but we want to allow calling some functions that can error in indexes/constraints.) Fix this by marking the functions as stable but making sure that their *trampoline* wrappers are immutable, since those are what actually go into indexes/constraints.
1 parent fdc788f commit a947e19

File tree

3 files changed

+64
-12
lines changed

3 files changed

+64
-12
lines changed

edb/pgsql/metaschema.py

Lines changed: 18 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1772,7 +1772,8 @@ def __init__(self) -> None:
17721772
('detail', ('text',), "''"),
17731773
],
17741774
returns=('text',),
1775-
volatility='immutable',
1775+
volatility='stable',
1776+
wrapper_volatility='immutable',
17761777
text=self.text,
17771778
)
17781779

@@ -2006,7 +2007,8 @@ def __init__(self) -> None:
20062007
returns=('anyelement',),
20072008
# Min volatility of exception helpers and pg_typeof is 'stable',
20082009
# but for all practical purposes, we can assume 'immutable'
2009-
volatility='immutable',
2010+
volatility='stable',
2011+
wrapper_volatility='immutable',
20102012
text=self.text,
20112013
)
20122014

@@ -2035,7 +2037,8 @@ def __init__(self) -> None:
20352037
("stop", ("bigint",)),
20362038
],
20372039
returns=("anyarray",),
2038-
volatility="immutable",
2040+
volatility="stable",
2041+
wrapper_volatility='immutable',
20392042
text=self.text,
20402043
)
20412044

@@ -2073,7 +2076,8 @@ def __init__(self) -> None:
20732076
returns=('text',),
20742077
# Min volatility of exception helpers and pg_typeof is 'stable',
20752078
# but for all practical purposes, we can assume 'immutable'
2076-
volatility='immutable',
2079+
volatility='stable',
2080+
wrapper_volatility='immutable',
20772081
text=self.text,
20782082
)
20792083

@@ -2110,7 +2114,8 @@ def __init__(self) -> None:
21102114
returns=('bytea',),
21112115
# Min volatility of exception helpers and pg_typeof is 'stable',
21122116
# but for all practical purposes, we can assume 'immutable'
2113-
volatility='immutable',
2117+
volatility='stable',
2118+
wrapper_volatility='immutable',
21142119
text=self.text,
21152120
)
21162121

@@ -2222,7 +2227,7 @@ def __init__(self) -> None:
22222227
('stop', ('bigint',)),
22232228
],
22242229
returns=('text',),
2225-
volatility='immutable',
2230+
volatility='stable',
22262231
text=self.text)
22272232

22282233

@@ -2241,7 +2246,7 @@ def __init__(self) -> None:
22412246
('stop', ('bigint',)),
22422247
],
22432248
returns=('bytea',),
2244-
volatility='immutable',
2249+
volatility='stable',
22452250
text=self.text)
22462251

22472252

@@ -2299,7 +2304,8 @@ def __init__(self) -> None:
22992304
returns=('jsonb',),
23002305
# Min volatility of exception helpers 'stable',
23012306
# but for all practical purposes, we can assume 'immutable'
2302-
volatility='immutable',
2307+
volatility='stable',
2308+
wrapper_volatility='immutable',
23032309
strict=True,
23042310
text=self.text,
23052311
)
@@ -2365,7 +2371,8 @@ def __init__(self) -> None:
23652371
returns=('jsonb',),
23662372
# Min volatility of exception helpers and pg_typeof is 'stable',
23672373
# but for all practical purposes, we can assume 'immutable'
2368-
volatility='immutable',
2374+
volatility='stable',
2375+
wrapper_volatility='immutable',
23692376
strict=True,
23702377
text=self.text,
23712378
)
@@ -2417,7 +2424,8 @@ def __init__(self) -> None:
24172424
returns=("jsonb",),
24182425
# Min volatility of to_jsonb is 'stable',
24192426
# but for all practical purposes, we can assume 'immutable'
2420-
volatility="immutable",
2427+
volatility="stable",
2428+
wrapper_volatility='immutable',
24212429
text=self.text,
24222430
)
24232431

edb/pgsql/patches.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -303,4 +303,13 @@ def get_version_key(num_patches: int) -> str:
303303
USING (to_bytes(to_str(j)));
304304
};
305305
'''),
306+
('metaschema-sql', 'ArrayIndexWithBoundsFunction'),
307+
('metaschema-sql', 'ArraySliceFunction'),
308+
('metaschema-sql', 'StringIndexWithBoundsFunction'),
309+
('metaschema-sql', 'BytesIndexWithBoundsFunction'),
310+
('metaschema-sql', 'StringSliceFunction'),
311+
('metaschema-sql', 'BytesSliceFunction'),
312+
('metaschema-sql', 'JSONIndexByTextFunction'),
313+
('metaschema-sql', 'JSONIndexByIntFunction'),
314+
('metaschema-sql', 'JSONSliceFunction'),
306315
]

edb/pgsql/trampoline.py

Lines changed: 37 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,8 @@
3636
from __future__ import annotations
3737
from typing import (
3838
TYPE_CHECKING,
39+
Optional,
40+
Sequence,
3941
)
4042

4143
import abc
@@ -62,13 +64,44 @@ def fixup_query(query: str) -> str:
6264

6365

6466
class VersionedFunction(dbops.Function):
65-
if not TYPE_CHECKING:
66-
def __init__(self, *args, **kwargs):
67+
if TYPE_CHECKING:
68+
# What the volatility of the trampoline wrapper should be.
69+
# This is sometimes immutable even when the underlying is
70+
# stable, for functions that must be immutable so they can go
71+
# into indexes/constraints but might do something technically
72+
# stable (like raise an error).
73+
#
74+
# This allows the real function to be inlined while allowing
75+
# the wrapper to get used in indexes/constraints.
76+
wrapper_volatility: Optional[str]
77+
78+
def __init__(
79+
self,
80+
name: tuple[str, ...],
81+
*,
82+
args: Optional[Sequence[dbops.FunctionArg]] = None,
83+
returns: str | tuple[str, ...],
84+
text: str,
85+
volatility: str = "volatile",
86+
language: str = "sql",
87+
has_variadic: Optional[bool] = None,
88+
strict: bool = False,
89+
parallel_safe: bool = False,
90+
set_returning: bool = False,
91+
92+
wrapper_volatility: Optional[str] = None,
93+
):
94+
pass
95+
96+
else:
97+
def __init__(self, *args, wrapper_volatility=None, **kwargs):
6798
super().__init__(*args, **kwargs)
6899
self.name = (
69100
common.maybe_versioned_schema(self.name[0]), *self.name[1:])
70101
self.text = fixup_query(self.text)
71102

103+
self.wrapper_volatility = wrapper_volatility
104+
72105
if self.args:
73106
nargs = []
74107
for arg in self.args:
@@ -156,6 +189,8 @@ def make_trampoline(func: dbops.Function) -> TrampolineFunction:
156189
new_func.text = f'select {q(*func.name)}({", ".join(args)})'
157190
new_func.language = 'sql'
158191
new_func.strict = False
192+
if isinstance(func, VersionedFunction) and func.wrapper_volatility:
193+
new_func.volatility = func.wrapper_volatility
159194
return TrampolineFunction(new_func.name, new_func)
160195

161196

0 commit comments

Comments
 (0)