Skip to content

Commit 54c7525

Browse files
ES|QL query builder
1 parent 44cbf67 commit 54c7525

File tree

5 files changed

+982
-17
lines changed

5 files changed

+982
-17
lines changed

docs/sphinx/index.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ High-level documentation for this client is `also available <https://www.elastic
1111
:maxdepth: 2
1212

1313
es_api
14+
esql
1415
dsl
1516
api_helpers
1617
exceptions

elasticsearch/dsl/document_base.py

Lines changed: 84 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
# specific language governing permissions and limitations
1616
# under the License.
1717

18+
import json
1819
from datetime import date, datetime
1920
from fnmatch import fnmatch
2021
from typing import (
@@ -56,7 +57,59 @@ def __init__(self, *args: Any, **kwargs: Any):
5657
self.args, self.kwargs = args, kwargs
5758

5859

59-
class InstrumentedField:
60+
class InstrumentedExpression:
61+
"""Proxy object for a ES|QL expression."""
62+
63+
def __init__(self, expr: str):
64+
self._expr = expr
65+
66+
def __str__(self) -> str:
67+
return self._expr
68+
69+
def __repr__(self) -> str:
70+
return f"InstrumentedExpression[{self._expr}]"
71+
72+
def __pos__(self) -> "InstrumentedExpression":
73+
return self
74+
75+
def __neg__(self) -> "InstrumentedExpression":
76+
return InstrumentedExpression(f"-({self._expr})")
77+
78+
def __eq__(self, value: Any) -> "InstrumentedExpression": # type: ignore[override]
79+
return InstrumentedExpression(f"{self._expr} == {json.dumps(value)}")
80+
81+
def __ne__(self, value: Any) -> "InstrumentedExpression": # type: ignore[override]
82+
return InstrumentedExpression(f"{self._expr} != {json.dumps(value)}")
83+
84+
def __lt__(self, value: Any) -> "InstrumentedExpression":
85+
return InstrumentedExpression(f"{self._expr} < {json.dumps(value)}")
86+
87+
def __gt__(self, value: Any) -> "InstrumentedExpression":
88+
return InstrumentedExpression(f"{self._expr} > {json.dumps(value)}")
89+
90+
def __le__(self, value: Any) -> "InstrumentedExpression":
91+
return InstrumentedExpression(f"{self._expr} <= {json.dumps(value)}")
92+
93+
def __ge__(self, value: Any) -> "InstrumentedExpression":
94+
return InstrumentedExpression(f"{self._expr} >= {json.dumps(value)}")
95+
96+
def __add__(self, value: Any) -> "InstrumentedExpression":
97+
return InstrumentedExpression(f"{self._expr} + {json.dumps(value)}")
98+
99+
def __sub__(self, value: Any) -> "InstrumentedExpression":
100+
return InstrumentedExpression(f"{self._expr} - {json.dumps(value)}")
101+
102+
def __mul__(self, value: Any) -> "InstrumentedExpression":
103+
return InstrumentedExpression(f"{self._expr} * {json.dumps(value)}")
104+
105+
def __truediv__(self, value: Any) -> "InstrumentedExpression":
106+
return InstrumentedExpression(f"{self._expr} / {json.dumps(value)}")
107+
108+
def __mod__(self, value: Any) -> "InstrumentedExpression":
109+
return InstrumentedExpression(f"{self._expr} % {json.dumps(value)}")
110+
111+
112+
class InstrumentedField(InstrumentedExpression):
60113
"""Proxy object for a mapped document field.
61114
62115
An object of this instance is returned when a field is accessed as a class
@@ -71,8 +124,8 @@ class MyDocument(Document):
71124
s = s.sort(-MyDocument.name) # sort by name in descending order
72125
"""
73126

74-
def __init__(self, name: str, field: Field):
75-
self._name = name
127+
def __init__(self, name: str, field: Optional[Field]):
128+
super().__init__(name)
76129
self._field = field
77130

78131
# note that the return value type here assumes classes will only be used to
@@ -83,26 +136,41 @@ def __getattr__(self, attr: str) -> "InstrumentedField":
83136
# first let's see if this is an attribute of this object
84137
return super().__getattribute__(attr) # type: ignore[no-any-return]
85138
except AttributeError:
86-
try:
87-
# next we see if we have a sub-field with this name
88-
return InstrumentedField(f"{self._name}.{attr}", self._field[attr])
89-
except KeyError:
90-
# lastly we let the wrapped field resolve this attribute
91-
return getattr(self._field, attr) # type: ignore[no-any-return]
92-
93-
def __pos__(self) -> str:
139+
if self._field:
140+
try:
141+
# next we see if we have a sub-field with this name
142+
return InstrumentedField(f"{self._expr}.{attr}", self._field[attr])
143+
except KeyError:
144+
# lastly we let the wrapped field resolve this attribute
145+
return getattr(self._field, attr) # type: ignore[no-any-return]
146+
else:
147+
raise
148+
149+
def __pos__(self) -> str: # type: ignore[override]
94150
"""Return the field name representation for ascending sort order"""
95-
return f"{self._name}"
151+
return f"{self._expr}"
96152

97-
def __neg__(self) -> str:
153+
def __neg__(self) -> str: # type: ignore[override]
98154
"""Return the field name representation for descending sort order"""
99-
return f"-{self._name}"
155+
return f"-{self._expr}"
156+
157+
def asc(self) -> "InstrumentedField":
158+
return InstrumentedField(f"{self._expr} ASC", None)
159+
160+
def desc(self) -> "InstrumentedField":
161+
return InstrumentedField(f"{self._expr} DESC", None)
162+
163+
def nulls_first(self) -> "InstrumentedField":
164+
return InstrumentedField(f"{self._expr} NULLS FIRST", None)
165+
166+
def nulls_last(self) -> "InstrumentedField":
167+
return InstrumentedField(f"{self._expr} NULLS LAST", None)
100168

101169
def __str__(self) -> str:
102-
return self._name
170+
return self._expr
103171

104172
def __repr__(self) -> str:
105-
return f"InstrumentedField[{self._name}]"
173+
return f"InstrumentedField[{self._expr}]"
106174

107175

108176
class DocumentMeta(type):

elasticsearch/dsl/utils.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -333,7 +333,7 @@ def __init__(self, _expand__to_dot: Optional[bool] = None, **params: Any) -> Non
333333
_expand__to_dot = EXPAND__TO_DOT
334334
self._params: Dict[str, Any] = {}
335335
for pname, pvalue in params.items():
336-
if pvalue == DEFAULT:
336+
if pvalue is DEFAULT:
337337
continue
338338
# expand "__" to dots
339339
if "__" in pname and _expand__to_dot:

elasticsearch/esql/__init__.py

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
# Licensed to Elasticsearch B.V. under one or more contributor
2+
# license agreements. See the NOTICE file distributed with
3+
# this work for additional information regarding copyright
4+
# ownership. Elasticsearch B.V. licenses this file to you under
5+
# the Apache License, Version 2.0 (the "License"); you may
6+
# not use this file except in compliance with the License.
7+
# You may obtain a copy of the License at
8+
#
9+
# http://www.apache.org/licenses/LICENSE-2.0
10+
#
11+
# Unless required by applicable law or agreed to in writing,
12+
# software distributed under the License is distributed on an
13+
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14+
# KIND, either express or implied. See the License for the
15+
# specific language governing permissions and limitations
16+
# under the License.
17+
18+
from .esql import ESQL # noqa: F401

0 commit comments

Comments
 (0)