diff --git a/tests/fixtures/my_data.sql b/tests/fixtures/my_data.sql index ab8cca7a..cbb1d3a6 100644 --- a/tests/fixtures/my_data.sql +++ b/tests/fixtures/my_data.sql @@ -14,7 +14,7 @@ INSERT INTO "public"."my_data" ("geom" , "id", "datetime", "decimal", "numeric") INSERT INTO "public"."my_data" ("geom" , "id", "datetime", "decimal", "numeric") VALUES ('0103000020E61000000100000013000000C0155236C40A38C052F1FFE1D8C75340B244B5A16EC837C014EBB5CD0CC4534073D712F2414F37C0D3BCE3141DBD5340FE41CA2BA27737C016B27D9C8ABB5340A2728C64C30A38C03BFB4402D0B553400C6AB4D7723A3DC0BDA377861D82534058CA32C4B15E3DC062105839B48053402A2097D1F19641C0EAE96F4E58CC5340F0A7C64B379941C07F6ABC7493CC5340E11AE2531A8741C01F2670501DCE5340CED31A45F57241C03EC92059D3CF534009E08D47F1E83FC0EAC3384350F05340DFE755925F713EC036A2858243005440ACEF9DFAC14B3DC0E950B3BEBB0C544034C8A112A4243DC064CC7707650E5440F602E719D4063DC0AE877727A90F54400A68226C78FA3CC0234A7B832F105440A630DBCBFBF43CC0E22ABE1BDF0F5440C0155236C40A38C052F1FFE1D8C75340', '2', '2004-10-21 10:23:54', -5.68, -5.68); INSERT INTO "public"."my_data" ("geom" , "id", "datetime", "decimal", "numeric") VALUES ('0103000020E610000001000000110000001B2CBE53855542C051F99E0E805D534049A5CD2EAE0644C03857A7D846865340462575029A0844C0A60A46257586534063B4EEABC4F943C08D992E511D8853409C72BC6BC5E843C0920AAB5C038A5340721D3749863342C03D0220C7DABA53402A2097D1F19641C0EAE96F4E58CC5340E11AE2531A8741C01F2670501DCE534068226C787A7541C0075F984C15D05340CED31A45F57241C03EC92059D3CF534048E17A14AE173DC06B2BF697DD8353400C6AB4D7723A3DC0BDA377861D825340A03E0335AD283FC0314A54553C6953409C6F1F2DEA1541C00EA6095E6A425340BEC11726532541C0BE9F1A2FDD405340EB51B81E853342C0302C67AA4C5A53401B2CBE53855542C051F99E0E805D5340', '3', '2004-10-22 10:23:54', 98, 98); INSERT INTO "public"."my_data" ("geom" , "id", "datetime", "decimal", "numeric") VALUES ('0103000020E610000001000000110000000A4C8422590E46C0B656FB86F03B5340D5E76A2BF60F46C0075F984C153C5340FA28B2217F0346C0CE0A257ADB3D5340BEE6287052F545C01AA33BF2DF3F5340F25A937BB7D244C009CB92853C69534049A5CD2EAE0644C03857A7D84686534063B4EEABC4F943C08D992E511D88534034A2B437F8EA43C0F54A5986388A53409C72BC6BC5E843C0920AAB5C038A534050AF9465883342C0363B85F6B5605340D43E0032881142C02A5884BF7F5D5340F4FDD478E90641C007F01648504453409C6F1F2DEA1541C00EA6095E6A4253404E4E9C88873342C06DC6E4C7471E53403EDF52396E3443C0DC9EAF2DC7FD524044696FF0854143C032772D211FFC52400A4C8422590E46C0B656FB86F03B5340', '4', '2004-10-23 10:23:54', 7.55526, 7.55526); -INSERT INTO "public"."my_data" ("geom" , "id", "datetime", "decimal", "numeric") VALUES ('0103000020E6100000010000000D000000BBE9944235C347C0EBF06E7961EE52406ADE718A8EC447C0D122DBF97EEE5240942D6301ECB947C05B59871F60F0524086CAEEF61AAE47C0BDEF3BBB76F252400A4C8422590E46C0B656FB86F03B5340FA28B2217F0346C0CE0A257ADB3D534057EC2FBB27F745C02B1895D409405340BEE6287052F545C01AA33BF2DF3F53401D386744692743C07958A835CDFF52403EDF52396E3443C0DC9EAF2DC7FD5240B9E39237FD0645C0574B4E2543B552400AD7A3703D1245C03A234A7B83B35240BBE9944235C347C0EBF06E7961EE5240', '5', '2004-10-24 10:23:54', -78.56, -78.56); +INSERT INTO "public"."my_data" ("geom" , "id", "datetime", "decimal", "numeric") VALUES ('0103000020E6100000010000000D000000BBE9944235C347C0EBF06E7961EE52406ADE718A8EC447C0D122DBF97EEE5240942D6301ECB947C05B59871F60F0524086CAEEF61AAE47C0BDEF3BBB76F252400A4C8422590E46C0B656FB86F03B5340FA28B2217F0346C0CE0A257ADB3D534057EC2FBB27F745C02B1895D409405340BEE6287052F545C01AA33BF2DF3F53401D386744692743C07958A835CDFF52403EDF52396E3443C0DC9EAF2DC7FD5240B9E39237FD0645C0574B4E2543B552400AD7A3703D1245C03A234A7B83B35240BBE9944235C347C0EBF06E7961EE5240', '5', '2004-10-24 10:23:54', -78.56, null); ALTER TABLE public.my_data ADD COLUMN otherdt timestamptz; ALTER TABLE public.my_data ADD COLUMN othergeom geometry; UPDATE my_data SET otherdt=datetime+'1 year'::interval, othergeom=st_pointonsurface(geom); diff --git a/tests/routes/test_items.py b/tests/routes/test_items.py index 5148ade7..f5f00ff3 100644 --- a/tests/routes/test_items.py +++ b/tests/routes/test_items.py @@ -327,6 +327,31 @@ def test_items_properties_filter_cql2(app): assert body["features"][0]["properties"]["row"] == 10 Items.model_validate(body) + filter_query = {"op": "isNull", "args": [{"property": "numeric"}]} + response = app.get( + f"/collections/public.my_data/items?filter-lang=cql2-json&filter=&filter={json.dumps(filter_query)}" + ) + assert response.status_code == 200 + assert response.headers["content-type"] == "application/geo+json" + body = response.json() + assert len(body["features"]) == 1 + assert body["numberMatched"] == 1 + assert body["numberReturned"] == 1 + assert body["features"][0]["properties"]["id"] == "5" + Items.model_validate(body) + + response = app.get( + "/collections/public.my_data/items?filter-lang=cql2-text&filter=&filter=numeric IS NULL" + ) + assert response.status_code == 200 + assert response.headers["content-type"] == "application/geo+json" + body = response.json() + assert len(body["features"]) == 1 + assert body["numberMatched"] == 1 + assert body["numberReturned"] == 1 + assert body["features"][0]["properties"]["id"] == "5" + Items.model_validate(body) + def test_items_geo_filter_cql2(app): """Test CQL2 geo filter.""" diff --git a/tipg/filter/evaluate.py b/tipg/filter/evaluate.py index 6a14b250..5852ec25 100644 --- a/tipg/filter/evaluate.py +++ b/tipg/filter/evaluate.py @@ -54,7 +54,9 @@ def in_(self, node, lhs, *options): # noqa: D102 @handle(ast.IsNull) def null(self, node, lhs): # noqa: D102 - return filters.runop(lhs, None, "is_null", node.not_) + if isinstance(lhs, list): + lhs = filters.attribute(lhs[0].name, self.field_mapping) + return filters.isnull(lhs) # @handle(ast.ExistsPredicateNode) # def exists(self, node, lhs): diff --git a/tipg/filter/filters.py b/tipg/filter/filters.py index 2b8ef019..2e306e8e 100644 --- a/tipg/filter/filters.py +++ b/tipg/filter/filters.py @@ -35,8 +35,6 @@ class Operator: """Filter Operators.""" OPERATORS: Dict[str, Callable] = { - "is_null": lambda f, a=None: f.is_(None), - "is_not_null": lambda f, a=None: f.isnot(None), "==": lambda f, a: f == a, "=": lambda f, a: f == a, "eq": lambda f, a: f == a, @@ -317,6 +315,11 @@ def attribute(name: str, fields: List[str]): raise TypeError(f"Field {name} not in table.") +def isnull(lhs): + """null value.""" + return lhs.is_(V("NULL")) + + def literal(value): """literal value.""" return value