Skip to content

Commit 9f18b77

Browse files
feat(parser): support CALLED ON NULL INPUT function property (#7792)
* feat(parser): parse CALLED ON NULL INPUT as a property instead of Command * ruff order imports * refactor Co-authored-by: Giorgos Michas <geomichas96@gmail.com> * fix test * Update tests/dialects/test_snowflake.py Co-authored-by: Giorgos Michas <geomichas96@gmail.com> * update test --------- Co-authored-by: Giorgos Michas <geomichas96@gmail.com>
1 parent ca9d165 commit 9f18b77

6 files changed

Lines changed: 29 additions & 13 deletions

File tree

sqlglot/expressions/properties.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,8 @@
55
import typing as t
66
from enum import auto
77

8+
from sqlglot.expressions.core import ColumnConstraintKind, Expression, Literal, convert
89
from sqlglot.helper import AutoName
9-
from sqlglot.expressions.core import Expression, ColumnConstraintKind, Literal, convert
1010

1111

1212
class Property(Expression):
@@ -63,6 +63,10 @@ class BlockCompressionProperty(Property):
6363
}
6464

6565

66+
class CalledOnNullInputProperty(Property):
67+
arg_types = {}
68+
69+
6670
class CatalogProperty(Property):
6771
arg_types = {}
6872

sqlglot/generator.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -149,6 +149,7 @@ class Generator:
149149
exp.CaseSpecificColumnConstraint: lambda _, e: (
150150
f"{'NOT ' if e.args.get('not_') else ''}CASESPECIFIC"
151151
),
152+
exp.CalledOnNullInputProperty: lambda *_: "CALLED ON NULL INPUT",
152153
exp.Ceil: lambda self, e: self.ceil_floor(e),
153154
exp.CharacterSetColumnConstraint: lambda self, e: f"CHARACTER SET {self.sql(e, 'this')}",
154155
exp.CharacterSetProperty: lambda self, e: (
@@ -677,6 +678,7 @@ class Generator:
677678
exp.AutoRefreshProperty: exp.Properties.Location.POST_SCHEMA,
678679
exp.BackupProperty: exp.Properties.Location.POST_SCHEMA,
679680
exp.BlockCompressionProperty: exp.Properties.Location.POST_NAME,
681+
exp.CalledOnNullInputProperty: exp.Properties.Location.POST_SCHEMA,
680682
exp.CatalogProperty: exp.Properties.Location.POST_CREATE,
681683
exp.CharacterSetProperty: exp.Properties.Location.POST_SCHEMA,
682684
exp.ChecksumProperty: exp.Properties.Location.POST_NAME,

sqlglot/parser.py

Lines changed: 15 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,9 @@
44
import logging
55
import re
66
import typing as t
7+
from builtins import type as Type
78
from collections import defaultdict
9+
from collections.abc import Sequence
810

911
from sqlglot import exp
1012
from sqlglot.errors import (
@@ -17,20 +19,17 @@
1719
)
1820
from sqlglot.expressions import apply_index_offset
1921
from sqlglot.helper import ensure_list, i64, seq_get
20-
from sqlglot.trie import new_trie
2122
from sqlglot.time import format_time
2223
from sqlglot.tokens import Token, Tokenizer, TokenType
23-
from sqlglot.trie import TrieResult, in_trie
24-
from collections.abc import Sequence
25-
from builtins import type as Type
24+
from sqlglot.trie import TrieResult, in_trie, new_trie
2625

2726
if t.TYPE_CHECKING:
28-
from sqlglot.expressions import ExpOrStr
29-
from sqlglot._typing import E, BuilderArgs
30-
from sqlglot.dialects.dialect import Dialect, DialectType
31-
3227
from re import Pattern
3328

29+
from sqlglot._typing import BuilderArgs, E
30+
from sqlglot.dialects.dialect import Dialect, DialectType
31+
from sqlglot.expressions import ExpOrStr
32+
3433
T = t.TypeVar("T")
3534
TCeilFloor = t.TypeVar("TCeilFloor", exp.Ceil, exp.Floor)
3635

@@ -1236,6 +1235,7 @@ class Parser:
12361235
exp.BackupProperty(this=self._parse_var(any_token=True))
12371236
),
12381237
"BLOCKCOMPRESSION": lambda self: self._parse_blockcompression(),
1238+
"CALLED": lambda self: self._parse_called_on_null_input_property(),
12391239
"CHARSET": lambda self, **kwargs: self._parse_character_set(**kwargs),
12401240
"CHARACTER SET": lambda self, **kwargs: self._parse_character_set(**kwargs),
12411241
"CHECKSUM": lambda self: self._parse_checksum(),
@@ -2900,6 +2900,13 @@ def _parse_settings_property(self) -> exp.SettingsProperty:
29002900
exp.SettingsProperty(expressions=self._parse_csv(self._parse_assignment))
29012901
)
29022902

2903+
def _parse_called_on_null_input_property(self) -> exp.CalledOnNullInputProperty | None:
2904+
if not self._match_text_seq("ON", "NULL", "INPUT"):
2905+
self._retreat(self._index - 1)
2906+
return None
2907+
2908+
return self.expression(exp.CalledOnNullInputProperty())
2909+
29032910
def _parse_volatile_property(self) -> exp.VolatileProperty | exp.StabilityProperty:
29042911
if self._index >= 2:
29052912
pre_volatile_token = self._tokens[self._index - 2]

tests/dialects/test_postgres.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1292,8 +1292,8 @@ def test_ddl(self):
12921292
)
12931293
self.validate_identity(
12941294
"CREATE FUNCTION add(integer, integer) RETURNS integer AS 'select $1 + $2;' LANGUAGE SQL IMMUTABLE CALLED ON NULL INPUT",
1295-
check_command_warning=True,
1296-
)
1295+
"CREATE FUNCTION add(integer, integer) RETURNS INT LANGUAGE SQL IMMUTABLE CALLED ON NULL INPUT AS 'select $1 + $2;'",
1296+
).assert_is(exp.Create)
12971297
self.validate_identity(
12981298
"CREATE CONSTRAINT TRIGGER my_trigger AFTER INSERT OR DELETE OR UPDATE OF col_a, col_b ON public.my_table DEFERRABLE INITIALLY DEFERRED FOR EACH ROW EXECUTE FUNCTION DO_STH()"
12991299
)

tests/dialects/test_snowflake.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4322,7 +4322,7 @@ def test_ddl(self):
43224322
)
43234323
self.validate_identity(
43244324
"""CREATE OR REPLACE FUNCTION ibis_udfs.public.object_values("obj" OBJECT) RETURNS ARRAY LANGUAGE JAVASCRIPT RETURNS NULL ON NULL INPUT AS ' return Object.values(obj) '"""
4325-
)
4325+
).assert_is(exp.Create)
43264326
self.validate_identity(
43274327
"""CREATE OR REPLACE FUNCTION ibis_udfs.public.object_values("obj" OBJECT) RETURNS ARRAY LANGUAGE JAVASCRIPT STRICT AS ' return Object.values(obj) '"""
43284328
)
@@ -4427,7 +4427,9 @@ def test_user_defined_functions(self):
44274427
"snowflake": "CREATE FUNCTION a() RETURNS INT IMMUTABLE AS 'SELECT 1'",
44284428
},
44294429
)
4430-
4430+
self.validate_identity(
4431+
"CREATE FUNCTION a(x DOUBLE) RETURNS DOUBLE LANGUAGE SQL CALLED ON NULL INPUT AS ' x * 2 '"
4432+
).assert_is(exp.Create)
44314433
self.validate_identity(
44324434
"CREATE OR REPLACE FUNCTION repro_fn() RETURNS INT LANGUAGE PYTHON HANDLER = 'fn' RUNTIME_VERSION='3.11' PACKAGES=() AS '\\ndef fn():\\n return 1\\n'"
44334435
)

tests/fixtures/identity.sql

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -632,6 +632,7 @@ CREATE FUNCTION a.b(x INT) RETURNS INT AS RETURN x + 1
632632
CREATE FUNCTION a.b(x TEXT) RETURNS TEXT CONTAINS SQL AS RETURN x
633633
CREATE FUNCTION a.b(x TEXT) RETURNS TEXT LANGUAGE SQL MODIFIES SQL DATA AS RETURN x
634634
CREATE FUNCTION a.b(x TEXT) LANGUAGE SQL READS SQL DATA RETURNS TEXT AS RETURN x
635+
CREATE FUNCTION a(x INT) RETURNS INT LANGUAGE SQL CALLED ON NULL INPUT AS 'SELECT 1'
635636
CREATE FUNCTION a.b.c()
636637
CREATE INDEX abc ON t(a)
637638
CREATE INDEX "abc" ON t(a)

0 commit comments

Comments
 (0)