relationalai 0.12.4__py3-none-any.whl → 0.12.7__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/__init__.py +4 -0
- relationalai/clients/snowflake.py +34 -13
- relationalai/early_access/lqp/constructors/__init__.py +2 -2
- relationalai/early_access/metamodel/rewrite/__init__.py +2 -2
- relationalai/{semantics/reasoners/graph → experimental}/paths/README.md +2 -2
- relationalai/experimental/paths/__init__.py +14 -309
- relationalai/{semantics/reasoners/graph → experimental}/paths/examples/basic_example.py +2 -2
- relationalai/{semantics/reasoners/graph → experimental}/paths/examples/paths_benchmark.py +2 -2
- relationalai/{semantics/reasoners/graph → experimental}/paths/examples/paths_example.py +2 -2
- relationalai/{semantics/reasoners/graph → experimental}/paths/examples/pattern_to_automaton.py +1 -1
- relationalai/{semantics/reasoners/graph → experimental}/paths/path_algorithms/one_sided_ball_repetition.py +1 -1
- relationalai/{semantics/reasoners/graph → experimental}/paths/path_algorithms/single.py +3 -3
- relationalai/{semantics/reasoners/graph → experimental}/paths/path_algorithms/two_sided_balls_repetition.py +1 -1
- relationalai/{semantics/reasoners/graph → experimental}/paths/path_algorithms/two_sided_balls_upto.py +2 -2
- relationalai/{semantics/reasoners/graph → experimental}/paths/path_algorithms/usp-old.py +3 -3
- relationalai/{semantics/reasoners/graph → experimental}/paths/path_algorithms/usp-tuple.py +3 -3
- relationalai/{semantics/reasoners/graph → experimental}/paths/path_algorithms/usp.py +3 -3
- relationalai/{semantics/reasoners/graph → experimental}/paths/tests/tests_limit_sp_max_length.py +2 -2
- relationalai/{semantics/reasoners/graph → experimental}/paths/tests/tests_limit_sp_multiple.py +2 -2
- relationalai/{semantics/reasoners/graph → experimental}/paths/tests/tests_limit_sp_single.py +2 -2
- relationalai/{semantics/reasoners/graph → experimental}/paths/tests/tests_limit_walks_multiple.py +2 -2
- relationalai/{semantics/reasoners/graph → experimental}/paths/tests/tests_limit_walks_single.py +2 -2
- relationalai/{semantics/reasoners/graph → experimental}/paths/tests/tests_one_sided_ball_repetition_multiple.py +2 -2
- relationalai/{semantics/reasoners/graph → experimental}/paths/tests/tests_one_sided_ball_repetition_single.py +2 -2
- relationalai/{semantics/reasoners/graph → experimental}/paths/tests/tests_one_sided_ball_upto_multiple.py +2 -2
- relationalai/{semantics/reasoners/graph → experimental}/paths/tests/tests_one_sided_ball_upto_single.py +2 -2
- relationalai/{semantics/reasoners/graph → experimental}/paths/tests/tests_single_paths.py +2 -2
- relationalai/{semantics/reasoners/graph → experimental}/paths/tests/tests_single_walks.py +2 -2
- relationalai/{semantics/reasoners/graph → experimental}/paths/tests/tests_single_walks_undirected.py +2 -2
- relationalai/{semantics/reasoners/graph → experimental}/paths/tests/tests_two_sided_balls_repetition_multiple.py +2 -2
- relationalai/{semantics/reasoners/graph → experimental}/paths/tests/tests_two_sided_balls_repetition_single.py +2 -2
- relationalai/{semantics/reasoners/graph → experimental}/paths/tests/tests_two_sided_balls_upto_multiple.py +2 -2
- relationalai/{semantics/reasoners/graph → experimental}/paths/tests/tests_two_sided_balls_upto_single.py +2 -2
- relationalai/{semantics/reasoners/graph → experimental}/paths/tests/tests_usp_nsp_multiple.py +2 -2
- relationalai/{semantics/reasoners/graph → experimental}/paths/tests/tests_usp_nsp_single.py +2 -2
- relationalai/semantics/__init__.py +4 -0
- relationalai/semantics/internal/annotations.py +1 -0
- relationalai/semantics/internal/internal.py +3 -4
- relationalai/semantics/internal/snowflake.py +14 -1
- relationalai/semantics/lqp/builtins.py +1 -0
- relationalai/semantics/lqp/constructors.py +0 -5
- relationalai/semantics/lqp/executor.py +34 -10
- relationalai/semantics/lqp/intrinsics.py +2 -2
- relationalai/semantics/lqp/model2lqp.py +105 -9
- relationalai/semantics/lqp/passes.py +27 -8
- relationalai/semantics/lqp/primitives.py +18 -15
- relationalai/semantics/lqp/rewrite/__init__.py +2 -2
- relationalai/semantics/lqp/rewrite/{fd_constraints.py → function_annotations.py} +4 -4
- relationalai/semantics/lqp/utils.py +17 -13
- relationalai/semantics/metamodel/builtins.py +50 -1
- relationalai/semantics/metamodel/typer/typer.py +3 -0
- relationalai/semantics/reasoners/__init__.py +4 -0
- relationalai/semantics/reasoners/experimental/__init__.py +7 -0
- relationalai/semantics/reasoners/graph/core.py +1154 -122
- relationalai/semantics/rel/builtins.py +3 -1
- relationalai/semantics/rel/compiler.py +2 -2
- relationalai/semantics/rel/executor.py +30 -8
- relationalai/semantics/rel/rel_utils.py +5 -0
- relationalai/semantics/snowflake/__init__.py +2 -2
- relationalai/semantics/sql/compiler.py +6 -0
- relationalai/semantics/sql/executor/snowflake.py +6 -2
- relationalai/semantics/tests/test_snapshot_abstract.py +5 -4
- {relationalai-0.12.4.dist-info → relationalai-0.12.7.dist-info}/METADATA +2 -2
- {relationalai-0.12.4.dist-info → relationalai-0.12.7.dist-info}/RECORD +99 -115
- {relationalai-0.12.4.dist-info → relationalai-0.12.7.dist-info}/WHEEL +1 -1
- relationalai/early_access/paths/__init__.py +0 -22
- relationalai/early_access/paths/api/__init__.py +0 -12
- relationalai/early_access/paths/benchmarks/__init__.py +0 -13
- relationalai/early_access/paths/graph/__init__.py +0 -12
- relationalai/early_access/paths/path_algorithms/find_paths/__init__.py +0 -12
- relationalai/early_access/paths/path_algorithms/one_sided_ball_repetition/__init__.py +0 -12
- relationalai/early_access/paths/path_algorithms/one_sided_ball_upto/__init__.py +0 -12
- relationalai/early_access/paths/path_algorithms/single/__init__.py +0 -12
- relationalai/early_access/paths/path_algorithms/two_sided_balls_repetition/__init__.py +0 -12
- relationalai/early_access/paths/path_algorithms/two_sided_balls_upto/__init__.py +0 -12
- relationalai/early_access/paths/path_algorithms/usp/__init__.py +0 -12
- relationalai/early_access/paths/rpq/__init__.py +0 -13
- relationalai/early_access/paths/utilities/iterators/__init__.py +0 -12
- relationalai/experimental/paths/pathfinder.rel +0 -2560
- relationalai/semantics/reasoners/graph/paths/__init__.py +0 -16
- relationalai/semantics/reasoners/graph/paths/path_algorithms/__init__.py +0 -3
- relationalai/semantics/reasoners/graph/paths/utilities/__init__.py +0 -3
- /relationalai/{semantics/reasoners/graph → experimental}/paths/api.py +0 -0
- /relationalai/{semantics/reasoners/graph → experimental}/paths/benchmarks/__init__.py +0 -0
- /relationalai/{semantics/reasoners/graph → experimental}/paths/benchmarks/grid_graph.py +0 -0
- /relationalai/{semantics/reasoners/graph → experimental}/paths/code_organization.md +0 -0
- /relationalai/{semantics/reasoners/graph → experimental}/paths/examples/Movies.ipynb +0 -0
- /relationalai/{semantics/reasoners/graph → experimental}/paths/examples/minimal_engine_warmup.py +0 -0
- /relationalai/{semantics/reasoners/graph → experimental}/paths/examples/movie_example.py +0 -0
- /relationalai/{semantics/reasoners/graph → experimental}/paths/examples/movies_data/actedin.csv +0 -0
- /relationalai/{semantics/reasoners/graph → experimental}/paths/examples/movies_data/directed.csv +0 -0
- /relationalai/{semantics/reasoners/graph → experimental}/paths/examples/movies_data/follows.csv +0 -0
- /relationalai/{semantics/reasoners/graph → experimental}/paths/examples/movies_data/movies.csv +0 -0
- /relationalai/{semantics/reasoners/graph → experimental}/paths/examples/movies_data/person.csv +0 -0
- /relationalai/{semantics/reasoners/graph → experimental}/paths/examples/movies_data/produced.csv +0 -0
- /relationalai/{semantics/reasoners/graph → experimental}/paths/examples/movies_data/ratings.csv +0 -0
- /relationalai/{semantics/reasoners/graph → experimental}/paths/examples/movies_data/wrote.csv +0 -0
- /relationalai/{semantics/reasoners/graph → experimental}/paths/find_paths_via_automaton.py +0 -0
- /relationalai/{semantics/reasoners/graph → experimental}/paths/graph.py +0 -0
- /relationalai/{early_access → experimental}/paths/path_algorithms/__init__.py +0 -0
- /relationalai/{semantics/reasoners/graph → experimental}/paths/path_algorithms/find_paths.py +0 -0
- /relationalai/{semantics/reasoners/graph → experimental}/paths/path_algorithms/one_sided_ball_upto.py +0 -0
- /relationalai/{semantics/reasoners/graph → experimental}/paths/product_graph.py +0 -0
- /relationalai/{semantics/reasoners/graph → experimental}/paths/rpq/__init__.py +0 -0
- /relationalai/{semantics/reasoners/graph → experimental}/paths/rpq/automaton.py +0 -0
- /relationalai/{semantics/reasoners/graph → experimental}/paths/rpq/diagnostics.py +0 -0
- /relationalai/{semantics/reasoners/graph → experimental}/paths/rpq/filter.py +0 -0
- /relationalai/{semantics/reasoners/graph → experimental}/paths/rpq/glushkov.py +0 -0
- /relationalai/{semantics/reasoners/graph → experimental}/paths/rpq/rpq.py +0 -0
- /relationalai/{semantics/reasoners/graph → experimental}/paths/rpq/transition.py +0 -0
- /relationalai/{early_access → experimental}/paths/utilities/__init__.py +0 -0
- /relationalai/{semantics/reasoners/graph → experimental}/paths/utilities/iterators.py +0 -0
- /relationalai/{semantics/reasoners/graph → experimental}/paths/utilities/prefix_sum.py +0 -0
- /relationalai/{semantics/reasoners/graph → experimental}/paths/utilities/utilities.py +0 -0
- {relationalai-0.12.4.dist-info → relationalai-0.12.7.dist-info}/entry_points.txt +0 -0
- {relationalai-0.12.4.dist-info → relationalai-0.12.7.dist-info}/licenses/LICENSE +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
from relationalai.semantics import Model, Integer, define, select
|
|
2
|
-
from relationalai.
|
|
3
|
-
from relationalai.
|
|
2
|
+
from relationalai.experimental.paths.graph import Graph
|
|
3
|
+
from relationalai.experimental.paths.path_algorithms.one_sided_ball_upto import ball_upto
|
|
4
4
|
|
|
5
5
|
|
|
6
6
|
# First test with grid graph and multiple source and target nodes
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
from relationalai.semantics import Model, Integer, define, select
|
|
2
|
-
from relationalai.
|
|
3
|
-
from relationalai.
|
|
2
|
+
from relationalai.experimental.paths.graph import Graph
|
|
3
|
+
from relationalai.experimental.paths.path_algorithms.one_sided_ball_upto import ball_upto
|
|
4
4
|
|
|
5
5
|
|
|
6
6
|
# First test with grid graph
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
from relationalai.semantics import Model, Integer, define, select
|
|
2
|
-
from relationalai.
|
|
3
|
-
from relationalai.
|
|
2
|
+
from relationalai.experimental.paths.graph import Graph
|
|
3
|
+
from relationalai.experimental.paths.path_algorithms.single import single_shortest_path
|
|
4
4
|
|
|
5
5
|
|
|
6
6
|
model = Model("test_single_paths", dry_run=False)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
from relationalai.semantics import Model, Integer, define, select
|
|
2
|
-
from relationalai.
|
|
3
|
-
from relationalai.
|
|
2
|
+
from relationalai.experimental.paths.graph import Graph
|
|
3
|
+
from relationalai.experimental.paths.path_algorithms.single import single_walk
|
|
4
4
|
|
|
5
5
|
|
|
6
6
|
model = Model("test_single_paths", dry_run=False)
|
relationalai/{semantics/reasoners/graph → experimental}/paths/tests/tests_single_walks_undirected.py
RENAMED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
from relationalai.semantics import Model, Integer, define, select
|
|
2
|
-
from relationalai.
|
|
3
|
-
from relationalai.
|
|
2
|
+
from relationalai.experimental.paths.graph import Graph
|
|
3
|
+
from relationalai.experimental.paths.path_algorithms.single import single_walk
|
|
4
4
|
|
|
5
5
|
|
|
6
6
|
model = Model("test_single_paths", dry_run=False)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
from relationalai.semantics import Model, Integer, define, select
|
|
2
|
-
from relationalai.
|
|
3
|
-
from relationalai.
|
|
2
|
+
from relationalai.experimental.paths.graph import Graph
|
|
3
|
+
from relationalai.experimental.paths.path_algorithms.two_sided_balls_repetition import two_balls_repetition
|
|
4
4
|
|
|
5
5
|
|
|
6
6
|
# Test with diamond graph
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
from relationalai.semantics import Model, Integer, define, select
|
|
2
|
-
from relationalai.
|
|
3
|
-
from relationalai.
|
|
2
|
+
from relationalai.experimental.paths.graph import Graph
|
|
3
|
+
from relationalai.experimental.paths.path_algorithms.two_sided_balls_repetition import two_balls_repetition
|
|
4
4
|
|
|
5
5
|
|
|
6
6
|
# Test with diamond graph
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
from relationalai.semantics import Model, Integer, define, select
|
|
2
|
-
from relationalai.
|
|
3
|
-
from relationalai.
|
|
2
|
+
from relationalai.experimental.paths.graph import Graph
|
|
3
|
+
from relationalai.experimental.paths.path_algorithms.two_sided_balls_upto import two_balls_upto
|
|
4
4
|
|
|
5
5
|
|
|
6
6
|
# Test with grid graph
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
from relationalai.semantics import Model, Integer, define, select
|
|
2
|
-
from relationalai.
|
|
3
|
-
from relationalai.
|
|
2
|
+
from relationalai.experimental.paths.graph import Graph
|
|
3
|
+
from relationalai.experimental.paths.path_algorithms.two_sided_balls_upto import two_balls_upto
|
|
4
4
|
|
|
5
5
|
|
|
6
6
|
# Test with grid graph
|
relationalai/{semantics/reasoners/graph → experimental}/paths/tests/tests_usp_nsp_multiple.py
RENAMED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
from relationalai.semantics import Model, Integer, define, select
|
|
2
|
-
from relationalai.
|
|
3
|
-
from relationalai.
|
|
2
|
+
from relationalai.experimental.paths.graph import Graph
|
|
3
|
+
from relationalai.experimental.paths.path_algorithms.usp import compute_usp, compute_nsp
|
|
4
4
|
|
|
5
5
|
|
|
6
6
|
# First test for usp with grid graph
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
from relationalai.semantics import Model, Integer, define, select
|
|
2
|
-
from relationalai.
|
|
3
|
-
from relationalai.
|
|
2
|
+
from relationalai.experimental.paths.graph import Graph
|
|
3
|
+
from relationalai.experimental.paths.path_algorithms.usp import compute_usp, compute_nsp
|
|
4
4
|
|
|
5
5
|
|
|
6
6
|
# First test for usp with grid graph
|
|
@@ -2550,8 +2550,6 @@ class Fragment():
|
|
|
2550
2550
|
from .snowflake import Table
|
|
2551
2551
|
assert isinstance(table, Table), "Only Snowflake tables are supported for now"
|
|
2552
2552
|
|
|
2553
|
-
result_cols = table._col_names
|
|
2554
|
-
|
|
2555
2553
|
clone = Fragment(parent=self)
|
|
2556
2554
|
clone._is_export = True
|
|
2557
2555
|
qb_model = clone._model or Model("anon")
|
|
@@ -2559,8 +2557,7 @@ class Fragment():
|
|
|
2559
2557
|
clone._source = runtime_env.get_source_pos()
|
|
2560
2558
|
with debugging.span("query", dsl=str(clone), **with_source(clone), meta=clone._meta):
|
|
2561
2559
|
query_task = qb_model._compiler.fragment(clone)
|
|
2562
|
-
qb_model._to_executor().execute(ir_model, query_task,
|
|
2563
|
-
|
|
2560
|
+
qb_model._to_executor().execute(ir_model, query_task, export_to=table, update=update, meta=clone._meta)
|
|
2564
2561
|
|
|
2565
2562
|
#--------------------------------------------------
|
|
2566
2563
|
# Select / Where
|
|
@@ -3419,6 +3416,8 @@ class Compiler():
|
|
|
3419
3416
|
return out
|
|
3420
3417
|
|
|
3421
3418
|
elif isinstance(item, TypeRef):
|
|
3419
|
+
if isinstance(item._thing, Relationship):
|
|
3420
|
+
return self.to_relation(item._thing)
|
|
3422
3421
|
concept = to_type(item)
|
|
3423
3422
|
if not concept:
|
|
3424
3423
|
raise ValueError(f"Cannot find concept for {item}, {type(item)}")
|
|
@@ -12,7 +12,18 @@ from . import internal as b, annotations as anns
|
|
|
12
12
|
from relationalai import debugging
|
|
13
13
|
from relationalai.errors import UnsupportedColumnTypesWarning
|
|
14
14
|
from snowflake.snowpark.context import get_active_session
|
|
15
|
+
from typing import ClassVar, Optional
|
|
15
16
|
|
|
17
|
+
#--------------------------------------------------
|
|
18
|
+
# Iceberg Configuration
|
|
19
|
+
#--------------------------------------------------
|
|
20
|
+
@dataclass
|
|
21
|
+
class IcebergConfig:
|
|
22
|
+
"""Configuration for exporting to Iceberg tables."""
|
|
23
|
+
external_volume: str | None = None
|
|
24
|
+
default: ClassVar[Optional["IcebergConfig"]]
|
|
25
|
+
|
|
26
|
+
IcebergConfig.default = IcebergConfig()
|
|
16
27
|
#--------------------------------------------------
|
|
17
28
|
# Helpers
|
|
18
29
|
#--------------------------------------------------
|
|
@@ -191,7 +202,7 @@ class Table():
|
|
|
191
202
|
_schemas:dict[tuple[str, str], SchemaInfo] = {}
|
|
192
203
|
_used_sources:OrderedSet[Table] = ordered_set()
|
|
193
204
|
|
|
194
|
-
def __init__(self, fqn:str, cols:list[str]|None=None, schema:dict[str, str|b.Concept]|None=None) -> None:
|
|
205
|
+
def __init__(self, fqn:str, cols:list[str]|None=None, schema:dict[str, str|b.Concept]|None=None, config: IcebergConfig|None=None) -> None:
|
|
195
206
|
self._fqn = fqn
|
|
196
207
|
parser = IdentityParser(fqn, require_all_parts=True)
|
|
197
208
|
self._database, self._schema, self._table, self._fqn = parser.to_list()
|
|
@@ -201,6 +212,8 @@ class Table():
|
|
|
201
212
|
self._ref = self._concept.ref("row_id")
|
|
202
213
|
self._cols = {}
|
|
203
214
|
self._col_names = cols
|
|
215
|
+
self._iceberg_config = config
|
|
216
|
+
self._is_iceberg = config is not None
|
|
204
217
|
info = self._schemas.get((self._database, self._schema))
|
|
205
218
|
if not info:
|
|
206
219
|
info = self._schemas[(self._database, self._schema)] = SchemaInfo(self._database, self._schema)
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
from typing import Tuple
|
|
2
2
|
from relationalai.semantics.lqp import ir as lqp
|
|
3
|
-
from relationalai.semantics.metamodel.ir import sanitize
|
|
4
3
|
|
|
5
4
|
def mk_and(args: list[lqp.Formula]) -> lqp.Formula:
|
|
6
5
|
# Flatten nested conjunctions
|
|
@@ -49,10 +48,6 @@ def mk_specialized_value(value) -> lqp.SpecializedValue:
|
|
|
49
48
|
def mk_value(value) -> lqp.Value:
|
|
50
49
|
return lqp.Value(value=value, meta=None)
|
|
51
50
|
|
|
52
|
-
def mk_var(name: str) -> lqp.Var:
|
|
53
|
-
_name = '_' if name == '_' else sanitize(name)
|
|
54
|
-
return lqp.Var(name=_name, meta=None)
|
|
55
|
-
|
|
56
51
|
def mk_type(typename: lqp.TypeName, parameters: list[lqp.Value]=[]) -> lqp.Type:
|
|
57
52
|
return lqp.Type(type_name=typename, parameters=parameters, meta=None)
|
|
58
53
|
|
|
@@ -4,7 +4,7 @@ import atexit
|
|
|
4
4
|
import re
|
|
5
5
|
|
|
6
6
|
from pandas import DataFrame
|
|
7
|
-
from typing import Any, Optional, Literal
|
|
7
|
+
from typing import Any, Optional, Literal, TYPE_CHECKING
|
|
8
8
|
from snowflake.snowpark import Session
|
|
9
9
|
import relationalai as rai
|
|
10
10
|
|
|
@@ -20,10 +20,14 @@ from relationalai.semantics.lqp.ir import convert_transaction, validate_lqp
|
|
|
20
20
|
from relationalai.clients.config import Config
|
|
21
21
|
from relationalai.clients.snowflake import APP_NAME
|
|
22
22
|
from relationalai.clients.types import TransactionAsyncResponse
|
|
23
|
-
from relationalai.clients.util import IdentityParser
|
|
23
|
+
from relationalai.clients.util import IdentityParser, escape_for_f_string
|
|
24
24
|
from relationalai.tools.constants import USE_DIRECT_ACCESS, QUERY_ATTRIBUTES_HEADER
|
|
25
25
|
from relationalai.tools.query_utils import prepare_metadata_for_headers
|
|
26
26
|
|
|
27
|
+
if TYPE_CHECKING:
|
|
28
|
+
from relationalai.semantics.snowflake import Table
|
|
29
|
+
|
|
30
|
+
|
|
27
31
|
class LQPExecutor(e.Executor):
|
|
28
32
|
"""Executes LQP using the RAI client."""
|
|
29
33
|
|
|
@@ -172,12 +176,12 @@ class LQPExecutor(e.Executor):
|
|
|
172
176
|
elif len(all_errors) > 1:
|
|
173
177
|
raise errors.RAIExceptionSet(all_errors)
|
|
174
178
|
|
|
175
|
-
def _export(self, txn_id: str, export_info: tuple,
|
|
179
|
+
def _export(self, txn_id: str, export_info: tuple, dest: Table, actual_cols: list[str], declared_cols: list[str], update: bool):
|
|
176
180
|
# At this point of the export, we assume that a CSV file has already been written
|
|
177
181
|
# to the Snowflake Native App stage area. Thus, the purpose of this method is to
|
|
178
182
|
# copy the data from the CSV file to the destination table.
|
|
179
183
|
_exec = self.resources._exec
|
|
180
|
-
dest_database, dest_schema, dest_table, _ = IdentityParser(
|
|
184
|
+
dest_database, dest_schema, dest_table, _ = IdentityParser(dest._fqn, require_all_parts=True).to_list()
|
|
181
185
|
filename = export_info[0]
|
|
182
186
|
result_table_name = filename + "_table"
|
|
183
187
|
|
|
@@ -203,8 +207,28 @@ class LQPExecutor(e.Executor):
|
|
|
203
207
|
# destination table. This step also cleans up the result table.
|
|
204
208
|
out_sample = _exec(f"select * from {APP_NAME}.results.{result_table_name} limit 1;")
|
|
205
209
|
names = self._build_projection(declared_cols, actual_cols, column_fields, out_sample)
|
|
210
|
+
dest_fqn = dest._fqn
|
|
206
211
|
try:
|
|
207
212
|
if not update:
|
|
213
|
+
createTableLogic = f"""
|
|
214
|
+
CREATE TABLE {dest_fqn} AS
|
|
215
|
+
SELECT {names}
|
|
216
|
+
FROM {APP_NAME}.results.{result_table_name};
|
|
217
|
+
"""
|
|
218
|
+
if dest._is_iceberg:
|
|
219
|
+
assert dest._iceberg_config is not None
|
|
220
|
+
external_volume_clause = ""
|
|
221
|
+
if dest._iceberg_config.external_volume:
|
|
222
|
+
external_volume_clause = f"EXTERNAL_VOLUME = '{dest._iceberg_config.external_volume}'"
|
|
223
|
+
createTableLogic = f"""
|
|
224
|
+
CREATE ICEBERG TABLE {dest_fqn}
|
|
225
|
+
CATALOG = "SNOWFLAKE"
|
|
226
|
+
{external_volume_clause}
|
|
227
|
+
AS
|
|
228
|
+
SELECT {names}
|
|
229
|
+
FROM {APP_NAME}.results.{result_table_name};
|
|
230
|
+
"""
|
|
231
|
+
|
|
208
232
|
_exec(f"""
|
|
209
233
|
BEGIN
|
|
210
234
|
-- Check if table exists
|
|
@@ -227,9 +251,7 @@ class LQPExecutor(e.Executor):
|
|
|
227
251
|
ELSE
|
|
228
252
|
-- Create table based on the SELECT
|
|
229
253
|
EXECUTE IMMEDIATE '
|
|
230
|
-
|
|
231
|
-
SELECT {names}
|
|
232
|
-
FROM {APP_NAME}.results.{result_table_name};
|
|
254
|
+
{escape_for_f_string(createTableLogic)}
|
|
233
255
|
';
|
|
234
256
|
END IF;
|
|
235
257
|
END;
|
|
@@ -376,7 +398,7 @@ class LQPExecutor(e.Executor):
|
|
|
376
398
|
return final_model, export_info, txn_proto
|
|
377
399
|
|
|
378
400
|
# TODO (azreika): This should probably be split up into exporting and other processing. There are quite a lot of arguments here...
|
|
379
|
-
def _process_results(self, task: ir.Task, final_model: ir.Model, raw_results: TransactionAsyncResponse,
|
|
401
|
+
def _process_results(self, task: ir.Task, final_model: ir.Model, raw_results: TransactionAsyncResponse, export_info: Optional[tuple], export_to: Optional[Table], update: bool) -> DataFrame:
|
|
380
402
|
cols, extra_cols = self._compute_cols(task, final_model)
|
|
381
403
|
|
|
382
404
|
df, errs = result_helpers.format_results(raw_results, cols)
|
|
@@ -391,6 +413,8 @@ class LQPExecutor(e.Executor):
|
|
|
391
413
|
assert cols, "No columns found in the output"
|
|
392
414
|
assert isinstance(raw_results, TransactionAsyncResponse) and raw_results.transaction, "Invalid transaction result"
|
|
393
415
|
|
|
416
|
+
result_cols = export_to._col_names
|
|
417
|
+
|
|
394
418
|
if result_cols is not None:
|
|
395
419
|
assert all(col in result_cols or col in extra_cols for col in cols)
|
|
396
420
|
else:
|
|
@@ -403,7 +427,7 @@ class LQPExecutor(e.Executor):
|
|
|
403
427
|
return self._postprocess_df(self.config, df, extra_cols)
|
|
404
428
|
|
|
405
429
|
def execute(self, model: ir.Model, task: ir.Task, format: Literal["pandas", "snowpark"] = "pandas",
|
|
406
|
-
|
|
430
|
+
export_to: Optional[Table] = None,
|
|
407
431
|
update: bool = False, meta: dict[str, Any] | None = None) -> DataFrame:
|
|
408
432
|
self.prepare_data()
|
|
409
433
|
previous_model = self._last_model
|
|
@@ -433,7 +457,7 @@ class LQPExecutor(e.Executor):
|
|
|
433
457
|
assert isinstance(raw_results, TransactionAsyncResponse)
|
|
434
458
|
|
|
435
459
|
try:
|
|
436
|
-
return self._process_results(task, final_model, raw_results,
|
|
460
|
+
return self._process_results(task, final_model, raw_results, export_info, export_to, update)
|
|
437
461
|
except Exception as e:
|
|
438
462
|
# If processing the results failed, revert to the previous model.
|
|
439
463
|
self._last_model = previous_model
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
from datetime import datetime, timezone
|
|
2
2
|
|
|
3
3
|
from relationalai.semantics.lqp import ir as lqp
|
|
4
|
-
from relationalai.semantics.lqp.constructors import mk_abstraction, mk_value,
|
|
4
|
+
from relationalai.semantics.lqp.constructors import mk_abstraction, mk_value, mk_type, mk_primitive
|
|
5
5
|
from relationalai.semantics.lqp.utils import lqp_hash
|
|
6
6
|
|
|
7
7
|
def mk_intrinsic_datetime_now() -> lqp.Def:
|
|
8
8
|
"""Constructs a definition of the current datetime."""
|
|
9
9
|
id = lqp_hash("__pyrel_lqp_intrinsic_datetime_now")
|
|
10
|
-
out =
|
|
10
|
+
out = lqp.Var(name="out", meta=None)
|
|
11
11
|
out_type = mk_type(lqp.TypeName.DATETIME)
|
|
12
12
|
now = mk_value(lqp.DateTimeValue(value=datetime.now(timezone.utc), meta=None))
|
|
13
13
|
datetime_now = mk_abstraction(
|
|
@@ -7,7 +7,7 @@ from relationalai.semantics.lqp.pragmas import pragma_to_lqp_name
|
|
|
7
7
|
from relationalai.semantics.lqp.types import meta_type_to_lqp
|
|
8
8
|
from relationalai.semantics.lqp.constructors import (
|
|
9
9
|
mk_abstraction, mk_and, mk_exists, mk_or, mk_pragma, mk_primitive,
|
|
10
|
-
mk_specialized_value, mk_type, mk_value,
|
|
10
|
+
mk_specialized_value, mk_type, mk_value, mk_attribute
|
|
11
11
|
)
|
|
12
12
|
from relationalai.semantics.lqp.utils import TranslationCtx, gen_unique_var
|
|
13
13
|
from relationalai.semantics.lqp.validators import assert_valid_input
|
|
@@ -253,7 +253,7 @@ def _translate_rank(ctx: TranslationCtx, rank: ir.Rank, body: lqp.Formula) -> lq
|
|
|
253
253
|
# to convert it to Int128.
|
|
254
254
|
result_var, _ = _translate_term(ctx, rank.result)
|
|
255
255
|
# The primitive will return an Int64 result, so we need a var to hold the intermediary.
|
|
256
|
-
result_64_var = gen_unique_var(ctx, "
|
|
256
|
+
result_64_var = gen_unique_var(ctx, "v_rank")
|
|
257
257
|
result_64_type = mk_type(lqp.TypeName.INT)
|
|
258
258
|
|
|
259
259
|
cast = lqp.Cast(input=result_64_var, result=result_var, meta=None)
|
|
@@ -273,7 +273,7 @@ def _translate_descending_rank(ctx: TranslationCtx, limit: int, result: lqp.Var,
|
|
|
273
273
|
result_type = mk_type(lqp.TypeName.INT)
|
|
274
274
|
|
|
275
275
|
# Rename abstracted args in the body to new variable names
|
|
276
|
-
var_map = {var.name: gen_unique_var(ctx, var.name) for (var, _) in abstr_args}
|
|
276
|
+
var_map = {var.name: gen_unique_var(ctx, 't_' + var.name) for (var, _) in abstr_args}
|
|
277
277
|
body = utils.rename_vars_formula(body, var_map)
|
|
278
278
|
new_abstr_args = [(var_map[var.name], typ) for (var, typ) in abstr_args]
|
|
279
279
|
|
|
@@ -306,7 +306,7 @@ def _translate_descending_rank(ctx: TranslationCtx, limit: int, result: lqp.Var,
|
|
|
306
306
|
aggr_abstr_args = new_abstr_args + [(count_var, count_type)]
|
|
307
307
|
count_aggr = lqp.Reduce(
|
|
308
308
|
op=lqp_operator(
|
|
309
|
-
ctx
|
|
309
|
+
ctx,
|
|
310
310
|
"count",
|
|
311
311
|
"count",
|
|
312
312
|
mk_type(lqp.TypeName.INT)
|
|
@@ -340,7 +340,7 @@ def _translate_ascending_rank(ctx: TranslationCtx, limit: int, result_var: lqp.V
|
|
|
340
340
|
terms = [result_var] + [v[0] for v in abstr_args]
|
|
341
341
|
|
|
342
342
|
# Rename abstracted args in the body to new variable names
|
|
343
|
-
var_map = {var.name: gen_unique_var(ctx, var.name) for (var, _) in abstr_args}
|
|
343
|
+
var_map = {var.name: gen_unique_var(ctx, 't_' + var.name) for (var, _) in abstr_args}
|
|
344
344
|
body = utils.rename_vars_formula(body, var_map)
|
|
345
345
|
new_abstr_args = [(var_map[var.name], typ) for (var, typ) in abstr_args]
|
|
346
346
|
sort_abstr = mk_abstraction(new_abstr_args, body)
|
|
@@ -431,7 +431,7 @@ def _translate_aggregate(ctx: TranslationCtx, aggr: ir.Aggregate, body: lqp.Form
|
|
|
431
431
|
(sum_var, sum_type) = abstr_args[-2]
|
|
432
432
|
|
|
433
433
|
result = lqp.Reduce(
|
|
434
|
-
op=lqp_avg_op(ctx
|
|
434
|
+
op=lqp_avg_op(ctx, aggr.aggregation.name, sum_var.name, sum_type),
|
|
435
435
|
body=mk_abstraction(abstr_args, body),
|
|
436
436
|
terms=[sum_result, count_result],
|
|
437
437
|
meta=None,
|
|
@@ -461,9 +461,10 @@ def _translate_aggregate(ctx: TranslationCtx, aggr: ir.Aggregate, body: lqp.Form
|
|
|
461
461
|
# `input_args`` hold the types of the input arguments, but they may have been modified
|
|
462
462
|
# if we're dealing with a count, so we use `abstr_args` to find the type.
|
|
463
463
|
(aggr_arg, aggr_arg_type) = abstr_args[-1]
|
|
464
|
+
|
|
464
465
|
# Group-bys do not need to be handled at all, since they are introduced outside already
|
|
465
466
|
reduce = lqp.Reduce(
|
|
466
|
-
op=lqp_operator(ctx
|
|
467
|
+
op=lqp_operator(ctx, aggr.aggregation.name, aggr_arg.name, aggr_arg_type),
|
|
467
468
|
body=mk_abstraction(abstr_args, body),
|
|
468
469
|
terms=output_vars,
|
|
469
470
|
meta=None
|
|
@@ -522,9 +523,8 @@ def _translate_term(ctx: TranslationCtx, term: ir.Value) -> Tuple[lqp.Term, lqp.
|
|
|
522
523
|
# TODO: ScalarType is not like other terms, should be handled separately.
|
|
523
524
|
return to_lqp_value(term.name, types.String), meta_type_to_lqp(types.String)
|
|
524
525
|
elif isinstance(term, ir.Var):
|
|
525
|
-
name = ctx.var_names.get_name_by_id(term.id, term.name)
|
|
526
526
|
t = meta_type_to_lqp(term.type)
|
|
527
|
-
return
|
|
527
|
+
return _translate_var(ctx, term), t
|
|
528
528
|
else:
|
|
529
529
|
assert isinstance(term, ir.Literal), f"Cannot translate value {term!r} of type {type(term)} to LQP Term; neither Var nor Literal."
|
|
530
530
|
v = to_lqp_value(term.value, term.type)
|
|
@@ -542,6 +542,12 @@ def _translate_to_atom(ctx: TranslationCtx, task: ir.Lookup) -> lqp.Formula:
|
|
|
542
542
|
|
|
543
543
|
if task.relation == builtins.join:
|
|
544
544
|
return _translate_join(ctx, task)
|
|
545
|
+
elif task.relation == builtins.infomap:
|
|
546
|
+
return _translate_infomap(ctx, task)
|
|
547
|
+
elif task.relation == builtins.louvain:
|
|
548
|
+
return _translate_louvain(ctx, task)
|
|
549
|
+
elif task.relation == builtins.label_propagation:
|
|
550
|
+
return _translate_label_propagation(ctx, task)
|
|
545
551
|
|
|
546
552
|
terms = []
|
|
547
553
|
term_types = []
|
|
@@ -667,6 +673,92 @@ def _extract_pyrel_error_ids(ctx: TranslationCtx, model: ir.Model) -> list[Tuple
|
|
|
667
673
|
|
|
668
674
|
return pyrel_error_attrs
|
|
669
675
|
|
|
676
|
+
# Translate a relation reference into an abstraction over its fields.
|
|
677
|
+
def _translate_relation_ref(ctx: TranslationCtx, relation: ir.Relation) -> lqp.Abstraction:
|
|
678
|
+
projection = []
|
|
679
|
+
for field in relation.fields:
|
|
680
|
+
var = gen_unique_var(ctx, field.name)
|
|
681
|
+
typ = meta_type_to_lqp(field.type)
|
|
682
|
+
projection.append((var, typ))
|
|
683
|
+
|
|
684
|
+
rid = get_relation_id(ctx, relation, projection)
|
|
685
|
+
atom = lqp.Atom(name=rid, terms=[var for (var, _) in projection], meta=None)
|
|
686
|
+
return mk_abstraction(projection, atom)
|
|
687
|
+
|
|
688
|
+
# Common translation logic for graph algorithms.
|
|
689
|
+
# task.args[0] : normalized weight list (int64, int64, float)
|
|
690
|
+
# task.args[1] : normalized node count (relation or constant int64)
|
|
691
|
+
# task.args[2] : normalized edge count (relation or constant int64)
|
|
692
|
+
# task.args[3:-3] : algorithm parameters (var or constant)
|
|
693
|
+
# task.args[-3] : diagnostic info
|
|
694
|
+
# task.args[-2] : node index
|
|
695
|
+
# task.args[-1] : community ident
|
|
696
|
+
def _translate_graph_common(name: str, ctx: TranslationCtx, task: ir.Lookup):
|
|
697
|
+
abstractions = []
|
|
698
|
+
|
|
699
|
+
assert isinstance(task.args[0], ir.Relation), \
|
|
700
|
+
f"Expected relation as first arg to {name}, got {task.args[0]}:{type(task.args[0])}"
|
|
701
|
+
abstractions.append(_translate_relation_ref(ctx, task.args[0]))
|
|
702
|
+
|
|
703
|
+
# Allow constant args for node and edge count
|
|
704
|
+
for arg in task.args[1:3]:
|
|
705
|
+
if isinstance(arg, ir.Relation):
|
|
706
|
+
abst = _translate_relation_ref(ctx, arg)
|
|
707
|
+
typ = abst.vars[0][1]
|
|
708
|
+
assert typ.type_name == lqp.TypeName.INT, \
|
|
709
|
+
f"Expected Int64 types for node and edge counts, got type {typ.type_name}"
|
|
710
|
+
abstractions.append(abst)
|
|
711
|
+
else:
|
|
712
|
+
var, typ, eq = binding_to_lqp_var(ctx, arg)
|
|
713
|
+
assert eq is not None, \
|
|
714
|
+
f"Expected equality formula for {name} arg {arg}:{type(arg)}"
|
|
715
|
+
abstractions.append(mk_abstraction([(var, typ)], mk_and([eq])))
|
|
716
|
+
|
|
717
|
+
for arg in task.args[3:-3]:
|
|
718
|
+
var, typ, eq = binding_to_lqp_var(ctx, arg)
|
|
719
|
+
if eq:
|
|
720
|
+
abstractions.append(mk_abstraction([(var, typ)], mk_and([eq])))
|
|
721
|
+
else:
|
|
722
|
+
print(f"Primitive graph algorithm arg without eq:\n var:{var}, typ:{typ}\n arg:{arg}:{type(arg)}")
|
|
723
|
+
abstractions.append(mk_abstraction([(var, typ)], mk_and([])))
|
|
724
|
+
|
|
725
|
+
terms = []
|
|
726
|
+
for arg in task.args[-3:]:
|
|
727
|
+
term, _ = _translate_relterm(ctx, arg)
|
|
728
|
+
terms.append(term)
|
|
729
|
+
|
|
730
|
+
return (abstractions, terms)
|
|
731
|
+
|
|
732
|
+
def _translate_infomap(ctx: TranslationCtx, task: ir.Lookup) -> lqp.Formula:
|
|
733
|
+
abstractions, terms = _translate_graph_common("infomap", ctx, task)
|
|
734
|
+
|
|
735
|
+
return lqp.FFI(
|
|
736
|
+
meta=None,
|
|
737
|
+
name="rel_primitive_infomap",
|
|
738
|
+
args=abstractions,
|
|
739
|
+
terms=terms,
|
|
740
|
+
)
|
|
741
|
+
|
|
742
|
+
def _translate_louvain(ctx: TranslationCtx, task: ir.Lookup) -> lqp.Formula:
|
|
743
|
+
abstractions, terms = _translate_graph_common("louvain", ctx, task)
|
|
744
|
+
|
|
745
|
+
return lqp.FFI(
|
|
746
|
+
meta=None,
|
|
747
|
+
name="rel_primitive_louvain",
|
|
748
|
+
args=abstractions,
|
|
749
|
+
terms=terms,
|
|
750
|
+
)
|
|
751
|
+
|
|
752
|
+
def _translate_label_propagation(ctx: TranslationCtx, task: ir.Lookup) -> lqp.Formula:
|
|
753
|
+
abstractions, terms = _translate_graph_common("label_propagation", ctx, task)
|
|
754
|
+
|
|
755
|
+
return lqp.FFI(
|
|
756
|
+
meta=None,
|
|
757
|
+
name="rel_primitive_async_label_propagation",
|
|
758
|
+
args=abstractions,
|
|
759
|
+
terms=terms,
|
|
760
|
+
)
|
|
761
|
+
|
|
670
762
|
# Hard-coded implementation of Rel's string_join
|
|
671
763
|
def _translate_join(ctx: TranslationCtx, task: ir.Lookup) -> lqp.Formula:
|
|
672
764
|
assert len(task.args) == 3
|
|
@@ -708,3 +800,7 @@ def _translate_join(ctx: TranslationCtx, task: ir.Lookup) -> lqp.Formula:
|
|
|
708
800
|
output_term = _translate_term(ctx, target)[0]
|
|
709
801
|
|
|
710
802
|
return lqp.Reduce(meta=None, op=op, body=body, terms=[output_term])
|
|
803
|
+
|
|
804
|
+
def _translate_var(ctx: TranslationCtx, term: ir.Var) -> lqp.Var:
|
|
805
|
+
name = ctx.var_names.get_name_by_id(term.id, term.name)
|
|
806
|
+
return lqp.Var(name=name, meta=None)
|
|
@@ -7,16 +7,18 @@ from relationalai.semantics.metamodel.util import FrozenOrderedSet
|
|
|
7
7
|
from relationalai.semantics.metamodel.rewrite import Flatten
|
|
8
8
|
|
|
9
9
|
from ..metamodel.rewrite import DischargeConstraints, DNFUnionSplitter, ExtractNestedLogicals
|
|
10
|
-
from .rewrite import CDC, ExtractCommon, ExtractKeys,
|
|
10
|
+
from .rewrite import CDC, ExtractCommon, ExtractKeys, FunctionAnnotations, QuantifyVars, Splinter
|
|
11
11
|
|
|
12
12
|
from relationalai.semantics.lqp.utils import output_names
|
|
13
13
|
|
|
14
14
|
from typing import cast, List, Sequence, Tuple, Union, Optional, Iterable
|
|
15
15
|
from collections import defaultdict
|
|
16
|
+
import pandas as pd
|
|
17
|
+
import hashlib
|
|
16
18
|
|
|
17
19
|
def lqp_passes() -> list[Pass]:
|
|
18
20
|
return [
|
|
19
|
-
|
|
21
|
+
FunctionAnnotations(),
|
|
20
22
|
DischargeConstraints(),
|
|
21
23
|
Checker(),
|
|
22
24
|
CDC(), # specialize to physical relations before extracting nested and typing
|
|
@@ -337,7 +339,7 @@ class UnifyDefinitions(Pass):
|
|
|
337
339
|
)
|
|
338
340
|
|
|
339
341
|
# Creates intermediary relations for all Data nodes and replaces said Data nodes
|
|
340
|
-
# with a Lookup into these created relations.
|
|
342
|
+
# with a Lookup into these created relations. Reuse duplicate created relations.
|
|
341
343
|
class EliminateData(Pass):
|
|
342
344
|
def rewrite(self, model: ir.Model, options:dict={}) -> ir.Model:
|
|
343
345
|
r = self.DataRewriter()
|
|
@@ -350,17 +352,25 @@ class EliminateData(Pass):
|
|
|
350
352
|
# Counter for naming new relations.
|
|
351
353
|
# It must be that new_count == len new_updates == len new_relations.
|
|
352
354
|
new_count: int
|
|
355
|
+
# Cache for Data nodes to avoid creating duplicate intermediary relations
|
|
356
|
+
data_cache: dict[str, ir.Relation]
|
|
353
357
|
|
|
354
358
|
def __init__(self):
|
|
355
359
|
self.new_relations = []
|
|
356
360
|
self.new_updates = []
|
|
357
361
|
self.new_count = 0
|
|
362
|
+
self.data_cache = {}
|
|
358
363
|
super().__init__()
|
|
359
364
|
|
|
360
|
-
# Create a
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
365
|
+
# Create a cache key for a Data node based on its structure and content
|
|
366
|
+
def _data_cache_key(self, node: ir.Data) -> str:
|
|
367
|
+
values = pd.util.hash_pandas_object(node.data).values
|
|
368
|
+
return hashlib.sha256(bytes(values)).hexdigest()
|
|
369
|
+
|
|
370
|
+
def _intermediary_relation(self, node: ir.Data) -> ir.Relation:
|
|
371
|
+
cache_key = self._data_cache_key(node)
|
|
372
|
+
if cache_key in self.data_cache:
|
|
373
|
+
return self.data_cache[cache_key]
|
|
364
374
|
self.new_count += 1
|
|
365
375
|
intermediary_name = f"formerly_Data_{self.new_count}"
|
|
366
376
|
|
|
@@ -379,7 +389,6 @@ class EliminateData(Pass):
|
|
|
379
389
|
f.lookup(rel_builtins.eq, [f.literal(val), var])
|
|
380
390
|
for (val, var) in zip(row, node.vars)
|
|
381
391
|
],
|
|
382
|
-
hoisted = node.vars,
|
|
383
392
|
)
|
|
384
393
|
for row in node
|
|
385
394
|
],
|
|
@@ -390,6 +399,16 @@ class EliminateData(Pass):
|
|
|
390
399
|
])
|
|
391
400
|
self.new_updates.append(intermediary_update)
|
|
392
401
|
|
|
402
|
+
# Cache the result for reuse
|
|
403
|
+
self.data_cache[cache_key] = intermediary_relation
|
|
404
|
+
|
|
405
|
+
return intermediary_relation
|
|
406
|
+
|
|
407
|
+
# Create a new intermediary relation representing the Data (and pop it in
|
|
408
|
+
# new_updates/new_relations) and replace this Data with a Lookup of said
|
|
409
|
+
# intermediary.
|
|
410
|
+
def handle_data(self, node: ir.Data, parent: ir.Node) -> ir.Lookup:
|
|
411
|
+
intermediary_relation = self._intermediary_relation(node)
|
|
393
412
|
replacement_lookup = f.lookup(intermediary_relation, node.vars)
|
|
394
413
|
|
|
395
414
|
return replacement_lookup
|