relationalai 0.11.2__py3-none-any.whl → 0.11.4__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- relationalai/clients/snowflake.py +44 -15
- relationalai/clients/types.py +1 -0
- relationalai/clients/use_index_poller.py +446 -178
- relationalai/early_access/builder/std/__init__.py +1 -1
- relationalai/early_access/dsl/bindings/csv.py +4 -4
- relationalai/semantics/internal/internal.py +22 -4
- relationalai/semantics/lqp/executor.py +69 -18
- relationalai/semantics/lqp/intrinsics.py +23 -0
- relationalai/semantics/lqp/model2lqp.py +16 -6
- relationalai/semantics/lqp/passes.py +3 -4
- relationalai/semantics/lqp/primitives.py +38 -14
- relationalai/semantics/metamodel/builtins.py +152 -11
- relationalai/semantics/metamodel/factory.py +3 -2
- relationalai/semantics/metamodel/helpers.py +78 -2
- relationalai/semantics/reasoners/graph/core.py +343 -40
- relationalai/semantics/reasoners/optimization/solvers_dev.py +20 -1
- relationalai/semantics/reasoners/optimization/solvers_pb.py +24 -3
- relationalai/semantics/rel/compiler.py +5 -17
- relationalai/semantics/rel/executor.py +2 -2
- relationalai/semantics/rel/rel.py +6 -0
- relationalai/semantics/rel/rel_utils.py +37 -1
- relationalai/semantics/rel/rewrite/extract_common.py +153 -242
- relationalai/semantics/sql/compiler.py +540 -202
- relationalai/semantics/sql/executor/duck_db.py +21 -0
- relationalai/semantics/sql/executor/result_helpers.py +7 -0
- relationalai/semantics/sql/executor/snowflake.py +9 -2
- relationalai/semantics/sql/rewrite/denormalize.py +4 -6
- relationalai/semantics/sql/rewrite/recursive_union.py +23 -3
- relationalai/semantics/sql/sql.py +120 -46
- relationalai/semantics/std/__init__.py +9 -4
- relationalai/semantics/std/datetime.py +363 -0
- relationalai/semantics/std/math.py +77 -0
- relationalai/semantics/std/re.py +83 -0
- relationalai/semantics/std/strings.py +1 -1
- relationalai/tools/cli_controls.py +445 -60
- relationalai/util/format.py +78 -1
- {relationalai-0.11.2.dist-info → relationalai-0.11.4.dist-info}/METADATA +3 -2
- {relationalai-0.11.2.dist-info → relationalai-0.11.4.dist-info}/RECORD +41 -39
- relationalai/semantics/std/dates.py +0 -213
- {relationalai-0.11.2.dist-info → relationalai-0.11.4.dist-info}/WHEEL +0 -0
- {relationalai-0.11.2.dist-info → relationalai-0.11.4.dist-info}/entry_points.txt +0 -0
- {relationalai-0.11.2.dist-info → relationalai-0.11.4.dist-info}/licenses/LICENSE +0 -0
|
@@ -0,0 +1,363 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
from typing import Union, Literal
|
|
3
|
+
import datetime as dt
|
|
4
|
+
|
|
5
|
+
from relationalai.semantics.internal import internal as b
|
|
6
|
+
from .std import _DateTime, _Date, _Integer, _String, _make_expr
|
|
7
|
+
from .. import std
|
|
8
|
+
|
|
9
|
+
class ISO:
|
|
10
|
+
DATE = "yyyy-mm-dd"
|
|
11
|
+
HOURS = "yyyy-mm-ddTHH"
|
|
12
|
+
HOURS_TZ = "yyyy-mm-ddTHHz"
|
|
13
|
+
MINUTES = "yyyy-mm-ddTHH:MM"
|
|
14
|
+
MINUTES_TZ = "yyyy-mm-ddTHH:MMz"
|
|
15
|
+
SECONDS = "yyyy-mm-ddTHH:MM:SS"
|
|
16
|
+
SECONDS_TZ = "yyyy-mm-ddTHH:MM:SSz"
|
|
17
|
+
MILLIS = "yyyy-mm-ddTHH:MM:SS.s"
|
|
18
|
+
MILLIS_TZ = "yyyy-mm-ddTHH:MM:SS.sz"
|
|
19
|
+
|
|
20
|
+
#--------------------------------------------------
|
|
21
|
+
# Date functions
|
|
22
|
+
#--------------------------------------------------
|
|
23
|
+
|
|
24
|
+
class date:
|
|
25
|
+
|
|
26
|
+
def __new__(cls, year: _Integer, month: _Integer, day: _Integer) -> b.Expression:
|
|
27
|
+
return _make_expr("construct_date", std.cast_to_int64(year), std.cast_to_int64(month), std.cast_to_int64(day), b.Date.ref("res"))
|
|
28
|
+
|
|
29
|
+
@classmethod
|
|
30
|
+
def year(cls, date: _Date) -> b.Expression:
|
|
31
|
+
return _make_expr("date_year", date, b.Int64.ref("res"))
|
|
32
|
+
|
|
33
|
+
@classmethod
|
|
34
|
+
def quarter(cls, date: _Date) -> b.Expression:
|
|
35
|
+
return _make_expr("date_quarter", date, b.Int64.ref("res"))
|
|
36
|
+
|
|
37
|
+
@classmethod
|
|
38
|
+
def month(cls, date: _Date) -> b.Expression:
|
|
39
|
+
return _make_expr("date_month", date, b.Int64.ref("res"))
|
|
40
|
+
|
|
41
|
+
@classmethod
|
|
42
|
+
def week(cls, date: _Date) -> b.Expression:
|
|
43
|
+
return _make_expr("date_week", date, b.Int64.ref("res"))
|
|
44
|
+
|
|
45
|
+
@classmethod
|
|
46
|
+
def day(cls, date: _Date) -> b.Expression:
|
|
47
|
+
return _make_expr("date_day", date, b.Int64.ref("res"))
|
|
48
|
+
|
|
49
|
+
@classmethod
|
|
50
|
+
def dayofyear(cls, date: _Date) -> b.Expression:
|
|
51
|
+
return _make_expr("date_dayofyear", date, b.Int64.ref("res"))
|
|
52
|
+
|
|
53
|
+
@classmethod
|
|
54
|
+
def isoweekday(cls, date: _Date) -> b.Expression:
|
|
55
|
+
"""
|
|
56
|
+
Return the ISO weekday as an integer, where Monday is 1, and Sunday is 7.
|
|
57
|
+
"""
|
|
58
|
+
return _make_expr("date_weekday", date, b.Int64.ref("res"))
|
|
59
|
+
|
|
60
|
+
@classmethod
|
|
61
|
+
def weekday(cls, date: _Date) -> b.Expression:
|
|
62
|
+
return cls.isoweekday(date) - 1 # Convert ISO weekday (1=Mon..7=Sun) to weekday (0=Mon..6=Sun)
|
|
63
|
+
|
|
64
|
+
@classmethod
|
|
65
|
+
def fromordinal(cls, ordinal: _Integer) -> b.Expression:
|
|
66
|
+
# ordinal 1 = '0001-01-01'. Minus 1 day since we can't declare date 0000-00-00
|
|
67
|
+
return cls.add(b.Date(dt.date(1, 1, 1)), days(ordinal - 1))
|
|
68
|
+
|
|
69
|
+
@classmethod
|
|
70
|
+
def to_datetime(cls, date: _Date, hour: int = 0, minute: int = 0, second: int = 0, millisecond: int = 0, tz: str = "UTC") -> b.Expression:
|
|
71
|
+
_year = cls.year(date)
|
|
72
|
+
_month = cls.month(date)
|
|
73
|
+
_day = cls.day(date)
|
|
74
|
+
return _make_expr("construct_datetime_ms_tz", _year, _month, _day, hour, minute, second, millisecond, tz, b.DateTime.ref("res"))
|
|
75
|
+
|
|
76
|
+
@classmethod
|
|
77
|
+
def format(cls, date: _Date, format: _String) -> b.Expression:
|
|
78
|
+
return _make_expr("date_format", date, format, b.String.ref("res"))
|
|
79
|
+
|
|
80
|
+
@classmethod
|
|
81
|
+
def add(cls, date: _Date, period: b.Producer) -> b.Expression:
|
|
82
|
+
return _make_expr("date_add", date, period, b.Date.ref("res"))
|
|
83
|
+
|
|
84
|
+
@classmethod
|
|
85
|
+
def subtract(cls, date: _Date, period: b.Producer) -> b.Expression:
|
|
86
|
+
return _make_expr("date_subtract", date, period, b.Date.ref("res"))
|
|
87
|
+
|
|
88
|
+
@classmethod
|
|
89
|
+
def range(cls, start: _Date | None = None, end: _Date | None = None, periods: int = 1, freq: Frequency = "D") -> b.Expression:
|
|
90
|
+
"""
|
|
91
|
+
Note on date_ranges and datetime_range: The way the computation works is that it first overapproximates the
|
|
92
|
+
number of periods.
|
|
93
|
+
|
|
94
|
+
For example, date_range(2025-02-01, 2025-03-01, freq='M') and date_range(2025-02-01, 2025-03-31, freq='M') will
|
|
95
|
+
compute range_end to be ceil(28*1/(365/12))=1 and ceil(58*1/(365/12))=2.
|
|
96
|
+
|
|
97
|
+
Then, the computation fetches range_end+1 items into _date, which is the right number in the first case but
|
|
98
|
+
one too many in the second case. That's why a filter end >= _date (or variant of) is applied, to remove any
|
|
99
|
+
extra item. The result is two items in both cases.
|
|
100
|
+
"""
|
|
101
|
+
if start is None and end is None:
|
|
102
|
+
raise ValueError("Invalid start/end date for date_range. Must provide at least start date or end date")
|
|
103
|
+
_days = {
|
|
104
|
+
"D": 1,
|
|
105
|
+
"W": 1/7,
|
|
106
|
+
"M": 1/(365/12),
|
|
107
|
+
"Y": 1/365,
|
|
108
|
+
}
|
|
109
|
+
if freq not in _days.keys():
|
|
110
|
+
raise ValueError(f"Frequency '{freq}' is not allowed for date_range. List of allowed frequencies: {list(_days.keys())}")
|
|
111
|
+
date_func = cls.add
|
|
112
|
+
if start is None:
|
|
113
|
+
start = end
|
|
114
|
+
end = None
|
|
115
|
+
date_func = cls.subtract
|
|
116
|
+
assert start is not None
|
|
117
|
+
if end is not None:
|
|
118
|
+
num_days = cls.period_days(start, end)
|
|
119
|
+
if freq in ["W", "M", "Y"]:
|
|
120
|
+
range_end = std.cast(b.Int64, std.math.ceil(num_days * _days[freq]))
|
|
121
|
+
else:
|
|
122
|
+
range_end = num_days
|
|
123
|
+
# date_range is inclusive. add 1 since std.range is exclusive
|
|
124
|
+
ix = std.range(0, range_end + 1, 1)
|
|
125
|
+
else:
|
|
126
|
+
ix = std.range(0, periods, 1)
|
|
127
|
+
_date = date_func(start, _periods[freq](ix))
|
|
128
|
+
if isinstance(end, dt.date) :
|
|
129
|
+
return b.Date(end) >= _date
|
|
130
|
+
elif end is not None:
|
|
131
|
+
return end >= _date
|
|
132
|
+
return _date
|
|
133
|
+
|
|
134
|
+
@classmethod
|
|
135
|
+
def period_days(cls, start: _Date, end: _Date) -> b.Expression:
|
|
136
|
+
return _make_expr("dates_period_days", start, end, b.Int64.ref("res"))
|
|
137
|
+
|
|
138
|
+
@classmethod
|
|
139
|
+
def fromisoformat(cls, date_string: _String) -> b.Expression:
|
|
140
|
+
return _make_expr("parse_date", date_string, ISO.DATE, b.Date.ref("res"))
|
|
141
|
+
|
|
142
|
+
#--------------------------------------------------
|
|
143
|
+
# DateTime functions
|
|
144
|
+
#--------------------------------------------------
|
|
145
|
+
|
|
146
|
+
class datetime:
|
|
147
|
+
|
|
148
|
+
def __new__(cls, year: _Integer, month: _Integer, day: _Integer, hour: _Integer = 0, minute: _Integer = 0,
|
|
149
|
+
second: _Integer = 0, millisecond: _Integer = 0, tz: dt.tzinfo|_String = "UTC") -> b.Expression:
|
|
150
|
+
if isinstance(tz, dt.tzinfo):
|
|
151
|
+
tz = str(tz)
|
|
152
|
+
return _make_expr("construct_datetime_ms_tz", std.cast_to_int64(year), std.cast_to_int64(month),
|
|
153
|
+
std.cast_to_int64(day), std.cast_to_int64(hour), std.cast_to_int64(minute),
|
|
154
|
+
std.cast_to_int64(second), std.cast_to_int64(millisecond), tz, b.DateTime.ref("res"))
|
|
155
|
+
|
|
156
|
+
@classmethod
|
|
157
|
+
def now(cls) -> b.Expression:
|
|
158
|
+
return _make_expr("datetime_now", b.DateTime.ref("res"))
|
|
159
|
+
|
|
160
|
+
@classmethod
|
|
161
|
+
def year(cls, datetime: _DateTime, tz: dt.tzinfo|_String|None = None) -> b.Expression:
|
|
162
|
+
tz = _extract_tz(datetime, tz)
|
|
163
|
+
return _make_expr("datetime_year", datetime, tz, b.Int64.ref("res"))
|
|
164
|
+
|
|
165
|
+
@classmethod
|
|
166
|
+
def quarter(cls, datetime: _DateTime, tz: dt.tzinfo|_String|None = None) -> b.Expression:
|
|
167
|
+
tz = _extract_tz(datetime, tz)
|
|
168
|
+
return _make_expr("datetime_quarter", datetime, tz, b.Int64.ref("res"))
|
|
169
|
+
|
|
170
|
+
@classmethod
|
|
171
|
+
def month(cls, datetime: _DateTime, tz: dt.tzinfo|_String|None = None) -> b.Expression:
|
|
172
|
+
tz = _extract_tz(datetime, tz)
|
|
173
|
+
return _make_expr("datetime_month", datetime, tz, b.Int64.ref("res"))
|
|
174
|
+
|
|
175
|
+
@classmethod
|
|
176
|
+
def week(cls, datetime: _DateTime, tz: dt.tzinfo|_String|None = None) -> b.Expression:
|
|
177
|
+
tz = _extract_tz(datetime, tz)
|
|
178
|
+
return _make_expr("datetime_week", datetime, tz, b.Int64.ref("res"))
|
|
179
|
+
|
|
180
|
+
@classmethod
|
|
181
|
+
def day(cls, datetime: _DateTime, tz: dt.tzinfo|_String|None = None) -> b.Expression:
|
|
182
|
+
tz = _extract_tz(datetime, tz)
|
|
183
|
+
return _make_expr("datetime_day", datetime, tz, b.Int64.ref("res"))
|
|
184
|
+
|
|
185
|
+
@classmethod
|
|
186
|
+
def dayofyear(cls, datetime: _DateTime, tz: dt.tzinfo|_String|None = None) -> b.Expression:
|
|
187
|
+
tz = _extract_tz(datetime, tz)
|
|
188
|
+
return _make_expr("datetime_dayofyear", datetime, tz, b.Int64.ref("res"))
|
|
189
|
+
|
|
190
|
+
@classmethod
|
|
191
|
+
def hour(cls, datetime: _DateTime, tz: dt.tzinfo|_String|None = None) -> b.Expression:
|
|
192
|
+
tz = _extract_tz(datetime, tz)
|
|
193
|
+
return _make_expr("datetime_hour", datetime, tz, b.Int64.ref("res"))
|
|
194
|
+
|
|
195
|
+
@classmethod
|
|
196
|
+
def minute(cls, datetime: _DateTime, tz: dt.tzinfo|_String|None = None) -> b.Expression:
|
|
197
|
+
tz = _extract_tz(datetime, tz)
|
|
198
|
+
return _make_expr("datetime_minute", datetime, tz, b.Int64.ref("res"))
|
|
199
|
+
|
|
200
|
+
@classmethod
|
|
201
|
+
def second(cls, datetime: _DateTime) -> b.Expression:
|
|
202
|
+
return _make_expr("datetime_second", datetime, b.Int64.ref("res"))
|
|
203
|
+
|
|
204
|
+
@classmethod
|
|
205
|
+
def isoweekday(cls, datetime: _DateTime, tz: dt.tzinfo|_String|None = None) -> b.Expression:
|
|
206
|
+
"""
|
|
207
|
+
Return the ISO weekday as an integer, where Monday is 1, and Sunday is 7.
|
|
208
|
+
"""
|
|
209
|
+
tz = _extract_tz(datetime, tz)
|
|
210
|
+
return _make_expr("datetime_weekday", datetime, tz, b.Int64.ref("res"))
|
|
211
|
+
|
|
212
|
+
@classmethod
|
|
213
|
+
def weekday(cls, datetime: _DateTime, tz: dt.tzinfo|_String|None = None) -> b.Expression:
|
|
214
|
+
return cls.isoweekday(datetime, tz) - 1 # Convert ISO weekday (1=Mon..7=Sun) to weekday (0=Mon..6=Sun)
|
|
215
|
+
|
|
216
|
+
@classmethod
|
|
217
|
+
def fromordinal(cls, ordinal: _Integer) -> b.Expression:
|
|
218
|
+
# Convert ordinal to milliseconds, since ordinals in Python are days
|
|
219
|
+
# Minus 1 day since we can't declare date 0000-00-00
|
|
220
|
+
ordinal_milliseconds = (ordinal - 1) * 86400000 # 24 * 60 * 60 * 1000
|
|
221
|
+
return cls.add(b.DateTime(dt.datetime(1, 1, 1, 0, 0, 0)), milliseconds(ordinal_milliseconds))
|
|
222
|
+
|
|
223
|
+
@classmethod
|
|
224
|
+
def strptime(cls, date_str: _String, format: _String) -> b.Expression:
|
|
225
|
+
return _make_expr("parse_datetime", date_str, format, b.DateTime.ref("res"))
|
|
226
|
+
|
|
227
|
+
@classmethod
|
|
228
|
+
def to_date(cls, datetime: _DateTime, tz: dt.tzinfo|_String|None = None) -> b.Expression:
|
|
229
|
+
tz = _extract_tz(datetime, tz)
|
|
230
|
+
return _make_expr("construct_date_from_datetime", datetime, tz, b.Date.ref("res"))
|
|
231
|
+
|
|
232
|
+
@classmethod
|
|
233
|
+
def format(cls, date: _DateTime, format: _String, tz: dt.tzinfo|_String|None = None) -> b.Expression:
|
|
234
|
+
tz = _extract_tz(date, tz)
|
|
235
|
+
return _make_expr("datetime_format", date, format, tz, b.String.ref("res"))
|
|
236
|
+
|
|
237
|
+
@classmethod
|
|
238
|
+
def add(cls, date: _DateTime, period: b.Producer) -> b.Expression:
|
|
239
|
+
return _make_expr("datetime_add", date, period, b.DateTime.ref("res"))
|
|
240
|
+
|
|
241
|
+
@classmethod
|
|
242
|
+
def subtract(cls, date: _DateTime, period: b.Producer) -> b.Expression:
|
|
243
|
+
return _make_expr("datetime_subtract", date, period, b.DateTime.ref("res"))
|
|
244
|
+
|
|
245
|
+
@classmethod
|
|
246
|
+
def range(cls, start: _DateTime | None = None, end: _DateTime | None = None, periods: int = 1, freq: Frequency = "D") -> b.Expression:
|
|
247
|
+
"""
|
|
248
|
+
Note on date_ranges and datetime_range: The way the computation works is that it first overapproximates the
|
|
249
|
+
number of periods.
|
|
250
|
+
|
|
251
|
+
For example, date_range(2025-02-01, 2025-03-01, freq='M') and date_range(2025-02-01, 2025-03-31, freq='M') will
|
|
252
|
+
compute range_end to be ceil(28*1/(365/12))=1 and ceil(58*1/(365/12))=2.
|
|
253
|
+
|
|
254
|
+
Then, the computation fetches range_end+1 items into _date, which is the right number in the first case but
|
|
255
|
+
one too many in the second case. That's why a filter end >= _date (or variant of) is applied, to remove any
|
|
256
|
+
extra item. The result is two items in both cases.
|
|
257
|
+
"""
|
|
258
|
+
if start is None and end is None:
|
|
259
|
+
raise ValueError("Invalid start/end datetime for datetime_range. Must provide at least start datetime or end datetime")
|
|
260
|
+
_milliseconds = {
|
|
261
|
+
"ms": 1,
|
|
262
|
+
"s": 1 / 1_000,
|
|
263
|
+
"m": 1 / 60_000,
|
|
264
|
+
"H": 1 / 3_600_000,
|
|
265
|
+
"D": 1 / 86_400_000,
|
|
266
|
+
"W": 1 / (86_400_000 * 7),
|
|
267
|
+
"M": 1 / (86_400_000 * (365 / 12)),
|
|
268
|
+
"Y": 1 / (86_400_000 * 365),
|
|
269
|
+
}
|
|
270
|
+
date_func = cls.add
|
|
271
|
+
if start is None:
|
|
272
|
+
start = end
|
|
273
|
+
end = None
|
|
274
|
+
date_func = cls.subtract
|
|
275
|
+
assert start is not None
|
|
276
|
+
if end is not None:
|
|
277
|
+
num_ms = cls.period_milliseconds(start, end)
|
|
278
|
+
if freq == "ms":
|
|
279
|
+
_end = num_ms
|
|
280
|
+
else:
|
|
281
|
+
_end = std.cast(b.Int64, std.math.ceil(num_ms * _milliseconds[freq]))
|
|
282
|
+
# datetime_range is inclusive. add 1 since std.range is exclusive
|
|
283
|
+
ix = std.range(0, _end + 1, 1)
|
|
284
|
+
else:
|
|
285
|
+
ix = std.range(0, periods, 1)
|
|
286
|
+
_date = date_func(start, _periods[freq](ix))
|
|
287
|
+
if isinstance(end, dt.datetime) :
|
|
288
|
+
return b.DateTime(end) >= _date
|
|
289
|
+
elif end is not None:
|
|
290
|
+
return end >= _date
|
|
291
|
+
return _date
|
|
292
|
+
|
|
293
|
+
@classmethod
|
|
294
|
+
def period_milliseconds(cls, start: _DateTime, end: _DateTime) -> b.Expression:
|
|
295
|
+
return _make_expr("datetimes_period_milliseconds", start, end, b.Int64.ref("res"))
|
|
296
|
+
|
|
297
|
+
#--------------------------------------------------
|
|
298
|
+
# Periods
|
|
299
|
+
#--------------------------------------------------
|
|
300
|
+
|
|
301
|
+
def nanoseconds(period: _Integer) -> b.Expression:
|
|
302
|
+
return _make_expr("nanosecond", std.cast_to_int64(period), b.Int64.ref("res"))
|
|
303
|
+
|
|
304
|
+
def microseconds(period: _Integer) -> b.Expression:
|
|
305
|
+
return _make_expr("microsecond", std.cast_to_int64(period), b.Int64.ref("res"))
|
|
306
|
+
|
|
307
|
+
def milliseconds(period: _Integer) -> b.Expression:
|
|
308
|
+
return _make_expr("millisecond", std.cast_to_int64(period), b.Int64.ref("res"))
|
|
309
|
+
|
|
310
|
+
def seconds(period: _Integer) -> b.Expression:
|
|
311
|
+
return _make_expr("second", std.cast_to_int64(period), b.Int64.ref("res"))
|
|
312
|
+
|
|
313
|
+
def minutes(period: _Integer) -> b.Expression:
|
|
314
|
+
return _make_expr("minute", std.cast_to_int64(period), b.Int64.ref("res"))
|
|
315
|
+
|
|
316
|
+
def hours(period: _Integer) -> b.Expression:
|
|
317
|
+
return _make_expr("hour", std.cast_to_int64(period), b.Int64.ref("res"))
|
|
318
|
+
|
|
319
|
+
def days(period: _Integer) -> b.Expression:
|
|
320
|
+
return _make_expr("day", std.cast_to_int64(period), b.Int64.ref("res"))
|
|
321
|
+
|
|
322
|
+
def weeks(period: _Integer) -> b.Expression:
|
|
323
|
+
return _make_expr("week", std.cast_to_int64(period), b.Int64.ref("res"))
|
|
324
|
+
|
|
325
|
+
def months(period: _Integer) -> b.Expression:
|
|
326
|
+
return _make_expr("month", std.cast_to_int64(period), b.Int64.ref("res"))
|
|
327
|
+
|
|
328
|
+
def years(period: _Integer) -> b.Expression:
|
|
329
|
+
return _make_expr("year", std.cast_to_int64(period), b.Int64.ref("res"))
|
|
330
|
+
|
|
331
|
+
|
|
332
|
+
Frequency = Union[
|
|
333
|
+
Literal["ms"],
|
|
334
|
+
Literal["s"],
|
|
335
|
+
Literal["m"],
|
|
336
|
+
Literal["H"],
|
|
337
|
+
Literal["D"],
|
|
338
|
+
Literal["W"],
|
|
339
|
+
Literal["M"],
|
|
340
|
+
Literal["Y"],
|
|
341
|
+
]
|
|
342
|
+
|
|
343
|
+
_periods = {
|
|
344
|
+
"ms": milliseconds,
|
|
345
|
+
"s": seconds,
|
|
346
|
+
"m": minutes,
|
|
347
|
+
"H": hours,
|
|
348
|
+
"D": days,
|
|
349
|
+
"W": weeks,
|
|
350
|
+
"M": months,
|
|
351
|
+
"Y": years,
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
def _extract_tz(datetime: _DateTime, tz: dt.tzinfo|_String|None) -> _String:
|
|
355
|
+
default_tz = "UTC"
|
|
356
|
+
if tz is None:
|
|
357
|
+
if isinstance(datetime, dt.datetime):
|
|
358
|
+
tz = datetime.tzname() or default_tz
|
|
359
|
+
else:
|
|
360
|
+
tz = default_tz
|
|
361
|
+
elif isinstance(tz, dt.tzinfo) :
|
|
362
|
+
tz = tz.tzname(None) or default_tz
|
|
363
|
+
return tz
|
|
@@ -2,6 +2,7 @@ from __future__ import annotations
|
|
|
2
2
|
|
|
3
3
|
from relationalai.semantics.internal import internal as b
|
|
4
4
|
from .std import _Number, _make_expr
|
|
5
|
+
from math import pi
|
|
5
6
|
|
|
6
7
|
def abs(value: _Number) -> b.Expression:
|
|
7
8
|
return _make_expr("abs", value, b.Number.ref("res"))
|
|
@@ -9,6 +10,17 @@ def abs(value: _Number) -> b.Expression:
|
|
|
9
10
|
def natural_log(value: _Number) -> b.Expression:
|
|
10
11
|
return _make_expr("natural_log", value, b.Number.ref("res"))
|
|
11
12
|
|
|
13
|
+
def log(value: _Number, base: _Number | None = None) -> b.Expression:
|
|
14
|
+
if base is None:
|
|
15
|
+
return _make_expr("natural_log", value, b.Number.ref("res"))
|
|
16
|
+
return _make_expr("log", value, base, b.Number.ref("res"))
|
|
17
|
+
|
|
18
|
+
def log2(value: _Number) -> b.Expression:
|
|
19
|
+
return _make_expr("log", value, 2, b.Number.ref("res"))
|
|
20
|
+
|
|
21
|
+
def log10(value: _Number) -> b.Expression:
|
|
22
|
+
return _make_expr("log10", value, b.Number.ref("res"))
|
|
23
|
+
|
|
12
24
|
def sqrt(value: _Number) -> b.Expression:
|
|
13
25
|
return _make_expr("sqrt", value, b.Float.ref("res"))
|
|
14
26
|
|
|
@@ -62,3 +74,68 @@ def asin(value: _Number) -> b.Expression:
|
|
|
62
74
|
|
|
63
75
|
def asinh(value: _Number) -> b.Expression:
|
|
64
76
|
return _make_expr("asinh", value, b.Float.ref("res"))
|
|
77
|
+
|
|
78
|
+
def tan(value: _Number) -> b.Expression:
|
|
79
|
+
return _make_expr("tan", value, b.Float.ref("res"))
|
|
80
|
+
|
|
81
|
+
def tanh(value: _Number) -> b.Expression:
|
|
82
|
+
return _make_expr("tanh", value, b.Float.ref("res"))
|
|
83
|
+
|
|
84
|
+
def atan(value: _Number) -> b.Expression:
|
|
85
|
+
return _make_expr("atan", value, b.Float.ref("res"))
|
|
86
|
+
|
|
87
|
+
def atanh(value: _Number) -> b.Expression:
|
|
88
|
+
return _make_expr("atanh", value, b.Float.ref("res"))
|
|
89
|
+
|
|
90
|
+
def cot(value: _Number) -> b.Expression:
|
|
91
|
+
return _make_expr("cot", value, b.Float.ref("res"))
|
|
92
|
+
|
|
93
|
+
def acot(value: _Number) -> b.Expression:
|
|
94
|
+
return _make_expr("acot", value, b.Float.ref("res"))
|
|
95
|
+
|
|
96
|
+
def degrees(value: _Number) -> b.Expression:
|
|
97
|
+
divisor = pi / 180.0
|
|
98
|
+
return _make_expr("/", value, divisor, b.Float.ref("res"))
|
|
99
|
+
|
|
100
|
+
def radians(value: _Number) -> b.Expression:
|
|
101
|
+
divisor = 180.0 / pi
|
|
102
|
+
return _make_expr("/", value, divisor, b.Float.ref("res"))
|
|
103
|
+
|
|
104
|
+
def exp(value: _Number) -> b.Expression:
|
|
105
|
+
return _make_expr("exp", value, b.Float.ref("res"))
|
|
106
|
+
|
|
107
|
+
def erf(value: _Number) -> b.Expression:
|
|
108
|
+
return _make_expr("erf", value, b.Float.ref("res"))
|
|
109
|
+
|
|
110
|
+
def erfinv(value: _Number) -> b.Expression:
|
|
111
|
+
return _make_expr("erfinv", value, b.Float.ref("res"))
|
|
112
|
+
|
|
113
|
+
def haversine(x1: _Number, y1: _Number, x2: _Number, y2: _Number, r: _Number) -> b.Expression:
|
|
114
|
+
# 2 * r * asin[sqrt[sin[(x2 - x1)/2] ^ 2 + cos[x1] * cos[x2] * sin[(y2 - y1) / 2] ^ 2]]
|
|
115
|
+
# sin[(x2 - x1)/2] ^ 2
|
|
116
|
+
x_diff = _make_expr("-", x2, x1, b.Float.ref("x_diff"))
|
|
117
|
+
x_diff2 = _make_expr("/", x_diff, 2.0, b.Float.ref("x_diff2"))
|
|
118
|
+
sin_x_diff = _make_expr("sin", x_diff2, b.Float.ref("sin_x_diff"))
|
|
119
|
+
sin_x_pow = _make_expr("pow", sin_x_diff, 2.0, b.Float.ref("sin_x_pow"))
|
|
120
|
+
|
|
121
|
+
# cos[x1] * cos[x2]
|
|
122
|
+
cos_x1 = _make_expr("cos", x1, b.Float.ref("cos_x1"))
|
|
123
|
+
cos_x2 = _make_expr("cos", x2, b.Float.ref("cos_x2"))
|
|
124
|
+
cos_x1_x2 = _make_expr("*", cos_x1, cos_x2, b.Float.ref("cos_x1_x2"))
|
|
125
|
+
|
|
126
|
+
# sin[(y2 - y1) / 2] ^ 2
|
|
127
|
+
y_diff = _make_expr("-", y2, y1, b.Float.ref("y_diff"))
|
|
128
|
+
y_diff2 = _make_expr("/", y_diff, 2.0, b.Float.ref("y_diff2"))
|
|
129
|
+
sin_y_diff = _make_expr("sin", y_diff2, b.Float.ref("sin_y_diff"))
|
|
130
|
+
sin_y_pow = _make_expr("pow", sin_y_diff, 2.0, b.Float.ref("sin_y_pow"))
|
|
131
|
+
|
|
132
|
+
# cos[x1] * cos[x2] * sin[(y2 - y1) / 2] ^ 2
|
|
133
|
+
prod = _make_expr("*", cos_x1_x2, sin_y_pow, b.Float.ref("prod"))
|
|
134
|
+
# sin[(x2 - x1)/2] ^ 2 + cos[x1] * cos[x2] * sin[(y2 - y1) / 2] ^ 2
|
|
135
|
+
prod_sum = _make_expr("+", sin_x_pow, prod, b.Float.ref("prod_sum"))
|
|
136
|
+
|
|
137
|
+
sqrt_val = _make_expr("sqrt", prod_sum, b.Float.ref("sqrt_val"))
|
|
138
|
+
asin_val = _make_expr("asin", sqrt_val, b.Float.ref("asin_val"))
|
|
139
|
+
haversine_r = _make_expr("*", r, asin_val, b.Float.ref("haversine_r"))
|
|
140
|
+
haversine_final = _make_expr("*", haversine_r, 2.0, b.Float.ref("haversine_final"))
|
|
141
|
+
return haversine_final
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from relationalai.semantics.internal import internal as i
|
|
4
|
+
from relationalai.semantics.metamodel.util import OrderedSet
|
|
5
|
+
from .std import _Integer, _String, _make_expr
|
|
6
|
+
from typing import Literal, Any
|
|
7
|
+
from .. import std
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def escape(regex: _String) -> i.Expression:
|
|
11
|
+
return _make_expr("escape_regex_metachars", regex, i.String.ref())
|
|
12
|
+
|
|
13
|
+
class Match(i.Producer):
|
|
14
|
+
|
|
15
|
+
def __init__(self, regex: _String, string: _String, pos: _Integer = 0, _type: Literal["search", "fullmatch", "match"] = "match"):
|
|
16
|
+
super().__init__(i.find_model([regex, string, pos]))
|
|
17
|
+
self.regex = regex
|
|
18
|
+
self.string = string
|
|
19
|
+
self.pos = pos
|
|
20
|
+
|
|
21
|
+
if _type == "match":
|
|
22
|
+
self._expr = _regex_match_all(self.regex, self.string, std.cast_to_int64(self.pos + 1))
|
|
23
|
+
self._offset, self._full_match = self._expr._arg_ref(2), self._expr._arg_ref(3)
|
|
24
|
+
elif _type == "search":
|
|
25
|
+
raise NotImplementedError("`search` is not implemented")
|
|
26
|
+
elif _type == "fullmatch":
|
|
27
|
+
_exp = _regex_match_all(self.regex, self.string, std.cast_to_int64(self.pos + 1))
|
|
28
|
+
self._offset, self._full_match = _exp._arg_ref(2), _exp._arg_ref(3)
|
|
29
|
+
self._expr = self._full_match == std.strings.substring(self.string, std.cast_to_int64(self.pos), std.strings.len(self.string))
|
|
30
|
+
|
|
31
|
+
def group(self, index: _Integer = 0) -> i.Producer:
|
|
32
|
+
if index == 0:
|
|
33
|
+
return self._full_match
|
|
34
|
+
else:
|
|
35
|
+
return _make_expr("capture_group_by_index", self.regex, self.string, std.cast_to_int64(self.pos + 1), std.cast_to_int64(index), i.String.ref("res"))
|
|
36
|
+
|
|
37
|
+
def group_by_name(self, name: _String) -> i.Producer:
|
|
38
|
+
return _make_expr("capture_group_by_name", self.regex, self.string, std.cast_to_int64(self.pos + 1), name, i.String.ref("res"))
|
|
39
|
+
|
|
40
|
+
def start(self) -> i.Expression:
|
|
41
|
+
return self._offset - 1
|
|
42
|
+
|
|
43
|
+
def end(self) -> i.Expression:
|
|
44
|
+
return std.strings.len(self.group(0)) + self.start() - 1
|
|
45
|
+
|
|
46
|
+
def span(self) -> tuple[i.Producer, i.Producer]:
|
|
47
|
+
return self.start(), self.end()
|
|
48
|
+
|
|
49
|
+
def _to_keys(self) -> OrderedSet[Any]:
|
|
50
|
+
return i.find_keys(self._expr)
|
|
51
|
+
|
|
52
|
+
def _compile_lookup(self, compiler:i.Compiler, ctx:i.CompilerContext):
|
|
53
|
+
compiler.lookup(self.regex, ctx)
|
|
54
|
+
compiler.lookup(self.string, ctx)
|
|
55
|
+
compiler.lookup(self.pos, ctx)
|
|
56
|
+
return compiler.lookup(self._expr, ctx)
|
|
57
|
+
|
|
58
|
+
def __getattr__(self, name: str) -> Any:
|
|
59
|
+
return object.__getattribute__(self, name)
|
|
60
|
+
|
|
61
|
+
def __setattr__(self, name: str, value: Any) -> None:
|
|
62
|
+
object.__setattr__(self, name, value)
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def match(regex: _String, string: _String) -> Match:
|
|
66
|
+
return Match(regex, string)
|
|
67
|
+
|
|
68
|
+
def search(regex: _String, string: _String, pos: _Integer = 0) -> Match:
|
|
69
|
+
return Match(regex, string, pos, _type="search")
|
|
70
|
+
|
|
71
|
+
def fullmatch(regex: _String, string: _String, pos: _Integer = 0) -> Match:
|
|
72
|
+
return Match(regex, string, pos, _type="fullmatch")
|
|
73
|
+
|
|
74
|
+
def findall(regex: _String, string: _String) -> tuple[i.Producer, i.Producer]:
|
|
75
|
+
exp = _regex_match_all(regex, string)
|
|
76
|
+
ix, match = exp._arg_ref(2), exp._arg_ref(3)
|
|
77
|
+
rank = i.rank(i.asc(ix, match))
|
|
78
|
+
return rank, match
|
|
79
|
+
|
|
80
|
+
def _regex_match_all(regex: _String, string: _String, pos: _Integer|None = None) -> i.Expression:
|
|
81
|
+
if pos is None:
|
|
82
|
+
pos = i.Int64.ref()
|
|
83
|
+
return _make_expr("regex_match_all", regex, string, pos, i.String.ref())
|
|
@@ -15,7 +15,7 @@ def concat(s0: _String, s1: _String, *args: _String) -> b.Expression:
|
|
|
15
15
|
return res
|
|
16
16
|
|
|
17
17
|
def len(s: _String) -> b.Expression:
|
|
18
|
-
return _make_expr("num_chars", s, b.
|
|
18
|
+
return _make_expr("num_chars", s, b.Int64.ref("res"))
|
|
19
19
|
|
|
20
20
|
def startswith(s0: _String, s1: _String) -> b.Expression:
|
|
21
21
|
return _make_expr("starts_with", s0, s1)
|