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.
Files changed (42) hide show
  1. relationalai/clients/snowflake.py +44 -15
  2. relationalai/clients/types.py +1 -0
  3. relationalai/clients/use_index_poller.py +446 -178
  4. relationalai/early_access/builder/std/__init__.py +1 -1
  5. relationalai/early_access/dsl/bindings/csv.py +4 -4
  6. relationalai/semantics/internal/internal.py +22 -4
  7. relationalai/semantics/lqp/executor.py +69 -18
  8. relationalai/semantics/lqp/intrinsics.py +23 -0
  9. relationalai/semantics/lqp/model2lqp.py +16 -6
  10. relationalai/semantics/lqp/passes.py +3 -4
  11. relationalai/semantics/lqp/primitives.py +38 -14
  12. relationalai/semantics/metamodel/builtins.py +152 -11
  13. relationalai/semantics/metamodel/factory.py +3 -2
  14. relationalai/semantics/metamodel/helpers.py +78 -2
  15. relationalai/semantics/reasoners/graph/core.py +343 -40
  16. relationalai/semantics/reasoners/optimization/solvers_dev.py +20 -1
  17. relationalai/semantics/reasoners/optimization/solvers_pb.py +24 -3
  18. relationalai/semantics/rel/compiler.py +5 -17
  19. relationalai/semantics/rel/executor.py +2 -2
  20. relationalai/semantics/rel/rel.py +6 -0
  21. relationalai/semantics/rel/rel_utils.py +37 -1
  22. relationalai/semantics/rel/rewrite/extract_common.py +153 -242
  23. relationalai/semantics/sql/compiler.py +540 -202
  24. relationalai/semantics/sql/executor/duck_db.py +21 -0
  25. relationalai/semantics/sql/executor/result_helpers.py +7 -0
  26. relationalai/semantics/sql/executor/snowflake.py +9 -2
  27. relationalai/semantics/sql/rewrite/denormalize.py +4 -6
  28. relationalai/semantics/sql/rewrite/recursive_union.py +23 -3
  29. relationalai/semantics/sql/sql.py +120 -46
  30. relationalai/semantics/std/__init__.py +9 -4
  31. relationalai/semantics/std/datetime.py +363 -0
  32. relationalai/semantics/std/math.py +77 -0
  33. relationalai/semantics/std/re.py +83 -0
  34. relationalai/semantics/std/strings.py +1 -1
  35. relationalai/tools/cli_controls.py +445 -60
  36. relationalai/util/format.py +78 -1
  37. {relationalai-0.11.2.dist-info → relationalai-0.11.4.dist-info}/METADATA +3 -2
  38. {relationalai-0.11.2.dist-info → relationalai-0.11.4.dist-info}/RECORD +41 -39
  39. relationalai/semantics/std/dates.py +0 -213
  40. {relationalai-0.11.2.dist-info → relationalai-0.11.4.dist-info}/WHEEL +0 -0
  41. {relationalai-0.11.2.dist-info → relationalai-0.11.4.dist-info}/entry_points.txt +0 -0
  42. {relationalai-0.11.2.dist-info → relationalai-0.11.4.dist-info}/licenses/LICENSE +0 -0
@@ -1,8 +1,10 @@
1
1
  from __future__ import annotations
2
2
 
3
3
  import duckdb
4
+ import math
4
5
  from pandas import DataFrame
5
6
  from typing import Any, Union, Literal
7
+ from scipy.special import erfinv as special_erfinv
6
8
 
7
9
  from relationalai.semantics.sql import Compiler
8
10
  from relationalai.semantics.sql.executor.result_helpers import format_duckdb_columns
@@ -18,10 +20,29 @@ class DuckDBExecutor(e.Executor):
18
20
  """ Execute the SQL query directly. """
19
21
  if format != "pandas":
20
22
  raise ValueError(f"Unsupported format: {format}")
23
+
21
24
  connection = duckdb.connect()
25
+
26
+ # Register scalar functions
27
+ connection.create_function("erf", self.erf)
28
+ connection.create_function("acot", self.acot)
29
+ connection.create_function("erfinv", self.erfinv)
30
+
22
31
  try:
23
32
  sql, _ = self.compiler.compile(model, {"is_duck_db": True})
24
33
  arrow_table = connection.query(sql).fetch_arrow_table()
25
34
  return format_duckdb_columns(arrow_table.to_pandas(), arrow_table.schema)
26
35
  finally:
27
36
  connection.close()
37
+
38
+ @staticmethod
39
+ def erf(x: float) -> float:
40
+ return math.erf(x)
41
+
42
+ @staticmethod
43
+ def erfinv(x: float) -> float:
44
+ return special_erfinv(x)
45
+
46
+ @staticmethod
47
+ def acot(x: float) -> float:
48
+ return math.atan(1 / x) if x != 0 else math.copysign(math.pi / 2, x)
@@ -35,7 +35,14 @@ def format_columns(result_frame:pd.DataFrame, result_metadata:Sequence[ResultMet
35
35
  result_frame[col] = series.astype(Int128Dtype())
36
36
  elif col_metadata.precision:
37
37
  result_frame[col] = _cast_integer_column(series, col_metadata.precision)
38
+ # Handle Snowflake VARIANT columns that are actually numeric
39
+ elif FIELD_ID_TO_NAME[col_metadata.type_code] == "VARIANT":
40
+ series = result_frame[col]
38
41
 
42
+ if col_type == Int64:
43
+ result_frame[col] = series.astype("Int64")
44
+ elif col_type == Int128:
45
+ result_frame[col] = series.astype(Int128Dtype())
39
46
 
40
47
  # SQL may return None for nulls; replace with np.nan unless the column is FIXED, because
41
48
  # both Int64 and Int128 already deal with nulls using pd.NA
@@ -65,9 +65,15 @@ class SnowflakeExecutor(e.Executor):
65
65
  update: bool = False) -> Union[pd.DataFrame, Any]:
66
66
  """ Execute the SQL query directly. """
67
67
 
68
+ warehouse = self.resources.config.get("warehouse", None)
69
+ default_dynamic_table_target_lag = (
70
+ self.resources.config.get("reasoner.rule.sql.default_dynamic_table_target_lag", None))
71
+
72
+ options = {"warehouse": warehouse, "default_dynamic_table_target_lag": default_dynamic_table_target_lag}
73
+
68
74
  if self._last_model != model:
69
75
  with debugging.span("compile", metamodel=model) as model_span:
70
- model_sql, _ = self.compiler.compile(model)
76
+ model_sql, _ = self.compiler.compile(model, options)
71
77
  model_span["compile_type"] = "model"
72
78
  model_span["sql"] = model_sql
73
79
  self._last_model = model
@@ -77,7 +83,8 @@ class SnowflakeExecutor(e.Executor):
77
83
  # compile into sql and keep the new_task, which is the task model after rewrites,
78
84
  # as it may contain results of type inference, which is useful for determining
79
85
  # how to format the outputs
80
- query_sql, new_task = self.compiler.compile(f.compute_model(f.logical([task])), {"query_compilation": True})
86
+ query_options = {**options, "query_compilation": True}
87
+ query_sql, new_task = self.compiler.compile(f.compute_model(f.logical([task])), query_options)
81
88
  compile_span["compile_type"] = "query"
82
89
  compile_span["sql"] = query_sql
83
90
 
@@ -25,7 +25,6 @@ class Denormalize(c.Pass):
25
25
 
26
26
  @dataclass
27
27
  class OldDenormalize(visitor.Rewriter):
28
- # TODO: use the new Rewriter when available.
29
28
 
30
29
  denormalized: dict[ir.Relation, ir.Relation] = field(default_factory=dict, init=False, hash=False, compare=False)
31
30
 
@@ -168,13 +167,12 @@ class OldDenormalize(visitor.Rewriter):
168
167
  """ Denormalize the relations that can be denormalized.
169
168
 
170
169
  Group together relations that are keyed by the same "entity". This method defines
171
- entities as being types that haver a unary relation containing only that type. All
170
+ entities as being types that have a unary relation containing only that type. All
172
171
  relations whose first argument is this type are grouped together.
173
172
 
174
173
  Returns a tuple with 2 elements:
175
- 1. the new set of relations after denormalization
176
- 2. a dict from relations that were denormalized away to the new relation that took
177
- its place.
174
+ 1. The new set of relations after denormalization
175
+ 2. A dict from relations that were denormalized away to the new relation that took its place.
178
176
  """
179
177
  new_relations = ordered_set()
180
178
  denormalized: dict[ir.Relation, ir.Relation] = dict()
@@ -184,7 +182,7 @@ class OldDenormalize(visitor.Rewriter):
184
182
  entity_relations: dict[ir.Type, ir.Relation] = dict()
185
183
 
186
184
  for r in relations:
187
- if len(r.fields) == 1 and not types.is_builtin(r.fields[0].type):
185
+ if len(r.fields) == 1 and not types.is_builtin(r.fields[0].type) and not r.name == 'Error':
188
186
  e = r.fields[0].type
189
187
  entity_types.add(e)
190
188
  entity_relations[e] = r
@@ -49,9 +49,29 @@ class RecursiveUnion(c.Pass):
49
49
  new_body = [logical for logical in root_logical.body if logical not in recursive_logicals]
50
50
 
51
51
  # Step 4: Add unions for each recursive group
52
- for group in recursive_groups.values():
53
- if group:
54
- new_body.append(f.union(list(group)))
52
+ for rel_id, logical_group in recursive_groups.items():
53
+ split_group = ordered_set()
54
+
55
+ for logical in logical_group:
56
+ # Count total ir.Update tasks in this logical
57
+ update_count = sum(isinstance(t, ir.Update) for t in logical.body)
58
+
59
+ # If there's only one, keep the original logical as-is
60
+ if update_count == 1:
61
+ split_group.add(logical)
62
+ continue
63
+
64
+ # Otherwise, keep only updates relevant to this relation (and non-update tasks)
65
+ filtered_body = [
66
+ t for t in logical.body
67
+ if not isinstance(t, ir.Update) or t.relation.id == rel_id
68
+ ]
69
+
70
+ if filtered_body:
71
+ split_group.add(f.logical(filtered_body))
72
+
73
+ if split_group:
74
+ new_body.append(f.union(list(split_group)))
55
75
 
56
76
  return model.reconstruct(model.engines, model.relations, model.types, f.logical(new_body), model.annotations)
57
77
 
@@ -6,7 +6,7 @@ from __future__ import annotations
6
6
  import datetime
7
7
  from dataclasses import dataclass
8
8
  from io import StringIO
9
- from typing import Optional, Union, Tuple, Any
9
+ from typing import Optional, Union, Tuple, Any, Sequence
10
10
  from relationalai.semantics.metamodel.util import Printer as BasePrinter
11
11
  import json
12
12
 
@@ -43,17 +43,35 @@ class CreateTable(Node):
43
43
  table: Table
44
44
  if_not_exists: bool = False
45
45
 
46
+ @dataclass(frozen=True)
47
+ class CreateDynamicTable(Node):
48
+ name: str
49
+ query: Union[list[Select], CTE]
50
+ target_lag: str
51
+ warehouse: str
52
+
46
53
  @dataclass(frozen=True)
47
54
  class CreateView(Node):
48
55
  name: str
49
- query: Union[Select, CTE]
56
+ query: Union[list[Select], CTE]
57
+
58
+ @dataclass(frozen=True)
59
+ class CreateFunction(Node):
60
+ name: str
61
+ inputs: list[Column]
62
+ return_type: str
63
+ body: str
64
+ language: str = "PYTHON"
65
+ runtime_version: str = "3.11"
66
+ handler: str = "compute"
67
+ packages: Optional[list[str]] = None
50
68
 
51
69
  @dataclass(frozen=True)
52
70
  class Insert(Node):
53
71
  table: str
54
72
  columns: list[str]
55
73
  values: list[Tuple[Any, ...]]
56
- select: Optional[Select]
74
+ query: Optional[Union[Select, CTE]] = None
57
75
 
58
76
  @dataclass(frozen=True)
59
77
  class Update(Node):
@@ -69,11 +87,12 @@ class UpdateSet(Node):
69
87
  @dataclass(frozen=True)
70
88
  class Select(Node):
71
89
  distinct: bool
72
- vars: list[Union[VarRef, RowNumberVar, int]]
73
- froms: Union[list[From], Select]
90
+ vars: Optional[Sequence[Union[VarRef, RowNumberVar, int]]] = None
91
+ froms: Optional[Union[list[From], Select]] = None
74
92
  where: Optional[Where] = None
75
93
  joins: Optional[list[Join]] = None
76
94
  group_by: Optional[list[VarRef]] = None
95
+ order_by: Optional[list[VarRef]] = None
77
96
  limit: Optional[int] = None
78
97
  is_output: bool = False
79
98
 
@@ -86,11 +105,12 @@ class VarRef(Node):
86
105
  name: str
87
106
  column: Optional[str] = None
88
107
  alias: Optional[str] = None
108
+ type: Optional[str] = None
89
109
 
90
110
  @dataclass(frozen=True)
91
111
  class From(Node):
92
112
  table: str
93
- alias: Optional[str]
113
+ alias: Optional[str] = None
94
114
 
95
115
  @dataclass(frozen=True)
96
116
  class Join(Node):
@@ -106,11 +126,16 @@ class LeftOuterJoin(Join):
106
126
  class FullOuterJoin(Join):
107
127
  pass
108
128
 
129
+ @dataclass(frozen=True)
130
+ class JoinWithoutCondition(Join):
131
+ pass
132
+
109
133
  @dataclass(frozen=True)
110
134
  class RowNumberVar(Node):
111
135
  order_by_vars: list[OrderByVar]
112
136
  partition_by_vars: list[str]
113
137
  alias: str
138
+ type: Optional[str] = None
114
139
 
115
140
  @dataclass(frozen=True)
116
141
  class OrderByVar(Node):
@@ -120,7 +145,7 @@ class OrderByVar(Node):
120
145
  # TODO: consider removing Where and make Select.where: Expr
121
146
  @dataclass(frozen=True)
122
147
  class Where(Node):
123
- expression: Expr
148
+ expression: Optional[Expr] = None
124
149
 
125
150
  @dataclass(frozen=True)
126
151
  class RawSource(Node):
@@ -139,6 +164,7 @@ class CTE(Node):
139
164
  name: str
140
165
  columns: list[str]
141
166
  selects: list[Select]
167
+ distinct: bool = False
142
168
 
143
169
  #-------------------------------------------------
144
170
  # Expressions
@@ -216,10 +242,9 @@ class Printer(BasePrinter):
216
242
  self._print(", ")
217
243
  self._print_value(v, True)
218
244
  elif isinstance(value, str):
219
- if quote_strings:
220
- self._print(json.dumps(value))
221
- else:
222
- self._print(value)
245
+ value = 'NULL' if value.lower() == 'none' else value
246
+ value = json.dumps(value) if quote_strings else value
247
+ self._print(value)
223
248
  elif isinstance(value, bool):
224
249
  self._print(str(value).lower())
225
250
  elif isinstance(value, datetime.date):
@@ -229,6 +254,18 @@ class Printer(BasePrinter):
229
254
  else:
230
255
  self._print(str(value))
231
256
 
257
+ def _print_query(self, indent, node, union_type:str = "UNION ALL"):
258
+ queries = node.query if isinstance(node.query, list) else [node.query]
259
+ if len(queries) == 1:
260
+ self.print_node(queries[0], indent, True)
261
+ else:
262
+ for i, s in enumerate(queries):
263
+ self._nl()
264
+ if i != 0:
265
+ self._indent_print_nl(indent + 2, union_type)
266
+ self.print_node(s, indent + 1, True)
267
+ self._print(";")
268
+
232
269
  def _get_table_name(self, name: str) -> str:
233
270
  return f'"{name}"' if self._is_reserved_name(name) else name
234
271
 
@@ -251,12 +288,31 @@ class Printer(BasePrinter):
251
288
  elif isinstance(node, CreateTable):
252
289
  clause = "IF NOT EXISTS " if node.if_not_exists else ""
253
290
  self._print(f"CREATE TABLE {clause}{node.table};")
291
+ elif isinstance(node, CreateDynamicTable):
292
+ self._print_nl(f"CREATE DYNAMIC TABLE {self._get_table_name(node.name)}")
293
+ self._indent_print_nl(indent+1, f"TARGET_LAG = '{node.target_lag}'")
294
+ self._indent_print_nl(indent+1, f"WAREHOUSE = '{node.warehouse}'")
295
+ self._print("AS ")
296
+ self._print_query(indent, node)
254
297
  elif isinstance(node, CreateView):
255
- # TODO - crying a bit inside :(
256
- self._print_nl(f"DROP TABLE IF EXISTS {self._get_table_name(node.name)};")
257
298
  self._print(f"CREATE VIEW {self._get_table_name(node.name)} AS ")
258
- self.print_node(node.query, indent, True)
259
- self._print(";")
299
+ self._print_query(indent, node, "UNION")
300
+ elif isinstance(node, CreateFunction):
301
+ self._print(f"CREATE OR REPLACE FUNCTION {self._get_table_name(node.name)} (")
302
+ self._join(node.inputs)
303
+ self._print_nl(")")
304
+ self._print_nl(f"RETURNS {node.return_type}")
305
+ self._print_nl(f"LANGUAGE {node.language}")
306
+ self._print_nl(f"RUNTIME_VERSION = '{node.runtime_version}'")
307
+ self._print_nl(f"HANDLER = '{node.handler}'")
308
+ if node.packages:
309
+ self._print("PACKAGES = (")
310
+ self._join(node.packages)
311
+ self._print_nl(")")
312
+ self._print_nl("AS ")
313
+ self._print_nl("$$")
314
+ self._print_nl(node.body)
315
+ self._print("$$;")
260
316
  elif isinstance(node, Insert):
261
317
  self._print(f"INSERT INTO {self._get_table_name(node.table)} ")
262
318
  if len(node.columns) > 0:
@@ -271,9 +327,9 @@ class Printer(BasePrinter):
271
327
  self._print(" UNION ALL ")
272
328
  self._print("SELECT ")
273
329
  self._join(value)
274
- if node.select is not None:
275
- self.print_node(node.select, indent, True)
276
- self._print(";")
330
+ self._print(";")
331
+ if node.query is not None:
332
+ self._print_query(indent, node)
277
333
  elif isinstance(node, Update):
278
334
  self._print(f"UPDATE {self._get_table_name(node.table)} SET ")
279
335
  self._join(node.set)
@@ -286,13 +342,16 @@ class Printer(BasePrinter):
286
342
  self._indent_print(indent, "SELECT ")
287
343
  if node.distinct and node.froms:
288
344
  self._print("DISTINCT ")
289
- self._join(node.vars, is_output=node.is_output or is_output)
345
+ if node.vars:
346
+ self._join(node.vars, is_output=node.is_output or is_output)
347
+ else:
348
+ self._print("*")
290
349
  if node.froms:
291
350
  self._print(" FROM ")
292
351
  if isinstance(node.froms, Select):
293
- # If `froms` is a `Select`, we need to print it inline
352
+ # If `froms` is a `Select`, we need to print it inline, and we do not need to indent it
294
353
  self._print("( ")
295
- self.print_node(node.froms, indent, True)
354
+ self.print_node(node.froms, 0, True)
296
355
  self._print(" )")
297
356
  else:
298
357
  self._join(node.froms)
@@ -304,6 +363,9 @@ class Printer(BasePrinter):
304
363
  if node.group_by:
305
364
  self._print(" GROUP BY ")
306
365
  self._join(node.group_by)
366
+ if node.order_by:
367
+ self._print(" ORDER BY ")
368
+ self._join(node.order_by)
307
369
  if node.limit:
308
370
  self._print(f" LIMIT {node.limit}")
309
371
  if not inlined:
@@ -321,41 +383,48 @@ class Printer(BasePrinter):
321
383
  if not inlined:
322
384
  self._print(";")
323
385
  elif isinstance(node, VarRef):
386
+ # --- Handle column or name ---
324
387
  if node.column is None:
325
388
  self._print(node.name)
389
+ elif node.column.lower() in ("any", "order"):
390
+ self._print(f'{node.name}."{node.column}"')
326
391
  else:
327
- if node.column.lower() in ("any", "order"):
328
- self._print(f'{node.name}."{node.column}"')
329
- else:
330
- self._print(f"{node.name}.{node.column}")
392
+ self._print(f"{node.name}.{node.column}")
393
+ # --- Add CAST if needed ---
394
+ if node.type:
395
+ self._print(f"::{node.type}")
396
+ # --- Handle alias ---
331
397
  if node.alias:
398
+ needs_quotes = any(c in node.alias for c in ['-', '?'])
332
399
  if is_output:
333
400
  self._print(f' as "{node.alias}"')
334
- elif node.alias != node.column:
335
- if any(c in node.alias for c in ['-', '?']):
336
- self._print(f' as "{node.alias}"')
337
- else:
338
- self._print(f" as {node.alias}")
401
+ elif node.alias != node.column or node.type or needs_quotes:
402
+ alias_str = f'"{node.alias}"' if needs_quotes else node.alias
403
+ self._print(f" as {alias_str}")
339
404
  elif isinstance(node, RowNumberVar):
405
+ # --- ROW_NUMBER clause ---
340
406
  self._print("ROW_NUMBER() OVER (")
407
+ # --- Partition by (if present) ---
341
408
  if node.partition_by_vars:
342
409
  self._print(" PARTITION BY ")
343
410
  self._join(node.partition_by_vars)
411
+ # --- ORDER BY ---
344
412
  self._print(" ORDER BY ")
345
413
  self._join(node.order_by_vars)
346
- if is_output:
347
- self._print(f' ) as "{node.alias}"')
348
- else:
349
- self._print(f' ) as {node.alias}')
414
+ self._print(" )")
415
+ # --- Add CAST if needed ---
416
+ if node.type:
417
+ self._print(f"::{node.type}")
418
+ # --- Handle alias ---
419
+ alias_str = f' as "{node.alias}"' if is_output else f' as {node.alias}'
420
+ self._print(alias_str)
350
421
  elif isinstance(node, OrderByVar):
351
422
  self._print(node.var)
352
- if node.is_ascending:
353
- self._print(" ASC")
354
- else:
355
- self._print(" DESC")
423
+ direction = "ASC" if node.is_ascending else "DESC"
424
+ self._print(f" {direction}")
356
425
  elif isinstance(node, From):
357
426
  self._print(self._get_table_name(node.table))
358
- if node.alias is not None:
427
+ if node.alias:
359
428
  self._print(f" AS {node.alias}")
360
429
  elif isinstance(node, Join):
361
430
  join_type = (
@@ -366,13 +435,15 @@ class Printer(BasePrinter):
366
435
  self._print(f"{join_type} {self._get_table_name(node.table)}")
367
436
  if node.alias is not None:
368
437
  self._print(f" AS {node.alias}")
369
- self._print(" ON ")
370
- if node.on is None:
371
- self._print("TRUE")
372
- else:
373
- self.print_node(node.on, indent, True)
438
+ if not isinstance(node, JoinWithoutCondition):
439
+ self._print(" ON ")
440
+ if node.on is None:
441
+ self._print("TRUE")
442
+ else:
443
+ self.print_node(node.on, indent, True)
374
444
  elif isinstance(node, Where):
375
- self._print(f" WHERE {node.expression}")
445
+ expr = node.expression if node.expression else 'FALSE'
446
+ self._print(f" WHERE {expr}")
376
447
  elif isinstance(node, RawSource):
377
448
  self._print(node.src)
378
449
 
@@ -390,7 +461,10 @@ class Printer(BasePrinter):
390
461
  self._indent_print_nl(indent + 2, "UNION ALL")
391
462
  self.print_node(s, indent + 1, True)
392
463
  self._nl()
393
- self._print(f") SELECT * FROM {self._get_table_name(node.name)}")
464
+ self._print(") SELECT")
465
+ if node.distinct:
466
+ self._print(" DISTINCT")
467
+ self._print(f" * FROM {self._get_table_name(node.name)}")
394
468
  if not inlined:
395
469
  self._print(";")
396
470
  # --------------------------------------------------
@@ -3,7 +3,7 @@ from typing import Any
3
3
 
4
4
  from relationalai.semantics.internal import internal as i
5
5
  from .std import _Date, _DateTime, _Number, _String, _Integer, _make_expr
6
- from . import dates, math, strings, decimals, integers, floats, pragmas, constraints
6
+ from . import datetime, math, strings, decimals, integers, floats, pragmas, constraints, re
7
7
 
8
8
  def range(*args: _Integer) -> i.Expression:
9
9
  # supports range(stop), range(start, stop), range(start, stop, step)
@@ -23,9 +23,12 @@ def hash(*args: Any, type=i.Hash) -> i.Expression:
23
23
  raise ValueError("hash expects at least one argument")
24
24
  return _make_expr("hash", i.TupleArg(args), type.ref("res"))
25
25
 
26
- def uuid_to_string(arg:_Integer) -> i.Expression:
26
+ def uuid_to_string(arg: _Integer) -> i.Expression:
27
27
  return _make_expr("uuid_to_string", arg, i.String.ref("res"))
28
28
 
29
+ def parse_uuid(arg: _String) -> i.Expression:
30
+ return _make_expr("parse_uuid", arg, i.Hash.ref("res"))
31
+
29
32
  def cast(type: i.Concept, arg: _Date|_DateTime|_Number|_String) -> i.Expression:
30
33
  return _make_expr("cast", i.TypeRef(type), arg, type.ref("res"))
31
34
 
@@ -37,13 +40,15 @@ __all__ = [
37
40
  "range",
38
41
  "hash",
39
42
  "cast",
40
- "dates",
43
+ "datetime",
41
44
  "math",
42
45
  "strings",
46
+ "re",
43
47
  "decimals",
44
48
  "integers",
45
49
  "floats",
46
50
  "pragmas",
47
51
  "constraints",
48
- "uuid_to_string"
52
+ "uuid_to_string",
53
+ "parse_uuid",
49
54
  ]