def test_numeric_equality_negative(self): parsed = parse_xpath("number = -100.32") expected_filter = { "nested": { "path": "case_properties", "query": { "filtered": { "query": { "match_all": {} }, "filter": { "and": ({ "term": { "case_properties.key.exact": "number" } }, { "term": { "case_properties.value.exact": -100.32, } }) } } } } } built_filter = build_filter_from_ast("domain", parsed) self.assertEqual(expected_filter, built_filter)
def test_numeric_comparison(self): parsed = parse_xpath("number <= '100.32'") expected_filter = { "nested": { "path": "case_properties", "query": { "bool": { "filter": [ { "term": { "case_properties.key.exact": "number" } } ], "must": { "range": { "case_properties.value.numeric": { "lte": 100.32 } } } } } } } self.checkQuery(expected_filter, build_filter_from_ast("domain", parsed), is_raw_query=True)
def test_nested_parent_lookups(self): parsed = parse_xpath("father/mother/house = 'Tyrell'") expected_filter = { "nested": { "path": "indices", "query": { "filtered": { "query": { "match_all": {}, }, "filter": { "and": ({ "terms": { "indices.referenced_id": [self.parent_case_id], } }, { "term": { "indices.identifier": "father" } }) } } } } } built_filter = build_filter_from_ast(self.domain, parsed) self.assertEqual(expected_filter, built_filter) self.assertEqual([self.child_case_id], CaseSearchES().filter(built_filter).values_list( '_id', flat=True))
def test_simple_filter(self): parsed = parse_xpath("name = 'farid'") expected_filter = { "nested": { "path": "case_properties", "query": { "filtered": { "query": { "match_all": {} }, "filter": { "and": ({ "term": { "case_properties.key.exact": "name" } }, { "term": { "case_properties.value.exact": "farid" } }) } } } } } built_filter = build_filter_from_ast("domain", parsed) self.assertEqual(expected_filter, built_filter)
def test_date_comparison(self): parsed = parse_xpath("dob >= '2017-02-12'") expected_filter = { "nested": { "path": "case_properties", "query": { "bool": { "filter": [ { "term": { "case_properties.key.exact": "dob" } } ], "must": { "range": { "case_properties.value.date": { "gte": "2017-02-12" } } } } } } } self.checkQuery(expected_filter, build_filter_from_ast("domain", parsed), is_raw_query=True)
def test_numeric_comparison(self): parsed = parse_xpath("number <= '100.32'") expected_filter = { "nested": { "path": "case_properties", "query": { "filtered": { "filter": { "term": { "case_properties.key.exact": "number" } }, "query": { "range": { "case_properties.value.numeric": { "lte": 100.32, } } } } } } } self.assertEqual(expected_filter, build_filter_from_ast("domain", parsed))
def test_date_comparison(self): parsed = parse_xpath("dob >= '2017-02-12'") expected_filter = { "nested": { "path": "case_properties", "query": { "filtered": { "filter": { "term": { "case_properties.key.exact": "dob" } }, "query": { "range": { "case_properties.value.date": { "gte": "2017-02-12", } } } } } } } self.assertEqual(expected_filter, build_filter_from_ast("domain", parsed))
def test_parent_lookups(self): parsed = parse_xpath("father/name = 'Mace'") # return all the cases who's parent (relationship named 'father') has case property 'name' = 'Mace' expected_filter = { "nested": { "path": "indices", "query": { "filtered": { "query": { "match_all": { }, }, "filter": { "and": ( { "terms": { "indices.referenced_id": [self.parent_case_id], } }, { "term": { "indices.identifier": "father" } } ) } } } } } built_filter = build_filter_from_ast(self.domain, parsed) self.assertEqual(expected_filter, built_filter) self.assertEqual([self.child_case_id], CaseSearchES().filter(built_filter).values_list('_id', flat=True))
def test_nested_parent_lookups(self): parsed = parse_xpath("father/mother/house = 'Tyrell'") expected_filter = { "nested": { "path": "indices", "query": { "filtered": { "query": { "match_all": { }, }, "filter": { "and": ( { "terms": { "indices.referenced_id": [self.parent_case_id], } }, { "term": { "indices.identifier": "father" } } ) } } } } } built_filter = build_filter_from_ast(self.domain, parsed) self.assertEqual(expected_filter, built_filter) self.assertEqual([self.child_case_id], CaseSearchES().filter(built_filter).values_list('_id', flat=True))
def test_numeric_equality_negative(self): parsed = parse_xpath("number = -100.32") expected_filter = { "nested": { "path": "case_properties", "query": { "bool": { "filter": [ { "bool": { "filter": ( { "term": { "case_properties.key.exact": "number" } }, { "term": { "case_properties.value.exact": -100.32 } } ) } } ], "must": { "match_all": {} } } } } } built_filter = build_filter_from_ast("domain", parsed) self.checkQuery(expected_filter, built_filter, is_raw_query=True)
def test_simple_filter(self): parsed = parse_xpath("name = 'farid'") expected_filter = { "nested": { "path": "case_properties", "query": { "filtered": { "query": { "match_all": {} }, "filter": { "and": ( { "term": { "case_properties.key.exact": "name" } }, { "term": { "case_properties.value.exact": "farid" } } ) } } } } } built_filter = build_filter_from_ast("domain", parsed) self.assertEqual(expected_filter, built_filter)
def test_simple_filter(self): parsed = parse_xpath("name = 'farid'") expected_filter = { "nested": { "path": "case_properties", "query": { "bool": { "filter": [ { "bool": { "filter": ( { "term": { "case_properties.key.exact": "name" } }, { "term": { "case_properties.value.exact": "farid" } } ) } } ], "must": { "match_all": {} } } } } } built_filter = build_filter_from_ast("domain", parsed) self.checkQuery(expected_filter, built_filter, is_raw_query=True)
def test_parent_lookups(self): parsed = parse_xpath("father/name = 'Mace'") # return all the cases who's parent (relationship named 'father') has case property 'name' = 'Mace' expected_filter = { "nested": { "path": "indices", "query": { "filtered": { "query": { "match_all": {}, }, "filter": { "and": ({ "terms": { "indices.referenced_id": [self.parent_case_id], } }, { "term": { "indices.identifier": "father" } }) } } } } } built_filter = build_filter_from_ast(self.domain, parsed) self.assertEqual(expected_filter, built_filter) self.assertEqual([self.child_case_id], CaseSearchES().filter(built_filter).values_list( '_id', flat=True))
def xpath_query(self, domain, xpath): """Search for cases using an XPath predicate expression. Enter an arbitrary XPath predicate in the context of the case. Also supports related case lookups. e.g you can do things like: - case properties: "first_name = 'dolores' and last_name = 'abernathy'" - date ranges: "first_came_online >= '2017-08-12' or died <= '2020-11-15" - numeric ranges: "age >= 100 and height < 1.25" - related cases: "mother/first_name = 'maeve' or parent/parent/host/age = 13" """ from corehq.apps.case_search.filter_dsl import ( CaseFilterError, build_filter_from_ast, ) try: return self.filter( build_filter_from_ast(domain, parse_xpath(xpath))) except (TypeError, RuntimeError) as e: raise CaseFilterError( _("Malformed search query: {search_query}").format( search_query=e), None, )
def test_case_property_existence(self): parsed = parse_xpath("property != ''") expected_filter = { "not": { "or": ( { "not": { "nested": { "path": "case_properties", "query": { "filtered": { "query": { "match_all": { } }, "filter": { "term": { "case_properties.key.exact": "property" } } } } } } }, { "nested": { "path": "case_properties", "query": { "filtered": { "query": { "match_all": { } }, "filter": { "and": ( { "term": { "case_properties.key.exact": "property" } }, { "term": { "case_properties.value.exact": "" } } ) } } } } } ) } } self.assertEqual(expected_filter, build_filter_from_ast("domain", parsed))
def test_case_property_existence(self): parsed = parse_xpath("property != ''") expected_filter = { "not": { "or": ( { "not": { "nested": { "path": "case_properties", "query": { "filtered": { "query": { "match_all": { } }, "filter": { "term": { "case_properties.key.exact": "property" } } } } } } }, { "nested": { "path": "case_properties", "query": { "filtered": { "query": { "match_all": { } }, "filter": { "and": ( { "term": { "case_properties.key.exact": "property" } }, { "term": { "case_properties.value.exact": "" } } ) } } } } } ) } } self.assertEqual(expected_filter, build_filter_from_ast("domain", parsed))
def test_self_reference(self): with self.assertRaises(CaseFilterError): build_filter_from_ast(None, parse_xpath("name = other_property")) with self.assertRaises(CaseFilterError): build_filter_from_ast(None, parse_xpath("name > other_property")) with self.assertRaises(CaseFilterError): build_filter_from_ast(None, parse_xpath("parent/name > other_property"))
def test_self_reference(self): with self.assertRaises(CaseFilterError): build_filter_from_ast(None, parse_xpath("name = other_property")) with self.assertRaises(CaseFilterError): build_filter_from_ast(None, parse_xpath("name > other_property")) with self.assertRaises(CaseFilterError): build_filter_from_ast(None, parse_xpath("parent/name > other_property"))
def test_date_comparison(self): parsed = parse_xpath("dob >= '2017-02-12'") expected_filter = { "nested": { "path": "case_properties", "query": { "filtered": { "filter": { "term": { "case_properties.key.exact": "dob" } }, "query": { "range": { "case_properties.value.date": { "gte": "2017-02-12", } } } } } } } self.assertEqual(expected_filter, build_filter_from_ast("domain", parsed))
def test_numeric_comparison(self): parsed = parse_xpath("number <= '100.32'") expected_filter = { "nested": { "path": "case_properties", "query": { "filtered": { "filter": { "term": { "case_properties.key.exact": "number" } }, "query": { "range": { "case_properties.value.numeric": { "lte": 100.32, } } } } } } } self.assertEqual(expected_filter, build_filter_from_ast("domain", parsed))
def test_nested_filter(self): parsed = parse_xpath("(name = 'farid' or name = 'leila') and dob <= '2017-02-11'") expected_filter = { "and": ( { "or": ( { "nested": { "path": "case_properties", "query": { "filtered": { "query": { "match_all": { } }, "filter": { "and": ( { "term": { "case_properties.key.exact": "name" } }, { "term": { "case_properties.value.exact": "farid" } } ) } } } } }, { "nested": { "path": "case_properties", "query": { "filtered": { "query": { "match_all": { } }, "filter": { "and": ( { "term": { "case_properties.key.exact": "name" } }, { "term": { "case_properties.value.exact": "leila" } } ) } } } } } ) }, { "nested": { "path": "case_properties", "query": { "filtered": { "filter": { "term": { "case_properties.key.exact": "dob" } }, "query": { "range": { "case_properties.value.date": { "lte": "2017-02-11" } } } } } } } ) } built_filter = build_filter_from_ast("domain", parsed) self.assertEqual(expected_filter, built_filter)
def test_nested_filter(self): parsed = parse_xpath("(name = 'farid' or name = 'leila') and dob <= '2017-02-11'") expected_filter = { "bool": { "filter": [ { "bool": { "should": [ { "nested": { "path": "case_properties", "query": { "bool": { "filter": [ { "bool": { "filter": [ { "term": { "case_properties.key.exact": "name" } }, { "term": { "case_properties.value.exact": "farid" } } ] } } ], "must": { "match_all": {} } } } } }, { "nested": { "path": "case_properties", "query": { "bool": { "filter": [ { "bool": { "filter": [ { "term": { "case_properties.key.exact": "name" } }, { "term": { "case_properties.value.exact": "leila" } } ] } } ], "must": { "match_all": {} } } } } } ] } }, { "nested": { "path": "case_properties", "query": { "bool": { "filter": [ { "term": { "case_properties.key.exact": "dob" } } ], "must": { "range": { "case_properties.value.date": { "lte": "2017-02-11" } } } } } } } ] } } built_filter = build_filter_from_ast("domain", parsed) self.checkQuery(expected_filter, built_filter, is_raw_query=True)
def test_case_property_existence(self): parsed = parse_xpath("property != ''") expected_filter = { "bool": { "must_not": { "bool": { "should": [ { "bool": { "must_not": { "nested": { "path": "case_properties", "query": { "bool": { "filter": [ { "term": { "case_properties.key.exact": "property" } } ], "must": { "match_all": {} } } } } } } }, { "nested": { "path": "case_properties", "query": { "bool": { "filter": [ { "bool": { "filter": [ { "term": { "case_properties.key.exact": "property" } }, { "term": { "case_properties.value.exact": "" } } ] } } ], "must": { "match_all": {} } } } } } ] } } } } self.checkQuery(expected_filter, build_filter_from_ast("domain", parsed), is_raw_query=True)
def not_(node, context): from corehq.apps.case_search.filter_dsl import build_filter_from_ast confirm_args_count(node, 1) return filters.NOT(build_filter_from_ast(node.args[0], context))
def _get_parent_case_ids_matching_subcase_query(subcase_query, context): """Get a list of case IDs for cases that have a subcase with the given index identifier and matching the subcase predicate filter. Only cases with `[>,=] case_count_gt` subcases will be returned. """ # TODO: validate that the subcase filter doesn't contain any ancestor filtering from corehq.apps.case_search.filter_dsl import ( MAX_RELATED_CASES, build_filter_from_ast, ) if subcase_query.subcase_filter: subcase_filter = build_filter_from_ast(subcase_query.subcase_filter, context) else: subcase_filter = filters.match_all() index_identifier_filter = filters.term('indices.identifier', subcase_query.index_identifier) index_query = queries.nested( 'indices', queries.filtered( queries.match_all(), filters.AND( index_identifier_filter, filters.NOT(filters.term('indices.referenced_id', '')) # exclude deleted indices ) ) ) es_query = ( CaseSearchES().domain(context.domain) .filter(index_query) .filter(subcase_filter) .aggregation( aggregations.NestedAggregation( 'indices', 'indices', ).aggregation( aggregations.FilterAggregation( 'matching_indices', index_identifier_filter ).aggregation( aggregations.TermsAggregation( 'referenced_id', 'indices.referenced_id' ) ) ) ) ) if es_query.count() > MAX_RELATED_CASES: from ..exceptions import TooManyRelatedCasesError raise TooManyRelatedCasesError( _("The related case lookup you are trying to perform would return too many cases"), serialize(subcase_query.subcase_filter) ) counts_by_parent_id = es_query.run().aggregations.indices.matching_indices.referenced_id.counts_by_bucket() if subcase_query.op == '>' and subcase_query.count <= 0: return list(counts_by_parent_id) return [ case_id for case_id, count in counts_by_parent_id.items() if subcase_query.filter_count(count) ]
def test_nested_filter(self): parsed = parse_xpath( "(name = 'farid' or name = 'leila') and dob <= '2017-02-11'") expected_filter = { "and": ({ "or": ({ "nested": { "path": "case_properties", "query": { "filtered": { "query": { "match_all": {} }, "filter": { "and": ({ "term": { "case_properties.key.exact": "name" } }, { "term": { "case_properties.value.exact": "farid" } }) } } } } }, { "nested": { "path": "case_properties", "query": { "filtered": { "query": { "match_all": {} }, "filter": { "and": ({ "term": { "case_properties.key.exact": "name" } }, { "term": { "case_properties.value.exact": "leila" } }) } } } } }) }, { "nested": { "path": "case_properties", "query": { "filtered": { "filter": { "term": { "case_properties.key.exact": "dob" } }, "query": { "range": { "case_properties.value.date": { "lte": "2017-02-11" } } } } } } }) } built_filter = build_filter_from_ast("domain", parsed) self.assertEqual(expected_filter, built_filter)
def _check(xpath, error_msg): with assert_raises_regex(XPathFunctionException, error_msg): parsed = parse_xpath(xpath) build_filter_from_ast(parsed, SearchFilterContext("mydomain"))