relationalai 0.13.0.dev0__py3-none-any.whl → 0.13.1__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.
- frontend/debugger/dist/.gitignore +2 -0
- frontend/debugger/dist/assets/favicon-Dy0ZgA6N.png +0 -0
- frontend/debugger/dist/assets/index-Cssla-O7.js +208 -0
- frontend/debugger/dist/assets/index-DlHsYx1V.css +9 -0
- frontend/debugger/dist/index.html +17 -0
- relationalai/__init__.py +256 -1
- relationalai/clients/__init__.py +18 -0
- relationalai/clients/client.py +912 -0
- relationalai/clients/config.py +673 -0
- relationalai/clients/direct_access_client.py +118 -0
- relationalai/clients/exec_txn_poller.py +91 -0
- relationalai/clients/hash_util.py +31 -0
- relationalai/clients/local.py +571 -0
- relationalai/clients/profile_polling.py +73 -0
- relationalai/clients/resources/__init__.py +8 -0
- relationalai/clients/resources/azure/azure.py +477 -0
- relationalai/clients/resources/snowflake/__init__.py +20 -0
- relationalai/clients/resources/snowflake/cli_resources.py +87 -0
- relationalai/clients/resources/snowflake/direct_access_resources.py +694 -0
- relationalai/clients/resources/snowflake/engine_state_handlers.py +309 -0
- relationalai/clients/resources/snowflake/error_handlers.py +199 -0
- relationalai/clients/resources/snowflake/export_procedure.py.jinja +249 -0
- relationalai/clients/resources/snowflake/resources_factory.py +99 -0
- relationalai/clients/resources/snowflake/snowflake.py +3190 -0
- relationalai/clients/resources/snowflake/use_index_poller.py +1019 -0
- relationalai/clients/resources/snowflake/use_index_resources.py +188 -0
- relationalai/clients/resources/snowflake/util.py +387 -0
- relationalai/clients/result_helpers.py +420 -0
- relationalai/clients/types.py +113 -0
- relationalai/clients/util.py +356 -0
- relationalai/debugging.py +389 -0
- relationalai/dsl.py +1749 -0
- relationalai/early_access/builder/__init__.py +30 -0
- relationalai/early_access/builder/builder/__init__.py +35 -0
- relationalai/early_access/builder/snowflake/__init__.py +12 -0
- relationalai/early_access/builder/std/__init__.py +25 -0
- relationalai/early_access/builder/std/decimals/__init__.py +12 -0
- relationalai/early_access/builder/std/integers/__init__.py +12 -0
- relationalai/early_access/builder/std/math/__init__.py +12 -0
- relationalai/early_access/builder/std/strings/__init__.py +14 -0
- relationalai/early_access/devtools/__init__.py +12 -0
- relationalai/early_access/devtools/benchmark_lqp/__init__.py +12 -0
- relationalai/early_access/devtools/extract_lqp/__init__.py +12 -0
- relationalai/early_access/dsl/adapters/orm/adapter_qb.py +427 -0
- relationalai/early_access/dsl/adapters/orm/parser.py +636 -0
- relationalai/early_access/dsl/adapters/owl/adapter.py +176 -0
- relationalai/early_access/dsl/adapters/owl/parser.py +160 -0
- relationalai/early_access/dsl/bindings/common.py +402 -0
- relationalai/early_access/dsl/bindings/csv.py +170 -0
- relationalai/early_access/dsl/bindings/legacy/binding_models.py +143 -0
- relationalai/early_access/dsl/bindings/snowflake.py +64 -0
- relationalai/early_access/dsl/codegen/binder.py +411 -0
- relationalai/early_access/dsl/codegen/common.py +79 -0
- relationalai/early_access/dsl/codegen/helpers.py +23 -0
- relationalai/early_access/dsl/codegen/relations.py +700 -0
- relationalai/early_access/dsl/codegen/weaver.py +417 -0
- relationalai/early_access/dsl/core/builders/__init__.py +47 -0
- relationalai/early_access/dsl/core/builders/logic.py +19 -0
- relationalai/early_access/dsl/core/builders/scalar_constraint.py +11 -0
- relationalai/early_access/dsl/core/constraints/predicate/atomic.py +455 -0
- relationalai/early_access/dsl/core/constraints/predicate/universal.py +73 -0
- relationalai/early_access/dsl/core/constraints/scalar.py +310 -0
- relationalai/early_access/dsl/core/context.py +13 -0
- relationalai/early_access/dsl/core/cset.py +132 -0
- relationalai/early_access/dsl/core/exprs/__init__.py +116 -0
- relationalai/early_access/dsl/core/exprs/relational.py +18 -0
- relationalai/early_access/dsl/core/exprs/scalar.py +412 -0
- relationalai/early_access/dsl/core/instances.py +44 -0
- relationalai/early_access/dsl/core/logic/__init__.py +193 -0
- relationalai/early_access/dsl/core/logic/aggregation.py +98 -0
- relationalai/early_access/dsl/core/logic/exists.py +223 -0
- relationalai/early_access/dsl/core/logic/helper.py +163 -0
- relationalai/early_access/dsl/core/namespaces.py +32 -0
- relationalai/early_access/dsl/core/relations.py +276 -0
- relationalai/early_access/dsl/core/rules.py +112 -0
- relationalai/early_access/dsl/core/std/__init__.py +45 -0
- relationalai/early_access/dsl/core/temporal/recall.py +6 -0
- relationalai/early_access/dsl/core/types/__init__.py +270 -0
- relationalai/early_access/dsl/core/types/concepts.py +128 -0
- relationalai/early_access/dsl/core/types/constrained/__init__.py +267 -0
- relationalai/early_access/dsl/core/types/constrained/nominal.py +143 -0
- relationalai/early_access/dsl/core/types/constrained/subtype.py +124 -0
- relationalai/early_access/dsl/core/types/standard.py +92 -0
- relationalai/early_access/dsl/core/types/unconstrained.py +50 -0
- relationalai/early_access/dsl/core/types/variables.py +203 -0
- relationalai/early_access/dsl/ir/compiler.py +318 -0
- relationalai/early_access/dsl/ir/executor.py +260 -0
- relationalai/early_access/dsl/ontologies/constraints.py +88 -0
- relationalai/early_access/dsl/ontologies/export.py +30 -0
- relationalai/early_access/dsl/ontologies/models.py +453 -0
- relationalai/early_access/dsl/ontologies/python_printer.py +303 -0
- relationalai/early_access/dsl/ontologies/readings.py +60 -0
- relationalai/early_access/dsl/ontologies/relationships.py +322 -0
- relationalai/early_access/dsl/ontologies/roles.py +87 -0
- relationalai/early_access/dsl/ontologies/subtyping.py +55 -0
- relationalai/early_access/dsl/orm/constraints.py +438 -0
- relationalai/early_access/dsl/orm/measures/dimensions.py +200 -0
- relationalai/early_access/dsl/orm/measures/initializer.py +16 -0
- relationalai/early_access/dsl/orm/measures/measure_rules.py +275 -0
- relationalai/early_access/dsl/orm/measures/measures.py +299 -0
- relationalai/early_access/dsl/orm/measures/role_exprs.py +268 -0
- relationalai/early_access/dsl/orm/models.py +256 -0
- relationalai/early_access/dsl/orm/object_oriented_printer.py +344 -0
- relationalai/early_access/dsl/orm/printer.py +469 -0
- relationalai/early_access/dsl/orm/reasoners.py +480 -0
- relationalai/early_access/dsl/orm/relations.py +19 -0
- relationalai/early_access/dsl/orm/relationships.py +251 -0
- relationalai/early_access/dsl/orm/types.py +42 -0
- relationalai/early_access/dsl/orm/utils.py +79 -0
- relationalai/early_access/dsl/orm/verb.py +204 -0
- relationalai/early_access/dsl/physical_metadata/tables.py +133 -0
- relationalai/early_access/dsl/relations.py +170 -0
- relationalai/early_access/dsl/rulesets.py +69 -0
- relationalai/early_access/dsl/schemas/__init__.py +450 -0
- relationalai/early_access/dsl/schemas/builder.py +48 -0
- relationalai/early_access/dsl/schemas/comp_names.py +51 -0
- relationalai/early_access/dsl/schemas/components.py +203 -0
- relationalai/early_access/dsl/schemas/contexts.py +156 -0
- relationalai/early_access/dsl/schemas/exprs.py +89 -0
- relationalai/early_access/dsl/schemas/fragments.py +464 -0
- relationalai/early_access/dsl/serialization.py +79 -0
- relationalai/early_access/dsl/serialize/exporter.py +163 -0
- relationalai/early_access/dsl/snow/api.py +105 -0
- relationalai/early_access/dsl/snow/common.py +76 -0
- relationalai/early_access/dsl/state_mgmt/__init__.py +129 -0
- relationalai/early_access/dsl/state_mgmt/state_charts.py +125 -0
- relationalai/early_access/dsl/state_mgmt/transitions.py +130 -0
- relationalai/early_access/dsl/types/__init__.py +40 -0
- relationalai/early_access/dsl/types/concepts.py +12 -0
- relationalai/early_access/dsl/types/entities.py +135 -0
- relationalai/early_access/dsl/types/values.py +17 -0
- relationalai/early_access/dsl/utils.py +102 -0
- relationalai/early_access/graphs/__init__.py +13 -0
- relationalai/early_access/lqp/__init__.py +12 -0
- relationalai/early_access/lqp/compiler/__init__.py +12 -0
- relationalai/early_access/lqp/constructors/__init__.py +18 -0
- relationalai/early_access/lqp/executor/__init__.py +12 -0
- relationalai/early_access/lqp/ir/__init__.py +12 -0
- relationalai/early_access/lqp/passes/__init__.py +12 -0
- relationalai/early_access/lqp/pragmas/__init__.py +12 -0
- relationalai/early_access/lqp/primitives/__init__.py +12 -0
- relationalai/early_access/lqp/types/__init__.py +12 -0
- relationalai/early_access/lqp/utils/__init__.py +12 -0
- relationalai/early_access/lqp/validators/__init__.py +12 -0
- relationalai/early_access/metamodel/__init__.py +58 -0
- relationalai/early_access/metamodel/builtins/__init__.py +12 -0
- relationalai/early_access/metamodel/compiler/__init__.py +12 -0
- relationalai/early_access/metamodel/dependency/__init__.py +12 -0
- relationalai/early_access/metamodel/factory/__init__.py +17 -0
- relationalai/early_access/metamodel/helpers/__init__.py +12 -0
- relationalai/early_access/metamodel/ir/__init__.py +14 -0
- relationalai/early_access/metamodel/rewrite/__init__.py +7 -0
- relationalai/early_access/metamodel/typer/__init__.py +3 -0
- relationalai/early_access/metamodel/typer/typer/__init__.py +12 -0
- relationalai/early_access/metamodel/types/__init__.py +15 -0
- relationalai/early_access/metamodel/util/__init__.py +15 -0
- relationalai/early_access/metamodel/visitor/__init__.py +12 -0
- relationalai/early_access/rel/__init__.py +12 -0
- relationalai/early_access/rel/executor/__init__.py +12 -0
- relationalai/early_access/rel/rel_utils/__init__.py +12 -0
- relationalai/early_access/rel/rewrite/__init__.py +7 -0
- relationalai/early_access/solvers/__init__.py +19 -0
- relationalai/early_access/sql/__init__.py +11 -0
- relationalai/early_access/sql/executor/__init__.py +3 -0
- relationalai/early_access/sql/rewrite/__init__.py +3 -0
- relationalai/early_access/tests/logging/__init__.py +12 -0
- relationalai/early_access/tests/test_snapshot_base/__init__.py +12 -0
- relationalai/early_access/tests/utils/__init__.py +12 -0
- relationalai/environments/__init__.py +35 -0
- relationalai/environments/base.py +381 -0
- relationalai/environments/colab.py +14 -0
- relationalai/environments/generic.py +71 -0
- relationalai/environments/ipython.py +68 -0
- relationalai/environments/jupyter.py +9 -0
- relationalai/environments/snowbook.py +169 -0
- relationalai/errors.py +2496 -0
- relationalai/experimental/SF.py +38 -0
- relationalai/experimental/inspect.py +47 -0
- relationalai/experimental/pathfinder/__init__.py +158 -0
- relationalai/experimental/pathfinder/api.py +160 -0
- relationalai/experimental/pathfinder/automaton.py +584 -0
- relationalai/experimental/pathfinder/bridge.py +226 -0
- relationalai/experimental/pathfinder/compiler.py +416 -0
- relationalai/experimental/pathfinder/datalog.py +214 -0
- relationalai/experimental/pathfinder/diagnostics.py +56 -0
- relationalai/experimental/pathfinder/filter.py +236 -0
- relationalai/experimental/pathfinder/glushkov.py +439 -0
- relationalai/experimental/pathfinder/options.py +265 -0
- relationalai/experimental/pathfinder/pathfinder-v0.7.0.rel +1951 -0
- relationalai/experimental/pathfinder/rpq.py +344 -0
- relationalai/experimental/pathfinder/transition.py +200 -0
- relationalai/experimental/pathfinder/utils.py +26 -0
- relationalai/experimental/paths/README.md +107 -0
- relationalai/experimental/paths/api.py +143 -0
- relationalai/experimental/paths/benchmarks/grid_graph.py +37 -0
- relationalai/experimental/paths/code_organization.md +2 -0
- relationalai/experimental/paths/examples/Movies.ipynb +16328 -0
- relationalai/experimental/paths/examples/basic_example.py +40 -0
- relationalai/experimental/paths/examples/minimal_engine_warmup.py +3 -0
- relationalai/experimental/paths/examples/movie_example.py +77 -0
- relationalai/experimental/paths/examples/movies_data/actedin.csv +193 -0
- relationalai/experimental/paths/examples/movies_data/directed.csv +45 -0
- relationalai/experimental/paths/examples/movies_data/follows.csv +7 -0
- relationalai/experimental/paths/examples/movies_data/movies.csv +39 -0
- relationalai/experimental/paths/examples/movies_data/person.csv +134 -0
- relationalai/experimental/paths/examples/movies_data/produced.csv +16 -0
- relationalai/experimental/paths/examples/movies_data/ratings.csv +10 -0
- relationalai/experimental/paths/examples/movies_data/wrote.csv +11 -0
- relationalai/experimental/paths/examples/paths_benchmark.py +115 -0
- relationalai/experimental/paths/examples/paths_example.py +116 -0
- relationalai/experimental/paths/examples/pattern_to_automaton.py +28 -0
- relationalai/experimental/paths/find_paths_via_automaton.py +85 -0
- relationalai/experimental/paths/graph.py +185 -0
- relationalai/experimental/paths/path_algorithms/find_paths.py +280 -0
- relationalai/experimental/paths/path_algorithms/one_sided_ball_repetition.py +26 -0
- relationalai/experimental/paths/path_algorithms/one_sided_ball_upto.py +111 -0
- relationalai/experimental/paths/path_algorithms/single.py +59 -0
- relationalai/experimental/paths/path_algorithms/two_sided_balls_repetition.py +39 -0
- relationalai/experimental/paths/path_algorithms/two_sided_balls_upto.py +103 -0
- relationalai/experimental/paths/path_algorithms/usp-old.py +130 -0
- relationalai/experimental/paths/path_algorithms/usp-tuple.py +183 -0
- relationalai/experimental/paths/path_algorithms/usp.py +150 -0
- relationalai/experimental/paths/product_graph.py +93 -0
- relationalai/experimental/paths/rpq/automaton.py +584 -0
- relationalai/experimental/paths/rpq/diagnostics.py +56 -0
- relationalai/experimental/paths/rpq/rpq.py +378 -0
- relationalai/experimental/paths/tests/tests_limit_sp_max_length.py +90 -0
- relationalai/experimental/paths/tests/tests_limit_sp_multiple.py +119 -0
- relationalai/experimental/paths/tests/tests_limit_sp_single.py +104 -0
- relationalai/experimental/paths/tests/tests_limit_walks_multiple.py +113 -0
- relationalai/experimental/paths/tests/tests_limit_walks_single.py +149 -0
- relationalai/experimental/paths/tests/tests_one_sided_ball_repetition_multiple.py +70 -0
- relationalai/experimental/paths/tests/tests_one_sided_ball_repetition_single.py +64 -0
- relationalai/experimental/paths/tests/tests_one_sided_ball_upto_multiple.py +115 -0
- relationalai/experimental/paths/tests/tests_one_sided_ball_upto_single.py +75 -0
- relationalai/experimental/paths/tests/tests_single_paths.py +152 -0
- relationalai/experimental/paths/tests/tests_single_walks.py +208 -0
- relationalai/experimental/paths/tests/tests_single_walks_undirected.py +297 -0
- relationalai/experimental/paths/tests/tests_two_sided_balls_repetition_multiple.py +107 -0
- relationalai/experimental/paths/tests/tests_two_sided_balls_repetition_single.py +76 -0
- relationalai/experimental/paths/tests/tests_two_sided_balls_upto_multiple.py +76 -0
- relationalai/experimental/paths/tests/tests_two_sided_balls_upto_single.py +110 -0
- relationalai/experimental/paths/tests/tests_usp_nsp_multiple.py +229 -0
- relationalai/experimental/paths/tests/tests_usp_nsp_single.py +108 -0
- relationalai/experimental/paths/tree_agg.py +168 -0
- relationalai/experimental/paths/utilities/iterators.py +27 -0
- relationalai/experimental/paths/utilities/prefix_sum.py +91 -0
- relationalai/experimental/solvers.py +1087 -0
- relationalai/loaders/csv.py +195 -0
- relationalai/loaders/loader.py +177 -0
- relationalai/loaders/types.py +23 -0
- relationalai/rel_emitter.py +373 -0
- relationalai/rel_utils.py +185 -0
- relationalai/semantics/__init__.py +22 -146
- relationalai/semantics/designs/query_builder/identify_by.md +106 -0
- relationalai/semantics/devtools/benchmark_lqp.py +535 -0
- relationalai/semantics/devtools/compilation_manager.py +294 -0
- relationalai/semantics/devtools/extract_lqp.py +110 -0
- relationalai/semantics/internal/internal.py +3785 -0
- relationalai/semantics/internal/snowflake.py +325 -0
- relationalai/semantics/lqp/README.md +34 -0
- relationalai/semantics/lqp/builtins.py +16 -0
- relationalai/semantics/lqp/compiler.py +22 -0
- relationalai/semantics/lqp/constructors.py +68 -0
- relationalai/semantics/lqp/executor.py +469 -0
- relationalai/semantics/lqp/intrinsics.py +24 -0
- relationalai/semantics/lqp/model2lqp.py +877 -0
- relationalai/semantics/lqp/passes.py +680 -0
- relationalai/semantics/lqp/primitives.py +252 -0
- relationalai/semantics/lqp/result_helpers.py +202 -0
- relationalai/semantics/lqp/rewrite/annotate_constraints.py +57 -0
- relationalai/semantics/lqp/rewrite/cdc.py +216 -0
- relationalai/semantics/lqp/rewrite/extract_common.py +338 -0
- relationalai/semantics/lqp/rewrite/extract_keys.py +512 -0
- relationalai/semantics/lqp/rewrite/function_annotations.py +114 -0
- relationalai/semantics/lqp/rewrite/functional_dependencies.py +314 -0
- relationalai/semantics/lqp/rewrite/quantify_vars.py +296 -0
- relationalai/semantics/lqp/rewrite/splinter.py +76 -0
- relationalai/semantics/lqp/types.py +101 -0
- relationalai/semantics/lqp/utils.py +160 -0
- relationalai/semantics/lqp/validators.py +57 -0
- relationalai/semantics/metamodel/__init__.py +40 -6
- relationalai/semantics/metamodel/builtins.py +771 -205
- relationalai/semantics/metamodel/compiler.py +133 -0
- relationalai/semantics/metamodel/dependency.py +862 -0
- relationalai/semantics/metamodel/executor.py +61 -0
- relationalai/semantics/metamodel/factory.py +287 -0
- relationalai/semantics/metamodel/helpers.py +361 -0
- relationalai/semantics/metamodel/rewrite/discharge_constraints.py +39 -0
- relationalai/semantics/metamodel/rewrite/dnf_union_splitter.py +210 -0
- relationalai/semantics/metamodel/rewrite/extract_nested_logicals.py +78 -0
- relationalai/semantics/metamodel/rewrite/flatten.py +554 -0
- relationalai/semantics/metamodel/rewrite/format_outputs.py +165 -0
- relationalai/semantics/metamodel/typer/checker.py +353 -0
- relationalai/semantics/metamodel/typer/typer.py +1395 -0
- relationalai/semantics/metamodel/util.py +506 -0
- relationalai/semantics/reasoners/__init__.py +10 -0
- relationalai/semantics/reasoners/graph/README.md +620 -0
- relationalai/semantics/reasoners/graph/__init__.py +37 -0
- relationalai/semantics/reasoners/graph/core.py +9019 -0
- relationalai/semantics/reasoners/graph/design/beyond_demand_transform.md +797 -0
- relationalai/semantics/reasoners/graph/tests/README.md +21 -0
- relationalai/semantics/reasoners/optimization/__init__.py +68 -0
- relationalai/semantics/reasoners/optimization/common.py +88 -0
- relationalai/semantics/reasoners/optimization/solvers_dev.py +568 -0
- relationalai/semantics/reasoners/optimization/solvers_pb.py +1414 -0
- relationalai/semantics/rel/builtins.py +40 -0
- relationalai/semantics/rel/compiler.py +989 -0
- relationalai/semantics/rel/executor.py +362 -0
- relationalai/semantics/rel/rel.py +482 -0
- relationalai/semantics/rel/rel_utils.py +276 -0
- relationalai/semantics/snowflake/__init__.py +3 -0
- relationalai/semantics/sql/compiler.py +2503 -0
- relationalai/semantics/sql/executor/duck_db.py +52 -0
- relationalai/semantics/sql/executor/result_helpers.py +64 -0
- relationalai/semantics/sql/executor/snowflake.py +149 -0
- relationalai/semantics/sql/rewrite/denormalize.py +222 -0
- relationalai/semantics/sql/rewrite/double_negation.py +49 -0
- relationalai/semantics/sql/rewrite/recursive_union.py +127 -0
- relationalai/semantics/sql/rewrite/sort_output_query.py +246 -0
- relationalai/semantics/sql/sql.py +504 -0
- relationalai/semantics/std/__init__.py +40 -60
- relationalai/semantics/std/constraints.py +43 -37
- relationalai/semantics/std/datetime.py +135 -246
- relationalai/semantics/std/decimals.py +52 -45
- relationalai/semantics/std/floats.py +5 -13
- relationalai/semantics/std/integers.py +11 -26
- relationalai/semantics/std/math.py +112 -183
- relationalai/semantics/std/pragmas.py +11 -0
- relationalai/semantics/std/re.py +62 -80
- relationalai/semantics/std/std.py +14 -0
- relationalai/semantics/std/strings.py +60 -117
- relationalai/semantics/tests/test_snapshot_abstract.py +143 -0
- relationalai/semantics/tests/test_snapshot_base.py +9 -0
- relationalai/semantics/tests/utils.py +46 -0
- relationalai/std/__init__.py +70 -0
- relationalai/tools/cli.py +1936 -0
- relationalai/tools/cli_controls.py +1826 -0
- relationalai/tools/cli_helpers.py +398 -0
- relationalai/tools/debugger.py +183 -289
- relationalai/tools/debugger_client.py +109 -0
- relationalai/tools/debugger_server.py +302 -0
- relationalai/tools/dev.py +685 -0
- relationalai/tools/notes +7 -0
- relationalai/tools/qb_debugger.py +425 -0
- relationalai/util/clean_up_databases.py +95 -0
- relationalai/util/format.py +106 -48
- relationalai/util/list_databases.py +9 -0
- relationalai/util/otel_configuration.py +26 -0
- relationalai/util/otel_handler.py +484 -0
- relationalai/util/snowflake_handler.py +88 -0
- relationalai/util/span_format_test.py +43 -0
- relationalai/util/span_tracker.py +207 -0
- relationalai/util/spans_file_handler.py +72 -0
- relationalai/util/tracing_handler.py +34 -0
- relationalai-0.13.1.dist-info/METADATA +74 -0
- relationalai-0.13.1.dist-info/RECORD +459 -0
- relationalai-0.13.1.dist-info/WHEEL +4 -0
- relationalai-0.13.1.dist-info/entry_points.txt +3 -0
- relationalai-0.13.1.dist-info/licenses/LICENSE +202 -0
- relationalai_test_util/__init__.py +4 -0
- relationalai_test_util/fixtures.py +233 -0
- relationalai_test_util/snapshot.py +252 -0
- relationalai_test_util/traceback.py +118 -0
- relationalai/config/__init__.py +0 -56
- relationalai/config/config.py +0 -289
- relationalai/config/config_fields.py +0 -86
- relationalai/config/connections/__init__.py +0 -46
- relationalai/config/connections/base.py +0 -23
- relationalai/config/connections/duckdb.py +0 -29
- relationalai/config/connections/snowflake.py +0 -243
- relationalai/config/external/__init__.py +0 -17
- relationalai/config/external/dbt_converter.py +0 -101
- relationalai/config/external/dbt_models.py +0 -93
- relationalai/config/external/snowflake_converter.py +0 -41
- relationalai/config/external/snowflake_models.py +0 -85
- relationalai/config/external/utils.py +0 -19
- relationalai/semantics/backends/lqp/annotations.py +0 -11
- relationalai/semantics/backends/sql/sql_compiler.py +0 -327
- relationalai/semantics/frontend/base.py +0 -1707
- relationalai/semantics/frontend/core.py +0 -179
- relationalai/semantics/frontend/front_compiler.py +0 -1313
- relationalai/semantics/frontend/pprint.py +0 -408
- relationalai/semantics/metamodel/metamodel.py +0 -437
- relationalai/semantics/metamodel/metamodel_analyzer.py +0 -519
- relationalai/semantics/metamodel/metamodel_compiler.py +0 -0
- relationalai/semantics/metamodel/pprint.py +0 -412
- relationalai/semantics/metamodel/rewriter.py +0 -266
- relationalai/semantics/metamodel/typer.py +0 -1378
- relationalai/semantics/std/aggregates.py +0 -149
- relationalai/semantics/std/common.py +0 -44
- relationalai/semantics/std/numbers.py +0 -86
- relationalai/shims/executor.py +0 -147
- relationalai/shims/helpers.py +0 -126
- relationalai/shims/hoister.py +0 -221
- relationalai/shims/mm2v0.py +0 -1290
- relationalai/tools/cli/__init__.py +0 -6
- relationalai/tools/cli/cli.py +0 -90
- relationalai/tools/cli/components/__init__.py +0 -5
- relationalai/tools/cli/components/progress_reader.py +0 -1524
- relationalai/tools/cli/components/utils.py +0 -58
- relationalai/tools/cli/config_template.py +0 -45
- relationalai/tools/cli/dev.py +0 -19
- relationalai/tools/typer_debugger.py +0 -93
- relationalai/util/dataclasses.py +0 -43
- relationalai/util/docutils.py +0 -40
- relationalai/util/error.py +0 -199
- relationalai/util/naming.py +0 -145
- relationalai/util/python.py +0 -35
- relationalai/util/runtime.py +0 -156
- relationalai/util/schema.py +0 -197
- relationalai/util/source.py +0 -185
- relationalai/util/structures.py +0 -163
- relationalai/util/tracing.py +0 -261
- relationalai-0.13.0.dev0.dist-info/METADATA +0 -46
- relationalai-0.13.0.dev0.dist-info/RECORD +0 -488
- relationalai-0.13.0.dev0.dist-info/WHEEL +0 -5
- relationalai-0.13.0.dev0.dist-info/entry_points.txt +0 -3
- relationalai-0.13.0.dev0.dist-info/top_level.txt +0 -2
- v0/relationalai/__init__.py +0 -216
- v0/relationalai/clients/__init__.py +0 -5
- v0/relationalai/clients/azure.py +0 -477
- v0/relationalai/clients/client.py +0 -912
- v0/relationalai/clients/config.py +0 -673
- v0/relationalai/clients/direct_access_client.py +0 -118
- v0/relationalai/clients/hash_util.py +0 -31
- v0/relationalai/clients/local.py +0 -571
- v0/relationalai/clients/profile_polling.py +0 -73
- v0/relationalai/clients/result_helpers.py +0 -420
- v0/relationalai/clients/snowflake.py +0 -3869
- v0/relationalai/clients/types.py +0 -113
- v0/relationalai/clients/use_index_poller.py +0 -980
- v0/relationalai/clients/util.py +0 -356
- v0/relationalai/debugging.py +0 -389
- v0/relationalai/dsl.py +0 -1749
- v0/relationalai/early_access/builder/__init__.py +0 -30
- v0/relationalai/early_access/builder/builder/__init__.py +0 -35
- v0/relationalai/early_access/builder/snowflake/__init__.py +0 -12
- v0/relationalai/early_access/builder/std/__init__.py +0 -25
- v0/relationalai/early_access/builder/std/decimals/__init__.py +0 -12
- v0/relationalai/early_access/builder/std/integers/__init__.py +0 -12
- v0/relationalai/early_access/builder/std/math/__init__.py +0 -12
- v0/relationalai/early_access/builder/std/strings/__init__.py +0 -14
- v0/relationalai/early_access/devtools/__init__.py +0 -12
- v0/relationalai/early_access/devtools/benchmark_lqp/__init__.py +0 -12
- v0/relationalai/early_access/devtools/extract_lqp/__init__.py +0 -12
- v0/relationalai/early_access/dsl/adapters/orm/adapter_qb.py +0 -427
- v0/relationalai/early_access/dsl/adapters/orm/parser.py +0 -636
- v0/relationalai/early_access/dsl/adapters/owl/adapter.py +0 -176
- v0/relationalai/early_access/dsl/adapters/owl/parser.py +0 -160
- v0/relationalai/early_access/dsl/bindings/common.py +0 -402
- v0/relationalai/early_access/dsl/bindings/csv.py +0 -170
- v0/relationalai/early_access/dsl/bindings/legacy/binding_models.py +0 -143
- v0/relationalai/early_access/dsl/bindings/snowflake.py +0 -64
- v0/relationalai/early_access/dsl/codegen/binder.py +0 -411
- v0/relationalai/early_access/dsl/codegen/common.py +0 -79
- v0/relationalai/early_access/dsl/codegen/helpers.py +0 -23
- v0/relationalai/early_access/dsl/codegen/relations.py +0 -700
- v0/relationalai/early_access/dsl/codegen/weaver.py +0 -417
- v0/relationalai/early_access/dsl/core/builders/__init__.py +0 -47
- v0/relationalai/early_access/dsl/core/builders/logic.py +0 -19
- v0/relationalai/early_access/dsl/core/builders/scalar_constraint.py +0 -11
- v0/relationalai/early_access/dsl/core/constraints/predicate/atomic.py +0 -455
- v0/relationalai/early_access/dsl/core/constraints/predicate/universal.py +0 -73
- v0/relationalai/early_access/dsl/core/constraints/scalar.py +0 -310
- v0/relationalai/early_access/dsl/core/context.py +0 -13
- v0/relationalai/early_access/dsl/core/cset.py +0 -132
- v0/relationalai/early_access/dsl/core/exprs/__init__.py +0 -116
- v0/relationalai/early_access/dsl/core/exprs/relational.py +0 -18
- v0/relationalai/early_access/dsl/core/exprs/scalar.py +0 -412
- v0/relationalai/early_access/dsl/core/instances.py +0 -44
- v0/relationalai/early_access/dsl/core/logic/__init__.py +0 -193
- v0/relationalai/early_access/dsl/core/logic/aggregation.py +0 -98
- v0/relationalai/early_access/dsl/core/logic/exists.py +0 -223
- v0/relationalai/early_access/dsl/core/logic/helper.py +0 -163
- v0/relationalai/early_access/dsl/core/namespaces.py +0 -32
- v0/relationalai/early_access/dsl/core/relations.py +0 -276
- v0/relationalai/early_access/dsl/core/rules.py +0 -112
- v0/relationalai/early_access/dsl/core/std/__init__.py +0 -45
- v0/relationalai/early_access/dsl/core/temporal/recall.py +0 -6
- v0/relationalai/early_access/dsl/core/types/__init__.py +0 -270
- v0/relationalai/early_access/dsl/core/types/concepts.py +0 -128
- v0/relationalai/early_access/dsl/core/types/constrained/__init__.py +0 -267
- v0/relationalai/early_access/dsl/core/types/constrained/nominal.py +0 -143
- v0/relationalai/early_access/dsl/core/types/constrained/subtype.py +0 -124
- v0/relationalai/early_access/dsl/core/types/standard.py +0 -92
- v0/relationalai/early_access/dsl/core/types/unconstrained.py +0 -50
- v0/relationalai/early_access/dsl/core/types/variables.py +0 -203
- v0/relationalai/early_access/dsl/ir/compiler.py +0 -318
- v0/relationalai/early_access/dsl/ir/executor.py +0 -260
- v0/relationalai/early_access/dsl/ontologies/constraints.py +0 -88
- v0/relationalai/early_access/dsl/ontologies/export.py +0 -30
- v0/relationalai/early_access/dsl/ontologies/models.py +0 -453
- v0/relationalai/early_access/dsl/ontologies/python_printer.py +0 -303
- v0/relationalai/early_access/dsl/ontologies/readings.py +0 -60
- v0/relationalai/early_access/dsl/ontologies/relationships.py +0 -322
- v0/relationalai/early_access/dsl/ontologies/roles.py +0 -87
- v0/relationalai/early_access/dsl/ontologies/subtyping.py +0 -55
- v0/relationalai/early_access/dsl/orm/constraints.py +0 -438
- v0/relationalai/early_access/dsl/orm/measures/dimensions.py +0 -200
- v0/relationalai/early_access/dsl/orm/measures/initializer.py +0 -16
- v0/relationalai/early_access/dsl/orm/measures/measure_rules.py +0 -275
- v0/relationalai/early_access/dsl/orm/measures/measures.py +0 -299
- v0/relationalai/early_access/dsl/orm/measures/role_exprs.py +0 -268
- v0/relationalai/early_access/dsl/orm/models.py +0 -256
- v0/relationalai/early_access/dsl/orm/object_oriented_printer.py +0 -344
- v0/relationalai/early_access/dsl/orm/printer.py +0 -469
- v0/relationalai/early_access/dsl/orm/reasoners.py +0 -480
- v0/relationalai/early_access/dsl/orm/relations.py +0 -19
- v0/relationalai/early_access/dsl/orm/relationships.py +0 -251
- v0/relationalai/early_access/dsl/orm/types.py +0 -42
- v0/relationalai/early_access/dsl/orm/utils.py +0 -79
- v0/relationalai/early_access/dsl/orm/verb.py +0 -204
- v0/relationalai/early_access/dsl/physical_metadata/tables.py +0 -133
- v0/relationalai/early_access/dsl/relations.py +0 -170
- v0/relationalai/early_access/dsl/rulesets.py +0 -69
- v0/relationalai/early_access/dsl/schemas/__init__.py +0 -450
- v0/relationalai/early_access/dsl/schemas/builder.py +0 -48
- v0/relationalai/early_access/dsl/schemas/comp_names.py +0 -51
- v0/relationalai/early_access/dsl/schemas/components.py +0 -203
- v0/relationalai/early_access/dsl/schemas/contexts.py +0 -156
- v0/relationalai/early_access/dsl/schemas/exprs.py +0 -89
- v0/relationalai/early_access/dsl/schemas/fragments.py +0 -464
- v0/relationalai/early_access/dsl/serialization.py +0 -79
- v0/relationalai/early_access/dsl/serialize/exporter.py +0 -163
- v0/relationalai/early_access/dsl/snow/api.py +0 -104
- v0/relationalai/early_access/dsl/snow/common.py +0 -76
- v0/relationalai/early_access/dsl/state_mgmt/__init__.py +0 -129
- v0/relationalai/early_access/dsl/state_mgmt/state_charts.py +0 -125
- v0/relationalai/early_access/dsl/state_mgmt/transitions.py +0 -130
- v0/relationalai/early_access/dsl/types/__init__.py +0 -40
- v0/relationalai/early_access/dsl/types/concepts.py +0 -12
- v0/relationalai/early_access/dsl/types/entities.py +0 -135
- v0/relationalai/early_access/dsl/types/values.py +0 -17
- v0/relationalai/early_access/dsl/utils.py +0 -102
- v0/relationalai/early_access/graphs/__init__.py +0 -13
- v0/relationalai/early_access/lqp/__init__.py +0 -12
- v0/relationalai/early_access/lqp/compiler/__init__.py +0 -12
- v0/relationalai/early_access/lqp/constructors/__init__.py +0 -18
- v0/relationalai/early_access/lqp/executor/__init__.py +0 -12
- v0/relationalai/early_access/lqp/ir/__init__.py +0 -12
- v0/relationalai/early_access/lqp/passes/__init__.py +0 -12
- v0/relationalai/early_access/lqp/pragmas/__init__.py +0 -12
- v0/relationalai/early_access/lqp/primitives/__init__.py +0 -12
- v0/relationalai/early_access/lqp/types/__init__.py +0 -12
- v0/relationalai/early_access/lqp/utils/__init__.py +0 -12
- v0/relationalai/early_access/lqp/validators/__init__.py +0 -12
- v0/relationalai/early_access/metamodel/__init__.py +0 -58
- v0/relationalai/early_access/metamodel/builtins/__init__.py +0 -12
- v0/relationalai/early_access/metamodel/compiler/__init__.py +0 -12
- v0/relationalai/early_access/metamodel/dependency/__init__.py +0 -12
- v0/relationalai/early_access/metamodel/factory/__init__.py +0 -17
- v0/relationalai/early_access/metamodel/helpers/__init__.py +0 -12
- v0/relationalai/early_access/metamodel/ir/__init__.py +0 -14
- v0/relationalai/early_access/metamodel/rewrite/__init__.py +0 -7
- v0/relationalai/early_access/metamodel/typer/__init__.py +0 -3
- v0/relationalai/early_access/metamodel/typer/typer/__init__.py +0 -12
- v0/relationalai/early_access/metamodel/types/__init__.py +0 -15
- v0/relationalai/early_access/metamodel/util/__init__.py +0 -15
- v0/relationalai/early_access/metamodel/visitor/__init__.py +0 -12
- v0/relationalai/early_access/rel/__init__.py +0 -12
- v0/relationalai/early_access/rel/executor/__init__.py +0 -12
- v0/relationalai/early_access/rel/rel_utils/__init__.py +0 -12
- v0/relationalai/early_access/rel/rewrite/__init__.py +0 -7
- v0/relationalai/early_access/solvers/__init__.py +0 -19
- v0/relationalai/early_access/sql/__init__.py +0 -11
- v0/relationalai/early_access/sql/executor/__init__.py +0 -3
- v0/relationalai/early_access/sql/rewrite/__init__.py +0 -3
- v0/relationalai/early_access/tests/logging/__init__.py +0 -12
- v0/relationalai/early_access/tests/test_snapshot_base/__init__.py +0 -12
- v0/relationalai/early_access/tests/utils/__init__.py +0 -12
- v0/relationalai/environments/__init__.py +0 -35
- v0/relationalai/environments/base.py +0 -381
- v0/relationalai/environments/colab.py +0 -14
- v0/relationalai/environments/generic.py +0 -71
- v0/relationalai/environments/ipython.py +0 -68
- v0/relationalai/environments/jupyter.py +0 -9
- v0/relationalai/environments/snowbook.py +0 -169
- v0/relationalai/errors.py +0 -2455
- v0/relationalai/experimental/SF.py +0 -38
- v0/relationalai/experimental/inspect.py +0 -47
- v0/relationalai/experimental/pathfinder/__init__.py +0 -158
- v0/relationalai/experimental/pathfinder/api.py +0 -160
- v0/relationalai/experimental/pathfinder/automaton.py +0 -584
- v0/relationalai/experimental/pathfinder/bridge.py +0 -226
- v0/relationalai/experimental/pathfinder/compiler.py +0 -416
- v0/relationalai/experimental/pathfinder/datalog.py +0 -214
- v0/relationalai/experimental/pathfinder/diagnostics.py +0 -56
- v0/relationalai/experimental/pathfinder/filter.py +0 -236
- v0/relationalai/experimental/pathfinder/glushkov.py +0 -439
- v0/relationalai/experimental/pathfinder/options.py +0 -265
- v0/relationalai/experimental/pathfinder/rpq.py +0 -344
- v0/relationalai/experimental/pathfinder/transition.py +0 -200
- v0/relationalai/experimental/pathfinder/utils.py +0 -26
- v0/relationalai/experimental/paths/api.py +0 -143
- v0/relationalai/experimental/paths/benchmarks/grid_graph.py +0 -37
- v0/relationalai/experimental/paths/examples/basic_example.py +0 -40
- v0/relationalai/experimental/paths/examples/minimal_engine_warmup.py +0 -3
- v0/relationalai/experimental/paths/examples/movie_example.py +0 -77
- v0/relationalai/experimental/paths/examples/paths_benchmark.py +0 -115
- v0/relationalai/experimental/paths/examples/paths_example.py +0 -116
- v0/relationalai/experimental/paths/examples/pattern_to_automaton.py +0 -28
- v0/relationalai/experimental/paths/find_paths_via_automaton.py +0 -85
- v0/relationalai/experimental/paths/graph.py +0 -185
- v0/relationalai/experimental/paths/path_algorithms/find_paths.py +0 -280
- v0/relationalai/experimental/paths/path_algorithms/one_sided_ball_repetition.py +0 -26
- v0/relationalai/experimental/paths/path_algorithms/one_sided_ball_upto.py +0 -111
- v0/relationalai/experimental/paths/path_algorithms/single.py +0 -59
- v0/relationalai/experimental/paths/path_algorithms/two_sided_balls_repetition.py +0 -39
- v0/relationalai/experimental/paths/path_algorithms/two_sided_balls_upto.py +0 -103
- v0/relationalai/experimental/paths/path_algorithms/usp-old.py +0 -130
- v0/relationalai/experimental/paths/path_algorithms/usp-tuple.py +0 -183
- v0/relationalai/experimental/paths/path_algorithms/usp.py +0 -150
- v0/relationalai/experimental/paths/product_graph.py +0 -93
- v0/relationalai/experimental/paths/rpq/automaton.py +0 -584
- v0/relationalai/experimental/paths/rpq/diagnostics.py +0 -56
- v0/relationalai/experimental/paths/rpq/rpq.py +0 -378
- v0/relationalai/experimental/paths/tests/tests_limit_sp_max_length.py +0 -90
- v0/relationalai/experimental/paths/tests/tests_limit_sp_multiple.py +0 -119
- v0/relationalai/experimental/paths/tests/tests_limit_sp_single.py +0 -104
- v0/relationalai/experimental/paths/tests/tests_limit_walks_multiple.py +0 -113
- v0/relationalai/experimental/paths/tests/tests_limit_walks_single.py +0 -149
- v0/relationalai/experimental/paths/tests/tests_one_sided_ball_repetition_multiple.py +0 -70
- v0/relationalai/experimental/paths/tests/tests_one_sided_ball_repetition_single.py +0 -64
- v0/relationalai/experimental/paths/tests/tests_one_sided_ball_upto_multiple.py +0 -115
- v0/relationalai/experimental/paths/tests/tests_one_sided_ball_upto_single.py +0 -75
- v0/relationalai/experimental/paths/tests/tests_single_paths.py +0 -152
- v0/relationalai/experimental/paths/tests/tests_single_walks.py +0 -208
- v0/relationalai/experimental/paths/tests/tests_single_walks_undirected.py +0 -297
- v0/relationalai/experimental/paths/tests/tests_two_sided_balls_repetition_multiple.py +0 -107
- v0/relationalai/experimental/paths/tests/tests_two_sided_balls_repetition_single.py +0 -76
- v0/relationalai/experimental/paths/tests/tests_two_sided_balls_upto_multiple.py +0 -76
- v0/relationalai/experimental/paths/tests/tests_two_sided_balls_upto_single.py +0 -110
- v0/relationalai/experimental/paths/tests/tests_usp_nsp_multiple.py +0 -229
- v0/relationalai/experimental/paths/tests/tests_usp_nsp_single.py +0 -108
- v0/relationalai/experimental/paths/tree_agg.py +0 -168
- v0/relationalai/experimental/paths/utilities/iterators.py +0 -27
- v0/relationalai/experimental/paths/utilities/prefix_sum.py +0 -91
- v0/relationalai/experimental/solvers.py +0 -1087
- v0/relationalai/loaders/csv.py +0 -195
- v0/relationalai/loaders/loader.py +0 -177
- v0/relationalai/loaders/types.py +0 -23
- v0/relationalai/rel_emitter.py +0 -373
- v0/relationalai/rel_utils.py +0 -185
- v0/relationalai/semantics/__init__.py +0 -29
- v0/relationalai/semantics/devtools/benchmark_lqp.py +0 -536
- v0/relationalai/semantics/devtools/compilation_manager.py +0 -294
- v0/relationalai/semantics/devtools/extract_lqp.py +0 -110
- v0/relationalai/semantics/internal/internal.py +0 -3785
- v0/relationalai/semantics/internal/snowflake.py +0 -324
- v0/relationalai/semantics/lqp/builtins.py +0 -16
- v0/relationalai/semantics/lqp/compiler.py +0 -22
- v0/relationalai/semantics/lqp/constructors.py +0 -68
- v0/relationalai/semantics/lqp/executor.py +0 -469
- v0/relationalai/semantics/lqp/intrinsics.py +0 -24
- v0/relationalai/semantics/lqp/model2lqp.py +0 -839
- v0/relationalai/semantics/lqp/passes.py +0 -680
- v0/relationalai/semantics/lqp/primitives.py +0 -252
- v0/relationalai/semantics/lqp/result_helpers.py +0 -202
- v0/relationalai/semantics/lqp/rewrite/annotate_constraints.py +0 -57
- v0/relationalai/semantics/lqp/rewrite/cdc.py +0 -216
- v0/relationalai/semantics/lqp/rewrite/extract_common.py +0 -338
- v0/relationalai/semantics/lqp/rewrite/extract_keys.py +0 -449
- v0/relationalai/semantics/lqp/rewrite/function_annotations.py +0 -114
- v0/relationalai/semantics/lqp/rewrite/functional_dependencies.py +0 -314
- v0/relationalai/semantics/lqp/rewrite/quantify_vars.py +0 -296
- v0/relationalai/semantics/lqp/rewrite/splinter.py +0 -76
- v0/relationalai/semantics/lqp/types.py +0 -101
- v0/relationalai/semantics/lqp/utils.py +0 -160
- v0/relationalai/semantics/lqp/validators.py +0 -57
- v0/relationalai/semantics/metamodel/__init__.py +0 -40
- v0/relationalai/semantics/metamodel/builtins.py +0 -774
- v0/relationalai/semantics/metamodel/compiler.py +0 -133
- v0/relationalai/semantics/metamodel/dependency.py +0 -862
- v0/relationalai/semantics/metamodel/executor.py +0 -61
- v0/relationalai/semantics/metamodel/factory.py +0 -287
- v0/relationalai/semantics/metamodel/helpers.py +0 -361
- v0/relationalai/semantics/metamodel/rewrite/discharge_constraints.py +0 -39
- v0/relationalai/semantics/metamodel/rewrite/dnf_union_splitter.py +0 -210
- v0/relationalai/semantics/metamodel/rewrite/extract_nested_logicals.py +0 -78
- v0/relationalai/semantics/metamodel/rewrite/flatten.py +0 -549
- v0/relationalai/semantics/metamodel/rewrite/format_outputs.py +0 -165
- v0/relationalai/semantics/metamodel/typer/checker.py +0 -353
- v0/relationalai/semantics/metamodel/typer/typer.py +0 -1395
- v0/relationalai/semantics/metamodel/util.py +0 -505
- v0/relationalai/semantics/reasoners/__init__.py +0 -10
- v0/relationalai/semantics/reasoners/graph/__init__.py +0 -37
- v0/relationalai/semantics/reasoners/graph/core.py +0 -9020
- v0/relationalai/semantics/reasoners/optimization/__init__.py +0 -68
- v0/relationalai/semantics/reasoners/optimization/common.py +0 -88
- v0/relationalai/semantics/reasoners/optimization/solvers_dev.py +0 -568
- v0/relationalai/semantics/reasoners/optimization/solvers_pb.py +0 -1163
- v0/relationalai/semantics/rel/builtins.py +0 -40
- v0/relationalai/semantics/rel/compiler.py +0 -989
- v0/relationalai/semantics/rel/executor.py +0 -359
- v0/relationalai/semantics/rel/rel.py +0 -482
- v0/relationalai/semantics/rel/rel_utils.py +0 -276
- v0/relationalai/semantics/snowflake/__init__.py +0 -3
- v0/relationalai/semantics/sql/compiler.py +0 -2503
- v0/relationalai/semantics/sql/executor/duck_db.py +0 -52
- v0/relationalai/semantics/sql/executor/result_helpers.py +0 -64
- v0/relationalai/semantics/sql/executor/snowflake.py +0 -145
- v0/relationalai/semantics/sql/rewrite/denormalize.py +0 -222
- v0/relationalai/semantics/sql/rewrite/double_negation.py +0 -49
- v0/relationalai/semantics/sql/rewrite/recursive_union.py +0 -127
- v0/relationalai/semantics/sql/rewrite/sort_output_query.py +0 -246
- v0/relationalai/semantics/sql/sql.py +0 -504
- v0/relationalai/semantics/std/__init__.py +0 -54
- v0/relationalai/semantics/std/constraints.py +0 -43
- v0/relationalai/semantics/std/datetime.py +0 -363
- v0/relationalai/semantics/std/decimals.py +0 -62
- v0/relationalai/semantics/std/floats.py +0 -7
- v0/relationalai/semantics/std/integers.py +0 -22
- v0/relationalai/semantics/std/math.py +0 -141
- v0/relationalai/semantics/std/pragmas.py +0 -11
- v0/relationalai/semantics/std/re.py +0 -83
- v0/relationalai/semantics/std/std.py +0 -14
- v0/relationalai/semantics/std/strings.py +0 -63
- v0/relationalai/semantics/tests/__init__.py +0 -0
- v0/relationalai/semantics/tests/test_snapshot_abstract.py +0 -143
- v0/relationalai/semantics/tests/test_snapshot_base.py +0 -9
- v0/relationalai/semantics/tests/utils.py +0 -46
- v0/relationalai/std/__init__.py +0 -70
- v0/relationalai/tools/__init__.py +0 -0
- v0/relationalai/tools/cli.py +0 -1940
- v0/relationalai/tools/cli_controls.py +0 -1826
- v0/relationalai/tools/cli_helpers.py +0 -390
- v0/relationalai/tools/debugger.py +0 -183
- v0/relationalai/tools/debugger_client.py +0 -109
- v0/relationalai/tools/debugger_server.py +0 -302
- v0/relationalai/tools/dev.py +0 -685
- v0/relationalai/tools/qb_debugger.py +0 -425
- v0/relationalai/util/clean_up_databases.py +0 -95
- v0/relationalai/util/format.py +0 -123
- v0/relationalai/util/list_databases.py +0 -9
- v0/relationalai/util/otel_configuration.py +0 -25
- v0/relationalai/util/otel_handler.py +0 -484
- v0/relationalai/util/snowflake_handler.py +0 -88
- v0/relationalai/util/span_format_test.py +0 -43
- v0/relationalai/util/span_tracker.py +0 -207
- v0/relationalai/util/spans_file_handler.py +0 -72
- v0/relationalai/util/tracing_handler.py +0 -34
- /relationalai/{semantics/frontend → analysis}/__init__.py +0 -0
- {v0/relationalai → relationalai}/analysis/mechanistic.py +0 -0
- {v0/relationalai → relationalai}/analysis/whynot.py +0 -0
- /relationalai/{shims → auth}/__init__.py +0 -0
- {v0/relationalai → relationalai}/auth/jwt_generator.py +0 -0
- {v0/relationalai → relationalai}/auth/oauth_callback_server.py +0 -0
- {v0/relationalai → relationalai}/auth/token_handler.py +0 -0
- {v0/relationalai → relationalai}/auth/util.py +0 -0
- {v0/relationalai/clients → relationalai/clients/resources/snowflake}/cache_store.py +0 -0
- {v0/relationalai → relationalai}/compiler.py +0 -0
- {v0/relationalai → relationalai}/dependencies.py +0 -0
- {v0/relationalai → relationalai}/docutils.py +0 -0
- {v0/relationalai/analysis → relationalai/early_access}/__init__.py +0 -0
- {v0/relationalai → relationalai}/early_access/dsl/__init__.py +0 -0
- {v0/relationalai/auth → relationalai/early_access/dsl/adapters}/__init__.py +0 -0
- {v0/relationalai/early_access → relationalai/early_access/dsl/adapters/orm}/__init__.py +0 -0
- {v0/relationalai → relationalai}/early_access/dsl/adapters/orm/model.py +0 -0
- {v0/relationalai/early_access/dsl/adapters → relationalai/early_access/dsl/adapters/owl}/__init__.py +0 -0
- {v0/relationalai → relationalai}/early_access/dsl/adapters/owl/model.py +0 -0
- {v0/relationalai/early_access/dsl/adapters/orm → relationalai/early_access/dsl/bindings}/__init__.py +0 -0
- {v0/relationalai/early_access/dsl/adapters/owl → relationalai/early_access/dsl/bindings/legacy}/__init__.py +0 -0
- {v0/relationalai/early_access/dsl/bindings → relationalai/early_access/dsl/codegen}/__init__.py +0 -0
- {v0/relationalai → relationalai}/early_access/dsl/constants.py +0 -0
- {v0/relationalai → relationalai}/early_access/dsl/core/__init__.py +0 -0
- {v0/relationalai → relationalai}/early_access/dsl/core/constraints/__init__.py +0 -0
- {v0/relationalai → relationalai}/early_access/dsl/core/constraints/predicate/__init__.py +0 -0
- {v0/relationalai → relationalai}/early_access/dsl/core/stack.py +0 -0
- {v0/relationalai/early_access/dsl/bindings/legacy → relationalai/early_access/dsl/core/temporal}/__init__.py +0 -0
- {v0/relationalai → relationalai}/early_access/dsl/core/utils.py +0 -0
- {v0/relationalai/early_access/dsl/codegen → relationalai/early_access/dsl/ir}/__init__.py +0 -0
- {v0/relationalai/early_access/dsl/core/temporal → relationalai/early_access/dsl/ontologies}/__init__.py +0 -0
- {v0/relationalai → relationalai}/early_access/dsl/ontologies/raw_source.py +0 -0
- {v0/relationalai/early_access/dsl/ir → relationalai/early_access/dsl/orm}/__init__.py +0 -0
- {v0/relationalai/early_access/dsl/ontologies → relationalai/early_access/dsl/orm/measures}/__init__.py +0 -0
- {v0/relationalai → relationalai}/early_access/dsl/orm/reasoner_errors.py +0 -0
- {v0/relationalai/early_access/dsl/orm → relationalai/early_access/dsl/physical_metadata}/__init__.py +0 -0
- {v0/relationalai/early_access/dsl/orm/measures → relationalai/early_access/dsl/serialize}/__init__.py +0 -0
- {v0/relationalai → relationalai}/early_access/dsl/serialize/binding_model.py +0 -0
- {v0/relationalai → relationalai}/early_access/dsl/serialize/model.py +0 -0
- {v0/relationalai/early_access/dsl/physical_metadata → relationalai/early_access/dsl/snow}/__init__.py +0 -0
- {v0/relationalai → relationalai}/early_access/tests/__init__.py +0 -0
- {v0/relationalai → relationalai}/environments/ci.py +0 -0
- {v0/relationalai → relationalai}/environments/hex.py +0 -0
- {v0/relationalai → relationalai}/environments/terminal.py +0 -0
- {v0/relationalai → relationalai}/experimental/__init__.py +0 -0
- {v0/relationalai → relationalai}/experimental/graphs.py +0 -0
- {v0/relationalai → relationalai}/experimental/paths/__init__.py +0 -0
- {v0/relationalai → relationalai}/experimental/paths/benchmarks/__init__.py +0 -0
- {v0/relationalai → relationalai}/experimental/paths/path_algorithms/__init__.py +0 -0
- {v0/relationalai → relationalai}/experimental/paths/rpq/__init__.py +0 -0
- {v0/relationalai → relationalai}/experimental/paths/rpq/filter.py +0 -0
- {v0/relationalai → relationalai}/experimental/paths/rpq/glushkov.py +0 -0
- {v0/relationalai → relationalai}/experimental/paths/rpq/transition.py +0 -0
- {v0/relationalai → relationalai}/experimental/paths/utilities/__init__.py +0 -0
- {v0/relationalai → relationalai}/experimental/paths/utilities/utilities.py +0 -0
- {v0/relationalai/early_access/dsl/serialize → relationalai/loaders}/__init__.py +0 -0
- {v0/relationalai → relationalai}/metagen.py +0 -0
- {v0/relationalai → relationalai}/metamodel.py +0 -0
- {v0/relationalai → relationalai}/rel.py +0 -0
- {v0/relationalai → relationalai}/semantics/devtools/__init__.py +0 -0
- {v0/relationalai → relationalai}/semantics/internal/__init__.py +0 -0
- {v0/relationalai → relationalai}/semantics/internal/annotations.py +0 -0
- {v0/relationalai → relationalai}/semantics/lqp/__init__.py +0 -0
- {v0/relationalai → relationalai}/semantics/lqp/ir.py +0 -0
- {v0/relationalai → relationalai}/semantics/lqp/pragmas.py +0 -0
- {v0/relationalai → relationalai}/semantics/lqp/rewrite/__init__.py +0 -0
- {v0/relationalai → relationalai}/semantics/metamodel/dataflow.py +0 -0
- {v0/relationalai → relationalai}/semantics/metamodel/ir.py +0 -0
- {v0/relationalai → relationalai}/semantics/metamodel/rewrite/__init__.py +0 -0
- {v0/relationalai → relationalai}/semantics/metamodel/typer/__init__.py +0 -0
- {v0/relationalai → relationalai}/semantics/metamodel/types.py +0 -0
- {v0/relationalai → relationalai}/semantics/metamodel/visitor.py +0 -0
- {v0/relationalai → relationalai}/semantics/reasoners/experimental/__init__.py +0 -0
- {v0/relationalai → relationalai}/semantics/rel/__init__.py +0 -0
- {v0/relationalai → relationalai}/semantics/sql/__init__.py +0 -0
- {v0/relationalai → relationalai}/semantics/sql/executor/__init__.py +0 -0
- {v0/relationalai → relationalai}/semantics/sql/rewrite/__init__.py +0 -0
- {v0/relationalai/early_access/dsl/snow → relationalai/semantics/tests}/__init__.py +0 -0
- {v0/relationalai → relationalai}/semantics/tests/logging.py +0 -0
- {v0/relationalai → relationalai}/std/aggregates.py +0 -0
- {v0/relationalai → relationalai}/std/dates.py +0 -0
- {v0/relationalai → relationalai}/std/graphs.py +0 -0
- {v0/relationalai → relationalai}/std/inspect.py +0 -0
- {v0/relationalai → relationalai}/std/math.py +0 -0
- {v0/relationalai → relationalai}/std/re.py +0 -0
- {v0/relationalai → relationalai}/std/strings.py +0 -0
- {v0/relationalai/loaders → relationalai/tools}/__init__.py +0 -0
- {v0/relationalai → relationalai}/tools/cleanup_snapshots.py +0 -0
- {v0/relationalai → relationalai}/tools/constants.py +0 -0
- {v0/relationalai → relationalai}/tools/query_utils.py +0 -0
- {v0/relationalai → relationalai}/tools/snapshot_viewer.py +0 -0
- {v0/relationalai → relationalai}/util/__init__.py +0 -0
- {v0/relationalai → relationalai}/util/constants.py +0 -0
- {v0/relationalai → relationalai}/util/graph.py +0 -0
- {v0/relationalai → relationalai}/util/timeout.py +0 -0
|
@@ -1,3785 +0,0 @@
|
|
|
1
|
-
from __future__ import annotations
|
|
2
|
-
from contextlib import contextmanager
|
|
3
|
-
from contextvars import ContextVar
|
|
4
|
-
from dataclasses import dataclass
|
|
5
|
-
from enum import Enum, EnumMeta
|
|
6
|
-
import re
|
|
7
|
-
from typing import Any, Optional, Sequence as PySequence, Type, TypedDict, cast
|
|
8
|
-
import itertools
|
|
9
|
-
import rich
|
|
10
|
-
import sys
|
|
11
|
-
|
|
12
|
-
from pandas import DataFrame
|
|
13
|
-
import numpy as np
|
|
14
|
-
import pandas as pd
|
|
15
|
-
from more_itertools import peekable
|
|
16
|
-
|
|
17
|
-
from v0.relationalai import debugging, errors
|
|
18
|
-
from v0.relationalai.environments.base import find_external_frame
|
|
19
|
-
from v0.relationalai.clients.config import Config
|
|
20
|
-
from v0.relationalai.util.otel_configuration import configure_otel
|
|
21
|
-
from v0.relationalai.clients.result_helpers import Int128Dtype
|
|
22
|
-
from v0.relationalai.semantics.metamodel import factory as f, helpers, ir, builtins, types
|
|
23
|
-
from v0.relationalai.semantics.metamodel.typer import typer
|
|
24
|
-
from v0.relationalai.semantics.metamodel.util import NameCache, OrderedSet, ordered_set, FrozenOrderedSet
|
|
25
|
-
from v0.relationalai.semantics.rel.executor import RelExecutor
|
|
26
|
-
from v0.relationalai.semantics.lqp.executor import LQPExecutor
|
|
27
|
-
from v0.relationalai.semantics.sql.executor import SnowflakeExecutor
|
|
28
|
-
from v0.relationalai.environments import runtime_env, SessionEnvironment
|
|
29
|
-
from collections import Counter, defaultdict
|
|
30
|
-
from snowflake.snowpark import Session, DataFrame as SnowparkDataFrame
|
|
31
|
-
|
|
32
|
-
from datetime import date, datetime
|
|
33
|
-
from decimal import Decimal as PyDecimal
|
|
34
|
-
|
|
35
|
-
#--------------------------------------------------
|
|
36
|
-
# Globals
|
|
37
|
-
#--------------------------------------------------
|
|
38
|
-
|
|
39
|
-
_global_id = peekable(itertools.count(0))
|
|
40
|
-
|
|
41
|
-
# Single context variable with default values
|
|
42
|
-
_overrides = ContextVar("overrides", default = {})
|
|
43
|
-
def overrides(key: str, default: bool | str | dict | datetime | None):
|
|
44
|
-
return _overrides.get().get(key, default)
|
|
45
|
-
|
|
46
|
-
# Flag that users set in the config or directly on the model, but that can still be
|
|
47
|
-
# overridden globally. Precedence is overrides > model argument > config.
|
|
48
|
-
def overridable_flag(key: str, config: Config, user_pref: bool | None, default: bool):
|
|
49
|
-
if user_pref is not None:
|
|
50
|
-
preferred = cast(bool, user_pref)
|
|
51
|
-
else:
|
|
52
|
-
preferred = cast(bool, config.get(key, default))
|
|
53
|
-
return overrides(key, preferred)
|
|
54
|
-
|
|
55
|
-
@contextmanager
|
|
56
|
-
def with_overrides(**kwargs):
|
|
57
|
-
token = _overrides.set({**_overrides.get(), **kwargs})
|
|
58
|
-
try:
|
|
59
|
-
yield
|
|
60
|
-
finally:
|
|
61
|
-
_overrides.reset(token)
|
|
62
|
-
|
|
63
|
-
# Intrinsic values to override for stable snapshots.
|
|
64
|
-
def get_intrinsic_overrides() -> dict[str, Any]:
|
|
65
|
-
datetime_now = overrides('datetime_now', None)
|
|
66
|
-
if datetime_now is not None:
|
|
67
|
-
return {'datetime_now': datetime_now}
|
|
68
|
-
return {}
|
|
69
|
-
|
|
70
|
-
#--------------------------------------------------
|
|
71
|
-
# Root tracking
|
|
72
|
-
#--------------------------------------------------
|
|
73
|
-
|
|
74
|
-
_track_default = True
|
|
75
|
-
_track_roots = ContextVar('track_roots', default=_track_default)
|
|
76
|
-
_global_roots = ordered_set()
|
|
77
|
-
|
|
78
|
-
def _add_root(root):
|
|
79
|
-
if _track_roots.get():
|
|
80
|
-
_global_roots.add(root)
|
|
81
|
-
|
|
82
|
-
def _remove_roots(items: PySequence[Producer|Fragment]):
|
|
83
|
-
for item in items:
|
|
84
|
-
if hasattr(item, "__hash__") and item.__hash__ and item in _global_roots:
|
|
85
|
-
_global_roots.remove(item)
|
|
86
|
-
|
|
87
|
-
# decorator
|
|
88
|
-
def roots(enabled=_track_default):
|
|
89
|
-
def decorator(func):
|
|
90
|
-
def wrapper(*args, **kwargs):
|
|
91
|
-
token = _track_roots.set(enabled)
|
|
92
|
-
try:
|
|
93
|
-
return func(*args, **kwargs)
|
|
94
|
-
finally:
|
|
95
|
-
_track_roots.reset(token)
|
|
96
|
-
return wrapper
|
|
97
|
-
return decorator
|
|
98
|
-
|
|
99
|
-
# with root_tracking(enabled=False): ...
|
|
100
|
-
@contextmanager
|
|
101
|
-
def root_tracking(enabled=_track_default):
|
|
102
|
-
token = _track_roots.set(enabled)
|
|
103
|
-
try:
|
|
104
|
-
yield
|
|
105
|
-
finally:
|
|
106
|
-
_track_roots.reset(token)
|
|
107
|
-
|
|
108
|
-
#--------------------------------------------------
|
|
109
|
-
# Helpers
|
|
110
|
-
#--------------------------------------------------
|
|
111
|
-
|
|
112
|
-
def default_dir(obj):
|
|
113
|
-
"""
|
|
114
|
-
This function returns the names of attributes from `__dict__` and relevant
|
|
115
|
-
parent's attributes. It's a simplification of what `dir()` does by default.
|
|
116
|
-
The intention is for `default_dir(obj)` to return what dir(obj) would
|
|
117
|
-
return if `obj.__dir__` was not defined.
|
|
118
|
-
|
|
119
|
-
There are some corner cases that are not handled because we don't need them,
|
|
120
|
-
e.g. obj being `None` or a type, or corner cases of `__slots__`.
|
|
121
|
-
"""
|
|
122
|
-
|
|
123
|
-
attributes = set()
|
|
124
|
-
|
|
125
|
-
# Add attributes from the object's __dict__ if it exists
|
|
126
|
-
if hasattr(obj, '__dict__'):
|
|
127
|
-
attributes.update(obj.__dict__.keys())
|
|
128
|
-
|
|
129
|
-
# Add attributes from class and base classes
|
|
130
|
-
if hasattr(obj, '__class__'):
|
|
131
|
-
for cls in obj.__class__.__mro__:
|
|
132
|
-
attributes.update(cls.__dict__.keys())
|
|
133
|
-
|
|
134
|
-
return attributes
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
def unwrap_list(item:Any) -> Any:
|
|
138
|
-
if isinstance(item, (list, tuple)) and len(item) == 1:
|
|
139
|
-
return item[0]
|
|
140
|
-
elif isinstance(item, (list, tuple)) and len(item) > 1:
|
|
141
|
-
raise ValueError(f"Expected a single item, got {len(item)}")
|
|
142
|
-
return item
|
|
143
|
-
|
|
144
|
-
def flatten(items:PySequence[Any], flatten_tuples=False) -> list[Any]:
|
|
145
|
-
flat = []
|
|
146
|
-
for item in items:
|
|
147
|
-
if isinstance(item, (list, tuple)) and (flatten_tuples or not isinstance(item, TupleArg)):
|
|
148
|
-
flat.extend(flatten(item, flatten_tuples=flatten_tuples))
|
|
149
|
-
else:
|
|
150
|
-
flat.append(item)
|
|
151
|
-
return flat
|
|
152
|
-
|
|
153
|
-
def find_subjects(items: PySequence[Producer]) -> set[Concept|Ref]:
|
|
154
|
-
subjects = set()
|
|
155
|
-
for item in items:
|
|
156
|
-
if isinstance(item, Concept):
|
|
157
|
-
subjects.add(item)
|
|
158
|
-
elif isinstance(item, ConceptExpression):
|
|
159
|
-
subjects.add(item._op)
|
|
160
|
-
elif isinstance(item, Expression):
|
|
161
|
-
subjects.update(find_subjects(item._params))
|
|
162
|
-
elif isinstance(item, Ref):
|
|
163
|
-
subjects.add(item)
|
|
164
|
-
elif isinstance(item, Relationship) and item._parent:
|
|
165
|
-
subjects.update(find_subjects([item._parent]))
|
|
166
|
-
return subjects
|
|
167
|
-
|
|
168
|
-
def to_type(item: Any) -> Concept|None:
|
|
169
|
-
if isinstance(item, Concept):
|
|
170
|
-
return item
|
|
171
|
-
elif isinstance(item, (Ref, Alias, TypeRef)):
|
|
172
|
-
return to_type(item._thing)
|
|
173
|
-
elif isinstance(item, ConceptExpression):
|
|
174
|
-
return to_type(item._op)
|
|
175
|
-
elif isinstance(item, Expression):
|
|
176
|
-
return to_type(item._params[-1])
|
|
177
|
-
|
|
178
|
-
def find_local(name:str) -> Any:
|
|
179
|
-
frame = find_external_frame()
|
|
180
|
-
if frame and name in frame.f_locals:
|
|
181
|
-
return frame.f_locals[name]
|
|
182
|
-
return None
|
|
183
|
-
|
|
184
|
-
def field_to_type(model:Model|None, field: Field) -> Concept:
|
|
185
|
-
if field.type is not None:
|
|
186
|
-
return field.type
|
|
187
|
-
type_str = field.type_str
|
|
188
|
-
if type_str in python_types_str_to_concepts:
|
|
189
|
-
return python_types_str_to_concepts[type_str]
|
|
190
|
-
elif model and type_str in model.concepts:
|
|
191
|
-
concepts = model.concepts[type_str]
|
|
192
|
-
if len(concepts) > 1:
|
|
193
|
-
# this can be expensive, but is only done if the type_str is ambiguous
|
|
194
|
-
found = find_local(type_str)
|
|
195
|
-
if found in set(concepts):
|
|
196
|
-
return found
|
|
197
|
-
if not found:
|
|
198
|
-
raise ValueError(f"Ambiguous reference to Concept '{type_str}'")
|
|
199
|
-
raise ValueError(f"Reference '{type_str}' is not a valid Concept")
|
|
200
|
-
return concepts[0]
|
|
201
|
-
elif type_str in Concept.builtins:
|
|
202
|
-
return Concept.builtins[type_str]
|
|
203
|
-
elif type_str.lower() in Concept.globals:
|
|
204
|
-
return Concept.globals[type_str.lower()]
|
|
205
|
-
elif found := find_local(type_str):
|
|
206
|
-
if isinstance(found, Concept):
|
|
207
|
-
return found
|
|
208
|
-
elif hasattr(found, "_to_type") and callable(found._to_type):
|
|
209
|
-
return cast(Concept, found._to_type())
|
|
210
|
-
raise ValueError(f"Reference '{type_str}' is not a valid Concept")
|
|
211
|
-
elif type_str.startswith("Decimal"):
|
|
212
|
-
return decimal_concept_by_name(type_str)
|
|
213
|
-
else:
|
|
214
|
-
return Concept.builtins["Any"]
|
|
215
|
-
|
|
216
|
-
def to_name(item:Any) -> str:
|
|
217
|
-
if isinstance(item, Relationship) and isinstance(item._parent, Concept):
|
|
218
|
-
return f"{item._parent._name}_{item._name}"
|
|
219
|
-
elif isinstance(item, (Ref, Alias)):
|
|
220
|
-
return item._name or to_name(item._thing)
|
|
221
|
-
elif isinstance(item, RelationshipRef):
|
|
222
|
-
return item._relationship._name
|
|
223
|
-
elif isinstance(item, ConceptExpression):
|
|
224
|
-
return item._op._name.lower()
|
|
225
|
-
elif isinstance(item, Concept):
|
|
226
|
-
return item._name.lower()
|
|
227
|
-
return getattr(item, "_name", "v")
|
|
228
|
-
|
|
229
|
-
def find_model(items: Any) -> Model|None:
|
|
230
|
-
if isinstance(items, (list, tuple)):
|
|
231
|
-
for item in items:
|
|
232
|
-
model = find_model(item)
|
|
233
|
-
if model:
|
|
234
|
-
return model
|
|
235
|
-
elif isinstance(items, dict):
|
|
236
|
-
for item in items.values():
|
|
237
|
-
model = find_model(item)
|
|
238
|
-
if model:
|
|
239
|
-
return model
|
|
240
|
-
else:
|
|
241
|
-
if hasattr(items, "_model") and items._model:
|
|
242
|
-
return items._model
|
|
243
|
-
return None
|
|
244
|
-
|
|
245
|
-
def with_source(item:Any):
|
|
246
|
-
if not hasattr(item, "_source"):
|
|
247
|
-
raise ValueError(f"Item {item} has no source")
|
|
248
|
-
elif item._source is None:
|
|
249
|
-
return {}
|
|
250
|
-
elif debugging.DEBUG:
|
|
251
|
-
source = item._source.to_source_info()
|
|
252
|
-
if source:
|
|
253
|
-
return { "file": source.file, "line": source.line, "source": source.source }
|
|
254
|
-
else:
|
|
255
|
-
return {"file":item._source.file, "line":item._source.line}
|
|
256
|
-
else:
|
|
257
|
-
return {"file":item._source.file, "line":item._source.line}
|
|
258
|
-
|
|
259
|
-
def has_keys(item: Any) -> bool:
|
|
260
|
-
try:
|
|
261
|
-
return bool(len(find_keys(item)))
|
|
262
|
-
except Exception:
|
|
263
|
-
return False
|
|
264
|
-
|
|
265
|
-
def find_keys(item: Any, keys:OrderedSet[Any]|None = None) -> OrderedSet[Any]:
|
|
266
|
-
if keys is None:
|
|
267
|
-
keys = ordered_set()
|
|
268
|
-
|
|
269
|
-
if isinstance(item, (list, tuple)):
|
|
270
|
-
for it in item:
|
|
271
|
-
find_keys(it, keys)
|
|
272
|
-
|
|
273
|
-
elif isinstance(item, (Relationship, RelationshipReading)) and item._parent:
|
|
274
|
-
find_keys(item._parent, keys)
|
|
275
|
-
if item.is_many():
|
|
276
|
-
keys.add(item._field_refs[-1])
|
|
277
|
-
|
|
278
|
-
elif isinstance(item, RelationshipRef):
|
|
279
|
-
find_keys(item._parent, keys)
|
|
280
|
-
if item._relationship.is_many():
|
|
281
|
-
keys.add(item._field_refs[-1])
|
|
282
|
-
|
|
283
|
-
elif isinstance(item, (Relationship, RelationshipReading, Property)):
|
|
284
|
-
if item.is_many():
|
|
285
|
-
keys.update(item._field_refs)
|
|
286
|
-
else:
|
|
287
|
-
keys.add(item._field_refs[0])
|
|
288
|
-
|
|
289
|
-
elif isinstance(item, Concept):
|
|
290
|
-
if not item._is_primitive():
|
|
291
|
-
keys.add(item)
|
|
292
|
-
|
|
293
|
-
elif isinstance(item, ConceptExpression):
|
|
294
|
-
for it in item._params[1].values():
|
|
295
|
-
find_keys(it, keys)
|
|
296
|
-
|
|
297
|
-
elif isinstance(item, Ref):
|
|
298
|
-
if isinstance(item._thing, Concept):
|
|
299
|
-
if not item._thing._is_primitive():
|
|
300
|
-
keys.add(item)
|
|
301
|
-
else:
|
|
302
|
-
find_keys(item._thing, keys)
|
|
303
|
-
|
|
304
|
-
elif isinstance(item, RelationshipFieldRef):
|
|
305
|
-
find_keys(item._relationship, keys)
|
|
306
|
-
|
|
307
|
-
elif isinstance(item, ArgumentRef):
|
|
308
|
-
find_keys(item._arg, keys)
|
|
309
|
-
|
|
310
|
-
elif isinstance(item, TypeRef):
|
|
311
|
-
find_keys(item._thing, keys)
|
|
312
|
-
|
|
313
|
-
elif isinstance(item, Alias):
|
|
314
|
-
find_keys(item._thing, keys)
|
|
315
|
-
|
|
316
|
-
elif isinstance(item, Aggregate):
|
|
317
|
-
keys.update(item._group)
|
|
318
|
-
|
|
319
|
-
elif isinstance(item, Expression):
|
|
320
|
-
find_keys(item._params, keys)
|
|
321
|
-
|
|
322
|
-
elif isinstance(item, Data):
|
|
323
|
-
keys.add(item._row_id)
|
|
324
|
-
|
|
325
|
-
elif isinstance(item, DataColumn):
|
|
326
|
-
keys.add(item._data)
|
|
327
|
-
|
|
328
|
-
elif isinstance(item, BranchRef):
|
|
329
|
-
find_keys(item._match, keys)
|
|
330
|
-
|
|
331
|
-
elif isinstance(item, Match):
|
|
332
|
-
pass
|
|
333
|
-
elif isinstance(item, Distinct):
|
|
334
|
-
pass
|
|
335
|
-
elif isinstance(item, PY_LITERAL_TYPES):
|
|
336
|
-
pass
|
|
337
|
-
elif hasattr(item, "_to_keys"):
|
|
338
|
-
keys.update(item._to_keys())
|
|
339
|
-
else:
|
|
340
|
-
raise ValueError(f"Cannot find keys for {item}")
|
|
341
|
-
|
|
342
|
-
return keys
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
class Key:
|
|
346
|
-
def __init__(self, val:Any, is_group:bool = False):
|
|
347
|
-
self.val = val
|
|
348
|
-
self.is_group = is_group
|
|
349
|
-
|
|
350
|
-
def find_select_keys(item: Any, keys:OrderedSet[Key]|None = None, enable_primitive_key:bool = False) -> OrderedSet[Key]:
|
|
351
|
-
if keys is None:
|
|
352
|
-
keys = ordered_set()
|
|
353
|
-
|
|
354
|
-
if isinstance(item, (list, tuple)):
|
|
355
|
-
for it in item:
|
|
356
|
-
find_select_keys(it, keys, enable_primitive_key=enable_primitive_key)
|
|
357
|
-
|
|
358
|
-
elif isinstance(item, (Relationship, RelationshipReading)) and item._parent:
|
|
359
|
-
find_select_keys(item._parent, keys)
|
|
360
|
-
if item.is_many():
|
|
361
|
-
keys.add( Key(item._field_refs[-1]) )
|
|
362
|
-
|
|
363
|
-
elif isinstance(item, RelationshipRef):
|
|
364
|
-
find_select_keys(item._parent, keys)
|
|
365
|
-
if item._relationship.is_many():
|
|
366
|
-
keys.add( Key(item._field_refs[-1]) )
|
|
367
|
-
|
|
368
|
-
elif isinstance(item, (Relationship, RelationshipReading)):
|
|
369
|
-
if item.is_many():
|
|
370
|
-
for fld in item._field_refs:
|
|
371
|
-
keys.add( Key(fld) )
|
|
372
|
-
else:
|
|
373
|
-
keys.add( Key(item._field_refs[0]) )
|
|
374
|
-
|
|
375
|
-
elif isinstance(item, Concept):
|
|
376
|
-
if not item._is_primitive() or enable_primitive_key:
|
|
377
|
-
keys.add( Key(item) )
|
|
378
|
-
|
|
379
|
-
elif isinstance(item, ConceptExpression):
|
|
380
|
-
for it in item._params[1].values():
|
|
381
|
-
find_select_keys(it, keys)
|
|
382
|
-
|
|
383
|
-
elif isinstance(item, Ref):
|
|
384
|
-
if isinstance(item._thing, Concept):
|
|
385
|
-
if not item._thing._is_primitive() or enable_primitive_key:
|
|
386
|
-
keys.add( Key(item) )
|
|
387
|
-
else:
|
|
388
|
-
find_select_keys(item._thing, keys)
|
|
389
|
-
|
|
390
|
-
elif isinstance(item, TypeRef):
|
|
391
|
-
pass
|
|
392
|
-
|
|
393
|
-
elif isinstance(item, RelationshipFieldRef):
|
|
394
|
-
find_select_keys(item._relationship, keys)
|
|
395
|
-
|
|
396
|
-
elif isinstance(item, ArgumentRef):
|
|
397
|
-
find_select_keys(item._arg, keys)
|
|
398
|
-
|
|
399
|
-
elif isinstance(item, Alias):
|
|
400
|
-
find_select_keys(item._thing, keys, enable_primitive_key=enable_primitive_key)
|
|
401
|
-
|
|
402
|
-
elif isinstance(item, Aggregate):
|
|
403
|
-
keys.update( Key(k, True) for k in item._group )
|
|
404
|
-
|
|
405
|
-
elif isinstance(item, Expression):
|
|
406
|
-
find_select_keys(item._params, keys)
|
|
407
|
-
|
|
408
|
-
elif isinstance(item, Data):
|
|
409
|
-
keys.add( Key(item._row_id) )
|
|
410
|
-
|
|
411
|
-
elif isinstance(item, DataColumn):
|
|
412
|
-
keys.add( Key(item._data) )
|
|
413
|
-
|
|
414
|
-
elif isinstance(item, BranchRef):
|
|
415
|
-
find_select_keys(item._match, keys)
|
|
416
|
-
|
|
417
|
-
elif isinstance(item, Match):
|
|
418
|
-
pass
|
|
419
|
-
elif isinstance(item, Distinct):
|
|
420
|
-
pass
|
|
421
|
-
elif isinstance(item, PY_LITERAL_TYPES):
|
|
422
|
-
pass
|
|
423
|
-
elif hasattr(item, "_to_keys"):
|
|
424
|
-
for sub in item._to_keys():
|
|
425
|
-
find_select_keys(sub, keys)
|
|
426
|
-
else:
|
|
427
|
-
raise ValueError(f"Cannot find keys for {item}")
|
|
428
|
-
|
|
429
|
-
return keys
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
#--------------------------------------------------
|
|
433
|
-
# Producer
|
|
434
|
-
#--------------------------------------------------
|
|
435
|
-
|
|
436
|
-
class Producer:
|
|
437
|
-
def __init__(self, model:Model|None) -> None:
|
|
438
|
-
self._id = next(_global_id)
|
|
439
|
-
self._model = model
|
|
440
|
-
|
|
441
|
-
#--------------------------------------------------
|
|
442
|
-
# Infix operator overloads
|
|
443
|
-
#--------------------------------------------------
|
|
444
|
-
|
|
445
|
-
def _bin_op(self, op, left, right) -> Expression:
|
|
446
|
-
res = Number.ref("res")
|
|
447
|
-
return Expression(Relationship.builtins[op], left, right, res)
|
|
448
|
-
|
|
449
|
-
def __add__(self, other):
|
|
450
|
-
return self._bin_op("+", self, other)
|
|
451
|
-
def __radd__(self, other):
|
|
452
|
-
return self._bin_op("+", other, self)
|
|
453
|
-
|
|
454
|
-
def __mul__(self, other):
|
|
455
|
-
return self._bin_op("*", self, other)
|
|
456
|
-
def __rmul__(self, other):
|
|
457
|
-
return self._bin_op("*", other, self)
|
|
458
|
-
|
|
459
|
-
def __sub__(self, other):
|
|
460
|
-
return self._bin_op("-", self, other)
|
|
461
|
-
def __rsub__(self, other):
|
|
462
|
-
return self._bin_op("-", other, self)
|
|
463
|
-
|
|
464
|
-
def __truediv__(self, other):
|
|
465
|
-
return self._bin_op("/", self, other)
|
|
466
|
-
def __rtruediv__(self, other):
|
|
467
|
-
return self._bin_op("/", other, self)
|
|
468
|
-
|
|
469
|
-
def __floordiv__(self, other):
|
|
470
|
-
return self._bin_op("//", self, other)
|
|
471
|
-
def __rfloordiv__(self, other):
|
|
472
|
-
return self._bin_op("//", other, self)
|
|
473
|
-
|
|
474
|
-
def __pow__(self, other):
|
|
475
|
-
return self._bin_op("^", self, other)
|
|
476
|
-
def __rpow__(self, other):
|
|
477
|
-
return self._bin_op("^", other, self)
|
|
478
|
-
|
|
479
|
-
def __mod__(self, other):
|
|
480
|
-
return self._bin_op("%", self, other)
|
|
481
|
-
def __rmod__(self, other):
|
|
482
|
-
return self._bin_op("%", other, self)
|
|
483
|
-
|
|
484
|
-
def __neg__(self):
|
|
485
|
-
return self._bin_op("*", self, -1)
|
|
486
|
-
|
|
487
|
-
#--------------------------------------------------
|
|
488
|
-
# Filter overloads
|
|
489
|
-
#--------------------------------------------------
|
|
490
|
-
|
|
491
|
-
def _filter(self, op, left, right) -> Expression:
|
|
492
|
-
return Expression(Relationship.builtins[op], left, right)
|
|
493
|
-
|
|
494
|
-
def __gt__(self, other):
|
|
495
|
-
return self._filter(">", self, other)
|
|
496
|
-
def __ge__(self, other):
|
|
497
|
-
return self._filter(">=", self, other)
|
|
498
|
-
def __lt__(self, other):
|
|
499
|
-
return self._filter("<", self, other)
|
|
500
|
-
def __le__(self, other):
|
|
501
|
-
return self._filter("<=", self, other)
|
|
502
|
-
def __eq__(self, other) -> Any:
|
|
503
|
-
return self._filter("=", self, other)
|
|
504
|
-
def __ne__(self, other) -> Any:
|
|
505
|
-
return self._filter("!=", self, other)
|
|
506
|
-
|
|
507
|
-
#--------------------------------------------------
|
|
508
|
-
# And/Or
|
|
509
|
-
#--------------------------------------------------
|
|
510
|
-
|
|
511
|
-
def __or__(self, other) -> Match:
|
|
512
|
-
return Match(self, other)
|
|
513
|
-
|
|
514
|
-
def __and__(self, other) -> Fragment:
|
|
515
|
-
if isinstance(other, Fragment):
|
|
516
|
-
return other.where(self)
|
|
517
|
-
return where(self, other)
|
|
518
|
-
|
|
519
|
-
#--------------------------------------------------
|
|
520
|
-
# in_
|
|
521
|
-
#--------------------------------------------------
|
|
522
|
-
|
|
523
|
-
def in_(self, values:list[Any]|Fragment) -> Expression:
|
|
524
|
-
columns = None
|
|
525
|
-
if isinstance(values, Fragment):
|
|
526
|
-
return self == values
|
|
527
|
-
if not isinstance(values[0], tuple):
|
|
528
|
-
values = [tuple([v]) for v in values]
|
|
529
|
-
columns = [f"v{i}" for i in range(len(values[0]))]
|
|
530
|
-
d = data(values, columns)
|
|
531
|
-
return self == d[0]
|
|
532
|
-
|
|
533
|
-
#--------------------------------------------------
|
|
534
|
-
# Relationship handling
|
|
535
|
-
#--------------------------------------------------
|
|
536
|
-
|
|
537
|
-
def _get_relationship(self, name:str) -> Relationship|RelationshipRef|RelationshipFieldRef:
|
|
538
|
-
root_type:Concept = to_type(self) or Concept.builtins["Any"]
|
|
539
|
-
namer = NameCache()
|
|
540
|
-
cls = Relationship if root_type is Error else Property
|
|
541
|
-
|
|
542
|
-
r = cls(
|
|
543
|
-
f"{{{root_type}}} has {{{name}:Any}}",
|
|
544
|
-
parent=self,
|
|
545
|
-
short_name=name,
|
|
546
|
-
model=self._model,
|
|
547
|
-
field_refs=cast(list[Ref], [
|
|
548
|
-
root_type.ref(namer.get_name(1, Relationship._sanitize_field_name(root_type._name))),
|
|
549
|
-
Concept.builtins["Any"].ref(namer.get_name(2, name)),
|
|
550
|
-
]),
|
|
551
|
-
)
|
|
552
|
-
# if we don't know the root type, then this relationship is unresolved and we're
|
|
553
|
-
# really just handing an anonymous relationship back that we expect to be resolved
|
|
554
|
-
# later
|
|
555
|
-
if root_type is Concept.builtins["Any"]:
|
|
556
|
-
r._unresolved = True
|
|
557
|
-
return r
|
|
558
|
-
|
|
559
|
-
#--------------------------------------------------
|
|
560
|
-
# dir and helpers
|
|
561
|
-
#--------------------------------------------------
|
|
562
|
-
|
|
563
|
-
def _dir_extras_from_get_relationship(self):
|
|
564
|
-
# Producer._get_relationship() does not distinguish on
|
|
565
|
-
# new/pre-existing, so we return nothing.
|
|
566
|
-
return set()
|
|
567
|
-
|
|
568
|
-
def _dir_extras_from_getattr(self):
|
|
569
|
-
"""
|
|
570
|
-
Helper function for computing `__dir__`
|
|
571
|
-
|
|
572
|
-
See Also
|
|
573
|
-
--------
|
|
574
|
-
:meth:`Producer.__dir__`
|
|
575
|
-
"""
|
|
576
|
-
attributes = set()
|
|
577
|
-
relationships = getattr(self, "_relationships", None)
|
|
578
|
-
if relationships is not None and isinstance(relationships, dict):
|
|
579
|
-
attributes.update(relationships.keys())
|
|
580
|
-
|
|
581
|
-
attributes.update(self._dir_extras_from_get_relationship())
|
|
582
|
-
|
|
583
|
-
return attributes
|
|
584
|
-
|
|
585
|
-
def __dir__(self):
|
|
586
|
-
"""
|
|
587
|
-
This method provides hints runtime autocompletion in Jupyter, IPython or similar,
|
|
588
|
-
see https://docs.python.org/3/library/functions.html#dir.
|
|
589
|
-
|
|
590
|
-
Our implementation works as follows. We get the "hardcoded" attributes using
|
|
591
|
-
`default_dir`. For the dynamic ones, which are provided via `__getattr__`, we implement
|
|
592
|
-
`_dir_extras_from_getattr`, the idea being that the latter returns the set of strings
|
|
593
|
-
that are a "sensible" input to the former. The former often accepts any string, but
|
|
594
|
-
some values would than fail during compilation, so we mimic the behaviour of
|
|
595
|
-
`__getattr__` up to the point where it starts creating new objects. The `__getattr__`
|
|
596
|
-
often calls `_get_relationship`, due to the virtual dispatch of Python that can belong
|
|
597
|
-
to a different static type (parent or child) than the executed `__getattr__`. Hence,
|
|
598
|
-
to closely mimic the behaviour, we also define `_dir_extras_from_get_relationship`
|
|
599
|
-
which again returns the "sensible" inputs to `_get_relationship`.
|
|
600
|
-
"""
|
|
601
|
-
|
|
602
|
-
return sorted(default_dir(self).union(self._dir_extras_from_getattr()))
|
|
603
|
-
|
|
604
|
-
#--------------------------------------------------
|
|
605
|
-
# getattr
|
|
606
|
-
#--------------------------------------------------
|
|
607
|
-
|
|
608
|
-
def __getattr__(self, name:str) -> Any:
|
|
609
|
-
if name.startswith("_"):
|
|
610
|
-
raise AttributeError(f"{type(self).__name__} has no attribute {name}")
|
|
611
|
-
if not hasattr(self, "_relationships"):
|
|
612
|
-
return super().__getattribute__(name)
|
|
613
|
-
|
|
614
|
-
if isinstance(self, (Concept, ConceptNew)):
|
|
615
|
-
concept = self._op if isinstance(self, ConceptNew) else self
|
|
616
|
-
topmost_parent = concept._get_topmost_parent()
|
|
617
|
-
if (concept is not Concept.builtins['Any'] and
|
|
618
|
-
not concept._is_enum() and
|
|
619
|
-
name not in concept._relationships and
|
|
620
|
-
not concept._has_inherited_relationship(name)):
|
|
621
|
-
|
|
622
|
-
if self._model and self._model._strict:
|
|
623
|
-
raise AttributeError(f"{self._name} has no relationship `{name}`")
|
|
624
|
-
if topmost_parent is not concept and topmost_parent not in Concept.builtin_concepts:
|
|
625
|
-
topmost_parent._relationships[name] = topmost_parent._get_relationship(name)
|
|
626
|
-
rich.print(f"[red bold][Implicit Subtype Relationship][/red bold] [yellow]{concept}.{name}[/yellow] appended to topmost parent [yellow]{topmost_parent}[/yellow] instead")
|
|
627
|
-
|
|
628
|
-
if name not in self._relationships:
|
|
629
|
-
self._relationships[name] = self._get_relationship(name)
|
|
630
|
-
return self._relationships[name]
|
|
631
|
-
|
|
632
|
-
def _has_inherited_relationship(self, name:str) -> bool:
|
|
633
|
-
if isinstance(self, Concept):
|
|
634
|
-
for parent in self._extends:
|
|
635
|
-
if not parent._is_primitive():
|
|
636
|
-
if parent._has_relationship(name):
|
|
637
|
-
return True
|
|
638
|
-
return False
|
|
639
|
-
|
|
640
|
-
def _has_relationship(self, name:str) -> bool:
|
|
641
|
-
if name in self._relationships:
|
|
642
|
-
return True
|
|
643
|
-
return self._has_inherited_relationship(name)
|
|
644
|
-
|
|
645
|
-
def __setattr__(self, name: str, value: Any) -> None:
|
|
646
|
-
if name.startswith("_"):
|
|
647
|
-
super().__setattr__(name, value)
|
|
648
|
-
elif isinstance(value, (Relationship, RelationshipReading)):
|
|
649
|
-
value._parent = self
|
|
650
|
-
if not value._passed_short_name:
|
|
651
|
-
value._passed_short_name = name
|
|
652
|
-
if name in self._relationships:
|
|
653
|
-
raise ValueError(f"Cannot set attribute {name} on {type(self).__name__} a second time. Make sure to set the relationship before any usages occur")
|
|
654
|
-
# update the first reading created implicitly
|
|
655
|
-
if isinstance(value, Relationship):
|
|
656
|
-
value._readings[0]._parent = value._parent
|
|
657
|
-
value._readings[0]._passed_short_name = value._passed_short_name
|
|
658
|
-
self._relationships[name] = value
|
|
659
|
-
else:
|
|
660
|
-
raise AttributeError(f"Cannot set attribute {name} on {type(self).__name__}")
|
|
661
|
-
|
|
662
|
-
#--------------------------------------------------
|
|
663
|
-
# ref + alias
|
|
664
|
-
#--------------------------------------------------
|
|
665
|
-
|
|
666
|
-
def ref(self, name:str|None=None) -> Ref|RelationshipRef:
|
|
667
|
-
return Ref(self, name=name)
|
|
668
|
-
|
|
669
|
-
def alias(self, name:str) -> Alias:
|
|
670
|
-
return Alias(self, name)
|
|
671
|
-
|
|
672
|
-
#--------------------------------------------------
|
|
673
|
-
# Find model
|
|
674
|
-
#--------------------------------------------------
|
|
675
|
-
|
|
676
|
-
def _find_model(self, items:list[Any]) -> Model|None:
|
|
677
|
-
if self._model:
|
|
678
|
-
return self._model
|
|
679
|
-
|
|
680
|
-
for item in items:
|
|
681
|
-
if isinstance(item, (Producer, Fragment)) and item._model:
|
|
682
|
-
self._model = item._model
|
|
683
|
-
return item._model
|
|
684
|
-
return None
|
|
685
|
-
|
|
686
|
-
#--------------------------------------------------
|
|
687
|
-
# Hash
|
|
688
|
-
#--------------------------------------------------
|
|
689
|
-
|
|
690
|
-
__hash__ = object.__hash__
|
|
691
|
-
|
|
692
|
-
#--------------------------------------------------
|
|
693
|
-
# _pprint
|
|
694
|
-
#--------------------------------------------------
|
|
695
|
-
|
|
696
|
-
def _pprint(self, indent:int=0) -> str:
|
|
697
|
-
return str(self)
|
|
698
|
-
|
|
699
|
-
#--------------------------------------------------
|
|
700
|
-
# Fallbacks
|
|
701
|
-
#--------------------------------------------------
|
|
702
|
-
|
|
703
|
-
def select(self, *args: Any):
|
|
704
|
-
raise NotImplementedError(f"`{type(self).__name__}.select` not implemented")
|
|
705
|
-
|
|
706
|
-
def where(self, *args: Any):
|
|
707
|
-
raise NotImplementedError(f"`{type(self).__name__}.where` not implemented")
|
|
708
|
-
|
|
709
|
-
def require(self, *args: Any):
|
|
710
|
-
raise NotImplementedError(f"`{type(self).__name__}.require` not implemented")
|
|
711
|
-
|
|
712
|
-
def define(self, *args: Any):
|
|
713
|
-
raise NotImplementedError(f"`{type(self).__name__}.then` not implemented")
|
|
714
|
-
|
|
715
|
-
#--------------------------------------------------
|
|
716
|
-
# Ref
|
|
717
|
-
#--------------------------------------------------
|
|
718
|
-
|
|
719
|
-
class Ref(Producer):
|
|
720
|
-
def __init__(self, thing:Producer, name:str|None=None):
|
|
721
|
-
super().__init__(thing._model)
|
|
722
|
-
self._thing = thing
|
|
723
|
-
self._name = name
|
|
724
|
-
self._no_lookup = False
|
|
725
|
-
self._relationships = {}
|
|
726
|
-
|
|
727
|
-
def _dir_extras_from_get_relationship(self):
|
|
728
|
-
return self._thing._dir_extras_from_getattr()
|
|
729
|
-
|
|
730
|
-
def _get_relationship(self, name: str) -> Relationship | RelationshipRef:
|
|
731
|
-
rel = getattr(self._thing, name)
|
|
732
|
-
return RelationshipRef(self, rel)
|
|
733
|
-
|
|
734
|
-
def __str__(self) -> str:
|
|
735
|
-
if self._name:
|
|
736
|
-
return f"{self._name}{self._id}"
|
|
737
|
-
return f"{self._thing}{self._id}"
|
|
738
|
-
|
|
739
|
-
class TypeRef(Producer):
|
|
740
|
-
""" A reference to the type of a Producer. """
|
|
741
|
-
|
|
742
|
-
def __init__(self, thing:Producer):
|
|
743
|
-
super().__init__(thing._model)
|
|
744
|
-
self._thing = thing
|
|
745
|
-
|
|
746
|
-
def __str__(self) -> str:
|
|
747
|
-
return f"typeof({self._thing})"
|
|
748
|
-
|
|
749
|
-
class ArgumentRef(Producer):
|
|
750
|
-
""" Represents a reference to an argument of an Expression.
|
|
751
|
-
Useful when you need to reuse arguments in another Expression
|
|
752
|
-
while maintaining a link to the original Expression during compilation.
|
|
753
|
-
"""
|
|
754
|
-
|
|
755
|
-
def __init__(self, expr:Expression, arg:Producer):
|
|
756
|
-
super().__init__(expr._model)
|
|
757
|
-
self._expr = expr
|
|
758
|
-
self._arg = arg
|
|
759
|
-
|
|
760
|
-
def __str__(self) -> str:
|
|
761
|
-
return f"{self._arg}{self._id}"
|
|
762
|
-
|
|
763
|
-
class RelationshipRef(Producer):
|
|
764
|
-
def __init__(self, parent:Any, relationship:Relationship|RelationshipRef, name:str|None=None):
|
|
765
|
-
super().__init__(find_model([parent, relationship]))
|
|
766
|
-
self._parent = parent
|
|
767
|
-
if isinstance(relationship, RelationshipRef):
|
|
768
|
-
relationship = relationship._relationship
|
|
769
|
-
self._relationship:Relationship = relationship
|
|
770
|
-
self._field_refs = [r.ref() for r in relationship._field_refs]
|
|
771
|
-
if name:
|
|
772
|
-
self._field_refs[-1].name = name
|
|
773
|
-
self._relationships = {}
|
|
774
|
-
|
|
775
|
-
def _dir_extras_from_get_relationship(self):
|
|
776
|
-
return self._relationship._dir_extras_from_getattr()
|
|
777
|
-
|
|
778
|
-
def _get_relationship(self, name: str) -> Relationship|RelationshipRef|RelationshipFieldRef:
|
|
779
|
-
rel = self._relationship._get_relationship(name)
|
|
780
|
-
if isinstance(rel, Relationship):
|
|
781
|
-
return RelationshipRef(self, rel)
|
|
782
|
-
elif isinstance(rel, RelationshipFieldRef):
|
|
783
|
-
return RelationshipFieldRef(self, rel._relationship, rel._field_ix)
|
|
784
|
-
else:
|
|
785
|
-
return RelationshipRef(self, rel)
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
def __call__(self, *args: Any, **kwargs) -> Any:
|
|
789
|
-
if kwargs and args:
|
|
790
|
-
raise ValueError("Cannot use both positional and keyword arguments")
|
|
791
|
-
if kwargs:
|
|
792
|
-
# check that all fields have been provided
|
|
793
|
-
clean_args = []
|
|
794
|
-
for ix, field in enumerate(self._relationship._field_names):
|
|
795
|
-
if field in kwargs:
|
|
796
|
-
clean_args.append(kwargs.get(field))
|
|
797
|
-
if ix == 0 and self._parent:
|
|
798
|
-
continue
|
|
799
|
-
if field not in kwargs:
|
|
800
|
-
raise ValueError(f"Missing argument {field}")
|
|
801
|
-
else:
|
|
802
|
-
clean_args = list(args)
|
|
803
|
-
if len(clean_args) < self._relationship._arity():
|
|
804
|
-
if self._parent:
|
|
805
|
-
clean_args = [self._parent, *clean_args]
|
|
806
|
-
if len(clean_args) != self._relationship._arity():
|
|
807
|
-
raise ValueError(f"Expected {self._relationship._arity()} arguments, got {len(clean_args)}")
|
|
808
|
-
return Expression(self._relationship, *clean_args)
|
|
809
|
-
|
|
810
|
-
def __str__(self) -> str:
|
|
811
|
-
return f"{self._parent}.{self._relationship._short_name}"
|
|
812
|
-
|
|
813
|
-
class RelationshipFieldRef(Producer):
|
|
814
|
-
def __init__(self, parent:Any, relationship:Relationship|RelationshipRef|RelationshipReading, field_ix:int):
|
|
815
|
-
super().__init__(find_model([relationship]))
|
|
816
|
-
self._parent = parent
|
|
817
|
-
if isinstance(relationship, RelationshipRef):
|
|
818
|
-
relationship = relationship._relationship
|
|
819
|
-
self._relationship:Relationship|RelationshipReading = relationship
|
|
820
|
-
self._field_ix = field_ix
|
|
821
|
-
self._relationships = {}
|
|
822
|
-
|
|
823
|
-
@property
|
|
824
|
-
def _field_ref(self) -> Ref|RelationshipRef:
|
|
825
|
-
return self._relationship._field_refs[self._field_ix]
|
|
826
|
-
|
|
827
|
-
@property
|
|
828
|
-
def _concept(self) -> Concept:
|
|
829
|
-
return field_to_type(self._model, self._relationship._fields[self._field_ix])
|
|
830
|
-
|
|
831
|
-
def _dir_extras_from_get_relationship(self):
|
|
832
|
-
return self._field_ref._dir_extras_from_getattr()
|
|
833
|
-
|
|
834
|
-
def _get_relationship(self, name: str) -> Relationship | RelationshipRef:
|
|
835
|
-
rel = getattr(self._field_ref, name)
|
|
836
|
-
return RelationshipRef(self, rel)
|
|
837
|
-
|
|
838
|
-
def __call__(self, arg: Any) -> Any:
|
|
839
|
-
return self == arg
|
|
840
|
-
|
|
841
|
-
def __str__(self) -> str:
|
|
842
|
-
return f"{self._relationship}.{self._field_ref}"
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
#--------------------------------------------------
|
|
846
|
-
# typed dicts for annotating ref scheme hierarchy
|
|
847
|
-
#--------------------------------------------------
|
|
848
|
-
|
|
849
|
-
# We define a hierarchy of two dicts, one allowing its key(s) to be ommitted,
|
|
850
|
-
# and another which extends the first with mandatory keys.
|
|
851
|
-
# Up until Python 3.10, this is the only way to create a TypedDict
|
|
852
|
-
# which contains both mandatory and non-mandatory keys.
|
|
853
|
-
# Once we stop supporting Python 3.10, this can
|
|
854
|
-
# be simplified by using Required and NotRequired
|
|
855
|
-
# https://peps.python.org/pep-0655/#motivation
|
|
856
|
-
|
|
857
|
-
class RefSchemeHierarchyParentDict(TypedDict, total=False):
|
|
858
|
-
mapping: Relationship
|
|
859
|
-
class RefSchemeHierarchyDict(RefSchemeHierarchyParentDict, total=True):
|
|
860
|
-
concept: Concept
|
|
861
|
-
scheme: tuple[Relationship|RelationshipReading, ...]
|
|
862
|
-
|
|
863
|
-
#--------------------------------------------------
|
|
864
|
-
# Concept
|
|
865
|
-
#--------------------------------------------------
|
|
866
|
-
|
|
867
|
-
class Concept(Producer):
|
|
868
|
-
# Concept instances created for metamodel builtin types
|
|
869
|
-
builtin_concepts = set()
|
|
870
|
-
# The concepts from above, indexed by name
|
|
871
|
-
builtins = {}
|
|
872
|
-
|
|
873
|
-
globals = {}
|
|
874
|
-
|
|
875
|
-
@staticmethod
|
|
876
|
-
def _validate_concept_name(name: str):
|
|
877
|
-
"""
|
|
878
|
-
Validate that a concept name matches the expected format:
|
|
879
|
-
- Format A: [a-zA-Z0-9_.]+
|
|
880
|
-
- Format B: [a-zA-Z0-9_.]+\\([0-9]+,[0-9]+\\) (like Decimal(38,14))
|
|
881
|
-
where the leading character(s) are not underscores.
|
|
882
|
-
"""
|
|
883
|
-
if name.startswith("_"):
|
|
884
|
-
raise ValueError("Concept names cannot start with '_'")
|
|
885
|
-
|
|
886
|
-
# Check if it matches either allowed format
|
|
887
|
-
pattern_a = r'^[a-zA-Z0-9_.]+$'
|
|
888
|
-
pattern_b = r'^[a-zA-Z0-9_.]+\([0-9]+,[0-9]+\)$'
|
|
889
|
-
|
|
890
|
-
if not (re.match(pattern_a, name) or re.match(pattern_b, name)):
|
|
891
|
-
raise ValueError(f"Concept name '{name}' contains invalid characters. "
|
|
892
|
-
f"Names must contain only letters, digits, dots, and underscores, "
|
|
893
|
-
f"optionally followed by precision/scale in parentheses like 'Decimal(38,14)'")
|
|
894
|
-
|
|
895
|
-
def __init__(self, name:str, extends:list[Any] = [], model:Model|None=None, identify_by:dict[str, Any]={}):
|
|
896
|
-
super().__init__(model)
|
|
897
|
-
|
|
898
|
-
self._validate_concept_name(name)
|
|
899
|
-
self._name = name
|
|
900
|
-
self._relationships = {}
|
|
901
|
-
self._extends : list[Concept] = []
|
|
902
|
-
self._reference_schemes: list[tuple[Relationship|RelationshipReading, ...]] = []
|
|
903
|
-
self._scheme_mapping:dict[Concept, Relationship] = {}
|
|
904
|
-
|
|
905
|
-
for e in extends:
|
|
906
|
-
if isinstance(e, Concept):
|
|
907
|
-
self._extends.append(e)
|
|
908
|
-
elif python_types_to_concepts.get(e):
|
|
909
|
-
self._extends.append(python_types_to_concepts[e])
|
|
910
|
-
else:
|
|
911
|
-
raise ValueError(f"Unknown concept {e} in extends")
|
|
912
|
-
|
|
913
|
-
if identify_by:
|
|
914
|
-
scheme = []
|
|
915
|
-
for k, v in identify_by.items():
|
|
916
|
-
if python_types_to_concepts.get(v):
|
|
917
|
-
v = python_types_to_concepts[v]
|
|
918
|
-
if isinstance(v, Concept):
|
|
919
|
-
setattr(self, k, Property(f"{{{self._name}}} has {{{k}:{v._name}}}", parent=self, short_name=k, model=self._model))
|
|
920
|
-
elif isinstance(v, type) and issubclass(v, self._model.Enum): #type: ignore
|
|
921
|
-
setattr(self, k, Property(f"{{{self._name}}} has {{{k}:{v._concept._name}}}", parent=self, short_name=k, model=self._model))
|
|
922
|
-
elif isinstance(v, Relationship):
|
|
923
|
-
self._validate_identifier_relationship(v)
|
|
924
|
-
setattr(self, k, v)
|
|
925
|
-
else:
|
|
926
|
-
raise ValueError(f"identify_by must be either a Concept or Relationship: {k}={v}")
|
|
927
|
-
scheme.append(getattr(self, k))
|
|
928
|
-
self._add_ref_scheme(*tuple(scheme))
|
|
929
|
-
self._annotations = []
|
|
930
|
-
|
|
931
|
-
def require(self, *args: Any) -> Fragment:
|
|
932
|
-
return where(self).require(*args)
|
|
933
|
-
|
|
934
|
-
def new(self, ident: Any|None=None, **kwargs) -> ConceptNew:
|
|
935
|
-
self._check_ref_scheme(kwargs)
|
|
936
|
-
return ConceptNew(self, ident, kwargs)
|
|
937
|
-
|
|
938
|
-
def filter_by(self, args: Any|None=None, **kwargs: Any) -> ConceptFilter:
|
|
939
|
-
return ConceptFilter(self, args, kwargs)
|
|
940
|
-
|
|
941
|
-
def to_identity(self, args: Any|None=None, **kwargs: Any) -> ConceptConstruct:
|
|
942
|
-
self._check_ref_scheme(kwargs, shallow=True)
|
|
943
|
-
return ConceptConstruct(self, args, kwargs)
|
|
944
|
-
|
|
945
|
-
def annotate(self, *annos:Expression|Relationship|ir.Annotation) -> Concept:
|
|
946
|
-
self._annotations.extend(annos)
|
|
947
|
-
return self
|
|
948
|
-
|
|
949
|
-
#--------------------------------------------------
|
|
950
|
-
# Reference schemes
|
|
951
|
-
#--------------------------------------------------
|
|
952
|
-
|
|
953
|
-
def identify_by(self, *args: Relationship|RelationshipReading):
|
|
954
|
-
if not args:
|
|
955
|
-
raise ValueError("identify_by requires at least one relationship")
|
|
956
|
-
for rel in args:
|
|
957
|
-
if not isinstance(rel, (Relationship, RelationshipReading)):
|
|
958
|
-
raise ValueError(f"identify_by must be called with a Relationship/RelationshipReading, got {type(rel)}")
|
|
959
|
-
else:
|
|
960
|
-
self._validate_identifier_relationship(rel)
|
|
961
|
-
self._add_ref_scheme(*args)
|
|
962
|
-
|
|
963
|
-
def _add_ref_scheme(self, *rels: Relationship|RelationshipReading):
|
|
964
|
-
# thanks to prior validation we we can safely assume that
|
|
965
|
-
# * the input types are correct due to prior validation
|
|
966
|
-
# * all relationships are binary and defined on this concept
|
|
967
|
-
|
|
968
|
-
self._reference_schemes.append(rels)
|
|
969
|
-
|
|
970
|
-
# for every concept x every field f has at most one value y.
|
|
971
|
-
# f(x,y): x -> y holds
|
|
972
|
-
concept_fields = tuple([rel.__getitem__(0) for rel in rels])
|
|
973
|
-
for field in concept_fields:
|
|
974
|
-
concept_uc = Unique(field, model=self._model)
|
|
975
|
-
require(concept_uc.to_expressions())
|
|
976
|
-
|
|
977
|
-
# for any combination of field values there is at most one concept x.
|
|
978
|
-
# f₁(x,y₁) ∧ … ∧ fₙ(x,yₙ): {y₁,…,yₙ} → {x}
|
|
979
|
-
key_fields = tuple([rel.__getitem__(1) for rel in rels])
|
|
980
|
-
key_uc = Unique(*key_fields, model=self._model)
|
|
981
|
-
require(key_uc.to_expressions())
|
|
982
|
-
|
|
983
|
-
def _validate_identifier_relationship(self, rel:Relationship|RelationshipReading):
|
|
984
|
-
if rel._arity() != 2:
|
|
985
|
-
raise ValueError("identify_by can only be applied on binary relations")
|
|
986
|
-
if rel._fields[0].type_str != self._name:
|
|
987
|
-
raise ValueError("For identify_by all relationships/readings must be defined on the same Concept")
|
|
988
|
-
|
|
989
|
-
def _ref_scheme(self, shallow=False) -> tuple[Relationship, ...] | None:
|
|
990
|
-
ref_schema = []
|
|
991
|
-
if not shallow:
|
|
992
|
-
for parent in self._extends:
|
|
993
|
-
parent_schema = parent._ref_scheme()
|
|
994
|
-
if parent_schema:
|
|
995
|
-
ref_schema.extend(parent_schema)
|
|
996
|
-
break
|
|
997
|
-
if self._reference_schemes:
|
|
998
|
-
ref_schema.extend(self._reference_schemes[0])
|
|
999
|
-
return tuple(ref_schema) if ref_schema else None
|
|
1000
|
-
|
|
1001
|
-
def _check_ref_scheme(self, kwargs: dict[str, Any], shallow=False):
|
|
1002
|
-
scheme = self._ref_scheme(shallow)
|
|
1003
|
-
if not scheme:
|
|
1004
|
-
return
|
|
1005
|
-
ks = [rel._short_name for rel in scheme]
|
|
1006
|
-
for k in ks:
|
|
1007
|
-
if k not in kwargs:
|
|
1008
|
-
raise ValueError(f"Missing argument {k} for {self._name}")
|
|
1009
|
-
|
|
1010
|
-
def _ref_scheme_hierarchy(self):
|
|
1011
|
-
|
|
1012
|
-
ref_schemes: list[RefSchemeHierarchyDict] = []
|
|
1013
|
-
for parent in self._extends:
|
|
1014
|
-
parent_schemes = parent._ref_scheme_hierarchy()
|
|
1015
|
-
if parent_schemes:
|
|
1016
|
-
ref_schemes.extend(parent_schemes)
|
|
1017
|
-
break
|
|
1018
|
-
if self._reference_schemes:
|
|
1019
|
-
ref_schemes.append({"concept": self, "scheme": self._reference_schemes[0]})
|
|
1020
|
-
|
|
1021
|
-
# add mappings
|
|
1022
|
-
top_parent_name = ref_schemes[0]["concept"]._name if ref_schemes else None
|
|
1023
|
-
for ix, scheme in enumerate(ref_schemes[1:]):
|
|
1024
|
-
cur = scheme["concept"]
|
|
1025
|
-
parent = ref_schemes[ix]["concept"]
|
|
1026
|
-
if not self._scheme_mapping.get(parent):
|
|
1027
|
-
self._scheme_mapping[parent] = cur._scheme_mapping.get(parent) or Relationship(
|
|
1028
|
-
f"{{{cur._name}}} to {{{top_parent_name}}}",
|
|
1029
|
-
short_name=f"{cur._name}_to_{parent._name}",
|
|
1030
|
-
model=self._model,
|
|
1031
|
-
)
|
|
1032
|
-
scheme["mapping"] = self._scheme_mapping[parent]
|
|
1033
|
-
|
|
1034
|
-
return ref_schemes
|
|
1035
|
-
|
|
1036
|
-
#--------------------------------------------------
|
|
1037
|
-
# Internals
|
|
1038
|
-
#--------------------------------------------------
|
|
1039
|
-
|
|
1040
|
-
def _get_topmost_parent(self) -> Concept:
|
|
1041
|
-
if not self._extends:
|
|
1042
|
-
return self
|
|
1043
|
-
return self._extends[0]._get_topmost_parent()
|
|
1044
|
-
|
|
1045
|
-
def _dir_extras_from_get_relationship(self):
|
|
1046
|
-
attributes = set()
|
|
1047
|
-
for parent in self._extends:
|
|
1048
|
-
attributes.update(parent._relationships.keys())
|
|
1049
|
-
attributes.update(parent._dir_extras_from_get_relationship())
|
|
1050
|
-
return attributes
|
|
1051
|
-
|
|
1052
|
-
def _get_relationship(self, name: str) -> Relationship | RelationshipRef | RelationshipFieldRef:
|
|
1053
|
-
relationship = self._get_parent_relationship(self, name)
|
|
1054
|
-
return relationship if relationship else super()._get_relationship(name)
|
|
1055
|
-
|
|
1056
|
-
def _get_parent_relationship(self, root:Concept, name: str) -> Relationship | RelationshipRef | RelationshipFieldRef | None:
|
|
1057
|
-
for parent in self._extends:
|
|
1058
|
-
if name in parent._relationships:
|
|
1059
|
-
return RelationshipRef(root, parent._relationships[name])
|
|
1060
|
-
elif not parent._is_primitive():
|
|
1061
|
-
return parent._get_parent_relationship(root, name)
|
|
1062
|
-
return None
|
|
1063
|
-
|
|
1064
|
-
def _isa(self, other:Concept) -> bool:
|
|
1065
|
-
if self is other:
|
|
1066
|
-
return True
|
|
1067
|
-
for parent in self._extends:
|
|
1068
|
-
if parent._isa(other):
|
|
1069
|
-
return True
|
|
1070
|
-
return False
|
|
1071
|
-
|
|
1072
|
-
def _is_primitive(self) -> bool:
|
|
1073
|
-
return self._isa(Primitive)
|
|
1074
|
-
|
|
1075
|
-
def _is_enum(self) -> bool:
|
|
1076
|
-
return self._isa(Concept.builtins["Enum"])
|
|
1077
|
-
|
|
1078
|
-
def _is_filter(self) -> bool:
|
|
1079
|
-
return False
|
|
1080
|
-
|
|
1081
|
-
def __call__(self, identity:Any=None, **kwargs: Any) -> ConceptMember:
|
|
1082
|
-
return ConceptMember(self, identity, kwargs)
|
|
1083
|
-
|
|
1084
|
-
def __str__(self):
|
|
1085
|
-
return self._name
|
|
1086
|
-
|
|
1087
|
-
#--------------------------------------------------
|
|
1088
|
-
# ErrorConcept
|
|
1089
|
-
#--------------------------------------------------
|
|
1090
|
-
|
|
1091
|
-
class ErrorConcept(Concept):
|
|
1092
|
-
_error_props = OrderedSet()
|
|
1093
|
-
_relation = None
|
|
1094
|
-
_overloads:dict[Concept, Relationship] = {}
|
|
1095
|
-
|
|
1096
|
-
def __init__(self, name:str, extends:list[Any] = [], model:Model|None=None):
|
|
1097
|
-
super().__init__(name, extends, model)
|
|
1098
|
-
|
|
1099
|
-
def new(self, ident: Any|None=None, **kwargs) -> ConceptNew:
|
|
1100
|
-
from v0.relationalai.semantics.internal import annotations as annos
|
|
1101
|
-
model = kwargs.get("_model") or find_model([ident, kwargs])
|
|
1102
|
-
if kwargs.get("_model"):
|
|
1103
|
-
del kwargs["_model"]
|
|
1104
|
-
|
|
1105
|
-
if not ErrorConcept._relation:
|
|
1106
|
-
# note: explicitly declaring fields to avoid incorrect madlib lookups
|
|
1107
|
-
ErrorConcept._relation = Relationship(
|
|
1108
|
-
"{Error} has {attribute:String} with {value:Any}",
|
|
1109
|
-
short_name="pyrel_error_attrs",
|
|
1110
|
-
model=model,
|
|
1111
|
-
fields=[
|
|
1112
|
-
Field("error", "Error", Error),
|
|
1113
|
-
Field("attribute", "String", String),
|
|
1114
|
-
Field("value", "Any", Concept.builtins["Any"])
|
|
1115
|
-
]
|
|
1116
|
-
).annotate(annos.external)
|
|
1117
|
-
ErrorConcept._relation._unresolved = True
|
|
1118
|
-
source = None
|
|
1119
|
-
if "_source" in kwargs:
|
|
1120
|
-
source = kwargs["_source"]
|
|
1121
|
-
del kwargs["_source"]
|
|
1122
|
-
else:
|
|
1123
|
-
source = runtime_env.get_source_pos()
|
|
1124
|
-
# kwargs["severity"] = "error"
|
|
1125
|
-
if source:
|
|
1126
|
-
source = source.to_source_info()
|
|
1127
|
-
source_id = len(errors.ModelError.error_locations)
|
|
1128
|
-
errors.ModelError.error_locations[source_id] = source
|
|
1129
|
-
kwargs["pyrel_id"] = source_id
|
|
1130
|
-
|
|
1131
|
-
for k, v in kwargs.items():
|
|
1132
|
-
v_type = to_type(v) or python_types_to_concepts.get(type(v)) or Concept.builtins["Any"]
|
|
1133
|
-
if v_type and v_type not in self._overloads:
|
|
1134
|
-
# note: explicitly declaring fields to avoid incorrect madlib lookups
|
|
1135
|
-
self._overloads[v_type] = Relationship(
|
|
1136
|
-
f"{{Error}} has {{attribute:String}} with {{value:{v_type._name}}}",
|
|
1137
|
-
short_name="pyrel_error_attrs",
|
|
1138
|
-
model=model,
|
|
1139
|
-
fields=[
|
|
1140
|
-
Field("error", "Error", Error),
|
|
1141
|
-
Field("attribute", "String", String),
|
|
1142
|
-
Field("value", "v_type._name", v_type)
|
|
1143
|
-
]
|
|
1144
|
-
).annotate(annos.external)
|
|
1145
|
-
assert v_type is not None, f"Cannot determine type for {k}={v}"
|
|
1146
|
-
overload = self._overloads[v_type]
|
|
1147
|
-
if (model, k) not in ErrorConcept._error_props and not k.startswith("_"):
|
|
1148
|
-
ErrorConcept._error_props.add((model, k))
|
|
1149
|
-
with root_tracking(True):
|
|
1150
|
-
frag = where(getattr(self, k)).define(
|
|
1151
|
-
overload(self, k, getattr(self, k))
|
|
1152
|
-
)
|
|
1153
|
-
frag._model = model
|
|
1154
|
-
|
|
1155
|
-
return super().new(ident, **kwargs)
|
|
1156
|
-
|
|
1157
|
-
def __call__(self, identity: Any = None, **kwargs: Any) -> Any:
|
|
1158
|
-
raise ValueError("Errors must always be created with a new identity. Use Error.new(..) instead of Error(..)")
|
|
1159
|
-
|
|
1160
|
-
#--------------------------------------------------
|
|
1161
|
-
# Builtin Concepts
|
|
1162
|
-
#--------------------------------------------------
|
|
1163
|
-
|
|
1164
|
-
Primitive = Concept.builtins["Primitive"] = Concept("Primitive")
|
|
1165
|
-
Error = Concept.builtins["Error"] = ErrorConcept("Error")
|
|
1166
|
-
|
|
1167
|
-
def _register_builtin(name):
|
|
1168
|
-
if name == "AnyEntity":
|
|
1169
|
-
c = Concept(name)
|
|
1170
|
-
else:
|
|
1171
|
-
c = Concept(name, extends=[Primitive])
|
|
1172
|
-
Concept.builtin_concepts.add(c)
|
|
1173
|
-
Concept.builtins[name] = c
|
|
1174
|
-
|
|
1175
|
-
# Load builtin types
|
|
1176
|
-
for builtin in types.builtin_types:
|
|
1177
|
-
if isinstance(builtin, ir.ScalarType):
|
|
1178
|
-
_register_builtin(builtin.name)
|
|
1179
|
-
|
|
1180
|
-
AnyEntity = Concept.builtins["AnyEntity"]
|
|
1181
|
-
Float = Concept.builtins["Float"]
|
|
1182
|
-
Number = Concept.builtins["Number"]
|
|
1183
|
-
Int64 = Concept.builtins["Int64"]
|
|
1184
|
-
Int128 = Concept.builtins["Int128"]
|
|
1185
|
-
# Integer aliases to Int128.
|
|
1186
|
-
Integer = Concept.builtins["Int128"]
|
|
1187
|
-
Hash = Concept.builtins["Hash"]
|
|
1188
|
-
String = Concept.builtins["String"]
|
|
1189
|
-
Bool = Concept.builtins["Bool"]
|
|
1190
|
-
Date = Concept.builtins["Date"]
|
|
1191
|
-
DateTime = Concept.builtins["DateTime"]
|
|
1192
|
-
|
|
1193
|
-
# The default Decimal type can be retrieved as "Decimal" or "Decimal(38,14)"
|
|
1194
|
-
Decimal = Concept.builtins["Decimal"] = Concept.builtins[types.Decimal.name]
|
|
1195
|
-
|
|
1196
|
-
def decimal_concept(precision: int = 38, scale: int = 14) -> Concept:
|
|
1197
|
-
""" Get the Concept for a decimal with this precision and scale. """
|
|
1198
|
-
return decimal_concept_by_name(f"Decimal({precision},{scale})")
|
|
1199
|
-
|
|
1200
|
-
def decimal_concept_by_name(name: str) -> Concept:
|
|
1201
|
-
""" Get the Concept for a decimal with this name, e.g. 'Decimal(38,14)'. """
|
|
1202
|
-
if name not in Concept.builtins:
|
|
1203
|
-
# cache for reuse
|
|
1204
|
-
_register_builtin(name)
|
|
1205
|
-
return Concept.builtins[name]
|
|
1206
|
-
|
|
1207
|
-
def is_decimal(concept: Concept) -> bool:
|
|
1208
|
-
""" Check whether this concept represents a Decimal. """
|
|
1209
|
-
return concept._name.startswith("Decimal") and concept in Concept.builtin_concepts
|
|
1210
|
-
|
|
1211
|
-
# The following is a workaround for having the builtin "Int"
|
|
1212
|
-
# but not other the builtin "Integer". The `Relationship`
|
|
1213
|
-
# class relies upon the builtin "Integer" existing in its
|
|
1214
|
-
# _build_inspection_fragment() method.
|
|
1215
|
-
Concept.builtins["Int"] = Concept.builtins["Int128"]
|
|
1216
|
-
Concept.builtins["Integer"] = Concept.builtins["Int128"]
|
|
1217
|
-
|
|
1218
|
-
_np_datetime = np.dtype('datetime64[ns]')
|
|
1219
|
-
python_types_to_concepts : dict[Any, Concept] = {
|
|
1220
|
-
int: Concept.builtins["Int128"],
|
|
1221
|
-
float: Concept.builtins["Float"],
|
|
1222
|
-
str: Concept.builtins["String"],
|
|
1223
|
-
bool: Concept.builtins["Bool"],
|
|
1224
|
-
date: Concept.builtins["Date"],
|
|
1225
|
-
datetime: Concept.builtins["DateTime"],
|
|
1226
|
-
PyDecimal: Decimal,
|
|
1227
|
-
|
|
1228
|
-
Int128Dtype(): Concept.builtins["Int128"],
|
|
1229
|
-
|
|
1230
|
-
# Pandas/NumPy dtype objects
|
|
1231
|
-
np.dtype('int64'): Concept.builtins["Int128"],
|
|
1232
|
-
np.dtype('int32'): Concept.builtins["Int128"],
|
|
1233
|
-
np.dtype('int16'): Concept.builtins["Int128"],
|
|
1234
|
-
np.dtype('int8'): Concept.builtins["Int128"],
|
|
1235
|
-
np.dtype('uint64'): Concept.builtins["Int128"],
|
|
1236
|
-
np.dtype('uint32'): Concept.builtins["Int128"],
|
|
1237
|
-
np.dtype('uint16'): Concept.builtins["Int128"],
|
|
1238
|
-
np.dtype('uint8'): Concept.builtins["Int128"],
|
|
1239
|
-
np.dtype('float64'): Concept.builtins["Float"],
|
|
1240
|
-
np.dtype('float32'): Concept.builtins["Float"],
|
|
1241
|
-
np.dtype('bool'): Concept.builtins["Bool"],
|
|
1242
|
-
np.dtype('object'): Concept.builtins["String"], # Often strings are stored as object dtype
|
|
1243
|
-
_np_datetime: Concept.builtins["DateTime"],
|
|
1244
|
-
|
|
1245
|
-
# Pandas extension dtypes
|
|
1246
|
-
pd.Int64Dtype(): Concept.builtins["Int128"],
|
|
1247
|
-
pd.Int32Dtype(): Concept.builtins["Int128"],
|
|
1248
|
-
pd.Int16Dtype(): Concept.builtins["Int128"],
|
|
1249
|
-
pd.Int8Dtype(): Concept.builtins["Int128"],
|
|
1250
|
-
pd.UInt64Dtype(): Concept.builtins["Int128"],
|
|
1251
|
-
pd.UInt32Dtype(): Concept.builtins["Int128"],
|
|
1252
|
-
pd.UInt16Dtype(): Concept.builtins["Int128"],
|
|
1253
|
-
pd.UInt8Dtype(): Concept.builtins["Int128"],
|
|
1254
|
-
pd.Float64Dtype(): Concept.builtins["Float"],
|
|
1255
|
-
pd.Float32Dtype(): Concept.builtins["Float"],
|
|
1256
|
-
pd.StringDtype(): Concept.builtins["String"],
|
|
1257
|
-
pd.BooleanDtype(): Concept.builtins["Bool"],
|
|
1258
|
-
}
|
|
1259
|
-
|
|
1260
|
-
# this map is required when we need to map standard python type string to a Concept
|
|
1261
|
-
python_types_str_to_concepts = {
|
|
1262
|
-
"int": python_types_to_concepts[int],
|
|
1263
|
-
"float": python_types_to_concepts[float],
|
|
1264
|
-
"str": python_types_to_concepts[str],
|
|
1265
|
-
"bool": python_types_to_concepts[bool],
|
|
1266
|
-
"date": python_types_to_concepts[date],
|
|
1267
|
-
"datetime": python_types_to_concepts[datetime],
|
|
1268
|
-
"decimal": python_types_to_concepts[PyDecimal]
|
|
1269
|
-
}
|
|
1270
|
-
|
|
1271
|
-
#--------------------------------------------------
|
|
1272
|
-
# Relationship
|
|
1273
|
-
#--------------------------------------------------
|
|
1274
|
-
|
|
1275
|
-
@dataclass()
|
|
1276
|
-
class Field():
|
|
1277
|
-
name:str
|
|
1278
|
-
type_str:str
|
|
1279
|
-
type:Concept|None = None
|
|
1280
|
-
|
|
1281
|
-
def __str__(self):
|
|
1282
|
-
return f"{self.name}:{self.type_str}"
|
|
1283
|
-
|
|
1284
|
-
def __hash__(self) -> int:
|
|
1285
|
-
type_str = self.type._name if self.type else self.type_str
|
|
1286
|
-
return hash((self.name, type_str))
|
|
1287
|
-
|
|
1288
|
-
class Relationship(Producer):
|
|
1289
|
-
builtins = {}
|
|
1290
|
-
|
|
1291
|
-
def __init__(self, madlib:str, parent:Producer|None=None, short_name:str="", model:Model|None=None, fields:list[Field]|None=None, field_refs:list[Ref]|None=None, ir_relation:ir.Relation|None=None):
|
|
1292
|
-
found_model = model or find_model(parent) or find_model(args)
|
|
1293
|
-
super().__init__(found_model)
|
|
1294
|
-
self._parent = parent
|
|
1295
|
-
self._madlib = madlib
|
|
1296
|
-
self._passed_short_name = short_name
|
|
1297
|
-
self._relationships = {}
|
|
1298
|
-
if fields is not None:
|
|
1299
|
-
self._fields:list[Field] = fields
|
|
1300
|
-
else:
|
|
1301
|
-
self._fields = self._parse_schema_format(madlib)
|
|
1302
|
-
if not self._fields and not ir_relation:
|
|
1303
|
-
raise ValueError(f"No fields found in relationship {self}")
|
|
1304
|
-
if model and model._strict:
|
|
1305
|
-
self._validate_fields(self._fields)
|
|
1306
|
-
self._ir_relation = ir_relation
|
|
1307
|
-
self._unresolved = False
|
|
1308
|
-
if field_refs is not None:
|
|
1309
|
-
self._field_refs = field_refs
|
|
1310
|
-
else:
|
|
1311
|
-
self._field_refs = [cast(Ref, field_to_type(found_model, field).ref(field.name)) for field in self._fields]
|
|
1312
|
-
for field in self._field_refs:
|
|
1313
|
-
field._no_lookup = True
|
|
1314
|
-
self._internal_constraints:set[FieldsConstraint] = set()
|
|
1315
|
-
self._field_names = [field.name for field in self._fields]
|
|
1316
|
-
self._readings = [RelationshipReading(madlib, alt_of=self, short_name=short_name, fields=self._fields, model=found_model, parent=parent)]
|
|
1317
|
-
self._annotations = []
|
|
1318
|
-
# now that the Relationship is validated, register into the model
|
|
1319
|
-
if found_model is not None:
|
|
1320
|
-
found_model.relationships.append(self)
|
|
1321
|
-
|
|
1322
|
-
@property
|
|
1323
|
-
def _name(self):
|
|
1324
|
-
return self._short_name or self._madlib
|
|
1325
|
-
|
|
1326
|
-
@property
|
|
1327
|
-
def _short_name(self):
|
|
1328
|
-
return self._passed_short_name or _short_name_from_madlib(self._madlib)
|
|
1329
|
-
|
|
1330
|
-
def is_many(self):
|
|
1331
|
-
if self._arity() == 1:
|
|
1332
|
-
return False
|
|
1333
|
-
uc = Unique.to_identity(*(self[i] for i in range(self._arity() - 1)))
|
|
1334
|
-
return uc not in self._internal_constraints
|
|
1335
|
-
|
|
1336
|
-
def _is_filter(self) -> bool:
|
|
1337
|
-
return self._short_name in [">", "<", "=", "!=", ">=", "<="]
|
|
1338
|
-
|
|
1339
|
-
@staticmethod
|
|
1340
|
-
def _sanitize_field_name(name: str) -> str:
|
|
1341
|
-
"""
|
|
1342
|
-
Sanitize a field name by converting to lowercase and replacing
|
|
1343
|
-
problematic characters with underscores. Special handling for
|
|
1344
|
-
precision/scale format (like Decimal(38,14)) to avoid trailing underscores.
|
|
1345
|
-
|
|
1346
|
-
This ensures consistent field naming between
|
|
1347
|
-
relationship parsing and field reference creation.
|
|
1348
|
-
"""
|
|
1349
|
-
lowered = name.lower()
|
|
1350
|
-
|
|
1351
|
-
# Check if this matches the precision/scale format: word(digits,digits)
|
|
1352
|
-
import re
|
|
1353
|
-
precision_scale_pattern = r'^([a-zA-Z0-9_.]+)\(([0-9]+),([0-9]+)\)$'
|
|
1354
|
-
match = re.match(precision_scale_pattern, lowered)
|
|
1355
|
-
|
|
1356
|
-
if match:
|
|
1357
|
-
# Format B: Convert Decimal(38,14) -> decimal_38_14 (no trailing underscore)
|
|
1358
|
-
base_name, precision, scale = match.groups()
|
|
1359
|
-
# First sanitize the base name part
|
|
1360
|
-
sanitized_base = re.sub(r"[ ,\.\|]", "_", base_name)
|
|
1361
|
-
return f"{sanitized_base}_{precision}_{scale}"
|
|
1362
|
-
else:
|
|
1363
|
-
# Format A: Regular sanitization, preserve original trailing underscores
|
|
1364
|
-
return re.sub(r"[ ,\.\(\)\|]", "_", lowered)
|
|
1365
|
-
|
|
1366
|
-
def _parse_schema_format(self, format_string:str):
|
|
1367
|
-
# Pattern to extract fields like {Type} or {name:Type}, where Type can have precision and scale, like Decimal(38,14)
|
|
1368
|
-
pattern = r'\{([a-zA-Z0-9_.]+(?:\([0-9]+,[0-9]+\))?)(?::([a-zA-Z0-9_.]+(?:\([0-9]+,[0-9]+\))?))?\}'
|
|
1369
|
-
matches = re.findall(pattern, format_string)
|
|
1370
|
-
|
|
1371
|
-
namer = NameCache()
|
|
1372
|
-
fields = []
|
|
1373
|
-
match_index = 0
|
|
1374
|
-
ix = 0
|
|
1375
|
-
for field_name, field_type in matches:
|
|
1376
|
-
# If no type is specified, use the field name as the type
|
|
1377
|
-
if not field_type:
|
|
1378
|
-
field_type = field_name
|
|
1379
|
-
# in this case, the field_name is based on the type name,
|
|
1380
|
-
# so sanitize to avoid, for example, ()s in decimal names,
|
|
1381
|
-
# and other problematic special characters.
|
|
1382
|
-
field_name = self._sanitize_field_name(field_name)
|
|
1383
|
-
|
|
1384
|
-
ix += 1
|
|
1385
|
-
field_name = namer.get_name(ix, field_name)
|
|
1386
|
-
|
|
1387
|
-
fields.append(Field(field_name, field_type))
|
|
1388
|
-
match_index +=1
|
|
1389
|
-
|
|
1390
|
-
return fields
|
|
1391
|
-
|
|
1392
|
-
def _dir_extras_from_get_relationship(self) -> Any:
|
|
1393
|
-
return self._field_refs[-1]._dir_extras_from_getattr()
|
|
1394
|
-
|
|
1395
|
-
def _get_relationship(self, name: str) -> Relationship | RelationshipRef | RelationshipFieldRef:
|
|
1396
|
-
rel:RelationshipRef = getattr(self._field_refs[-1], name)
|
|
1397
|
-
return RelationshipRef(self, rel._relationship)
|
|
1398
|
-
|
|
1399
|
-
def _arity(self):
|
|
1400
|
-
return len(self._fields)
|
|
1401
|
-
|
|
1402
|
-
def _dir_extras_from_getattr(self) -> Any:
|
|
1403
|
-
attributes = set()
|
|
1404
|
-
if self._arity() > 2:
|
|
1405
|
-
attributes.update(self._field_names)
|
|
1406
|
-
attributes.update(super()._dir_extras_from_getattr())
|
|
1407
|
-
return attributes
|
|
1408
|
-
|
|
1409
|
-
def __getattr__(self, name: str) -> Any:
|
|
1410
|
-
if self._arity() > 2 and name in self._field_names:
|
|
1411
|
-
return RelationshipFieldRef(self._parent, self, self._field_names.index(name))
|
|
1412
|
-
return super().__getattr__(name)
|
|
1413
|
-
|
|
1414
|
-
def annotate(self, *annos:Expression|Relationship|ir.Annotation) -> Relationship:
|
|
1415
|
-
self._annotations.extend(annos)
|
|
1416
|
-
return self
|
|
1417
|
-
|
|
1418
|
-
def __getitem__(self, arg:str|int|Concept) -> Any:
|
|
1419
|
-
return _get_relationship_item(self, arg)
|
|
1420
|
-
|
|
1421
|
-
def ref(self, name:str|None=None) -> Ref|RelationshipRef:
|
|
1422
|
-
return RelationshipRef(self._parent, self, name=name)
|
|
1423
|
-
|
|
1424
|
-
def alt(self, madlib:Any, short_name:str="", reading:RelationshipReading|None = None) -> RelationshipReading:
|
|
1425
|
-
if not reading:
|
|
1426
|
-
reading = RelationshipReading(madlib, alt_of=self, short_name=short_name, model=self._model)
|
|
1427
|
-
self._readings.append(reading)
|
|
1428
|
-
where(self(*self._field_refs)).define(
|
|
1429
|
-
reading._ignore_root(*reading._field_refs),
|
|
1430
|
-
)
|
|
1431
|
-
return reading
|
|
1432
|
-
|
|
1433
|
-
def _is_same_relationship(self, rel:Relationship|RelationshipReading) -> bool:
|
|
1434
|
-
return self._id == rel._alt_of._id if isinstance(rel, RelationshipReading) else self._id == rel._id
|
|
1435
|
-
|
|
1436
|
-
def _build_inspection_fragment(self):
|
|
1437
|
-
"""
|
|
1438
|
-
Helper function for the inspect() and to_df() methods below,
|
|
1439
|
-
that generates a Fragment from the Relationship, inspect()ing
|
|
1440
|
-
or to_df()'ing which yields all tuples in the Relationship.
|
|
1441
|
-
"""
|
|
1442
|
-
field_types = [field_to_type(self._model, field) for field in self._fields]
|
|
1443
|
-
field_vars = [field_type.ref() for field_type in field_types]
|
|
1444
|
-
return where(self(*field_vars)).select(*field_vars)
|
|
1445
|
-
|
|
1446
|
-
def inspect(self):
|
|
1447
|
-
return self._build_inspection_fragment().inspect()
|
|
1448
|
-
|
|
1449
|
-
def to_df(self):
|
|
1450
|
-
return self._build_inspection_fragment().to_df()
|
|
1451
|
-
|
|
1452
|
-
def _validate_fields(self, fields:list[Field]):
|
|
1453
|
-
# Check for multiple occurrences of the same type without explicit role names
|
|
1454
|
-
type_occurrences = defaultdict(list)
|
|
1455
|
-
for field in fields:
|
|
1456
|
-
type_occurrences[field.type_str].append(field.name)
|
|
1457
|
-
|
|
1458
|
-
if (len(type_occurrences[field.type_str]) > 1 and
|
|
1459
|
-
any(name == field.type_str.lower() for name in type_occurrences[field.type_str])):
|
|
1460
|
-
raise ValueError(f"The type {field.type_str} occurs multiple times in '{self._madlib}'. Please "
|
|
1461
|
-
f"disambiguate by providing explicit role names for each occurrence.")
|
|
1462
|
-
|
|
1463
|
-
def __call__(self, *args: Any, **kwargs) -> Any:
|
|
1464
|
-
return _relationship_call(self, *args, **kwargs)
|
|
1465
|
-
|
|
1466
|
-
def __str__(self):
|
|
1467
|
-
if self._parent and self._short_name:
|
|
1468
|
-
return f"{self._parent}.{self._short_name}"
|
|
1469
|
-
return self._name
|
|
1470
|
-
|
|
1471
|
-
class Property(Relationship):
|
|
1472
|
-
|
|
1473
|
-
def __init__(self, madlib:str, parent:Producer|None=None, short_name:str="", model:Model|None=None, fields:list[Field]|None=None, field_refs:list[Ref]|None=None, ir_relation:ir.Relation|None=None):
|
|
1474
|
-
super().__init__(madlib, parent, short_name, model, fields, field_refs, ir_relation)
|
|
1475
|
-
# for property should be an implicit unique constraint on the first n-1 fields
|
|
1476
|
-
uc = Unique(*(self[i] for i in range(self._arity() - 1)), model=self._model)
|
|
1477
|
-
require(uc.to_expressions())
|
|
1478
|
-
|
|
1479
|
-
|
|
1480
|
-
class RelationshipReading(Producer):
|
|
1481
|
-
|
|
1482
|
-
def __init__(self, madlib:str, alt_of:Relationship, short_name:str, fields:list[Field]|None=None, model:Model|None=None, parent:Producer|None=None,):
|
|
1483
|
-
found_model = model or find_model(parent)
|
|
1484
|
-
super().__init__(found_model)
|
|
1485
|
-
self._parent = parent
|
|
1486
|
-
self._alt_of = alt_of
|
|
1487
|
-
self._madlib = madlib
|
|
1488
|
-
self._passed_short_name = short_name
|
|
1489
|
-
if fields is not None:
|
|
1490
|
-
self._fields:list[Field] = fields
|
|
1491
|
-
else:
|
|
1492
|
-
self._fields = alt_of._parse_schema_format(madlib)
|
|
1493
|
-
if Counter(self._fields) != Counter(alt_of._fields):
|
|
1494
|
-
raise ValueError(
|
|
1495
|
-
f"Invalid alternative relationship. The alternative group of used fields ({', '.join(str(f) for f in self._fields)}) does not match with the original ({', '.join(str(f) for f in alt_of._fields)})")
|
|
1496
|
-
self._field_refs = [alt_of[f.name]._field_ref for f in self._fields]
|
|
1497
|
-
self._field_names = [field.name for field in self._fields]
|
|
1498
|
-
self._relationships = {}
|
|
1499
|
-
self._annotations = []
|
|
1500
|
-
|
|
1501
|
-
def is_many(self):
|
|
1502
|
-
if self._arity() == 1:
|
|
1503
|
-
return False
|
|
1504
|
-
uc = Unique.to_identity(*(self[i] for i in range(self._arity() - 1)))
|
|
1505
|
-
return uc not in self._alt_of._internal_constraints
|
|
1506
|
-
|
|
1507
|
-
def _arity(self):
|
|
1508
|
-
return len(self._fields)
|
|
1509
|
-
|
|
1510
|
-
def annotate(self, *annos:Expression|Relationship|ir.Annotation) -> RelationshipReading:
|
|
1511
|
-
self._annotations.extend(annos)
|
|
1512
|
-
return self
|
|
1513
|
-
|
|
1514
|
-
@property
|
|
1515
|
-
def _name(self):
|
|
1516
|
-
return self._short_name or self._madlib
|
|
1517
|
-
|
|
1518
|
-
@property
|
|
1519
|
-
def _short_name(self):
|
|
1520
|
-
return self._passed_short_name or _short_name_from_madlib(self._madlib)
|
|
1521
|
-
|
|
1522
|
-
def _ignore_root(self, *args, **kwargs):
|
|
1523
|
-
expr = self(*args, **kwargs)
|
|
1524
|
-
expr._ignore_root = True
|
|
1525
|
-
return expr
|
|
1526
|
-
|
|
1527
|
-
def _dir_extras_from_get_relationship(self) -> Any:
|
|
1528
|
-
return self._field_refs[-1]._dir_extras_from_getattr()
|
|
1529
|
-
|
|
1530
|
-
def _get_relationship(self, name: str) -> Relationship | RelationshipRef | RelationshipFieldRef:
|
|
1531
|
-
rel:RelationshipRef = getattr(self._field_refs[-1], name)
|
|
1532
|
-
return RelationshipRef(self, rel._relationship)
|
|
1533
|
-
|
|
1534
|
-
def _dir_extras_from_getattr(self) -> Any:
|
|
1535
|
-
attributes = set()
|
|
1536
|
-
if self._arity() > 2:
|
|
1537
|
-
attributes.update(self._field_names)
|
|
1538
|
-
attributes.update(self._alt_of._relationships.keys())
|
|
1539
|
-
attributes.update(self._dir_extras_from_get_relationship())
|
|
1540
|
-
return attributes
|
|
1541
|
-
|
|
1542
|
-
def _is_same_relationship(self, rel:Relationship|RelationshipReading) -> bool:
|
|
1543
|
-
return self._alt_of._id == rel._alt_of._id if isinstance(rel, RelationshipReading) else self._alt_of._id == rel._id
|
|
1544
|
-
|
|
1545
|
-
def __getattr__(self, name) -> Any:
|
|
1546
|
-
if not name.startswith("_"):
|
|
1547
|
-
if self._arity() > 2 and name in self._field_names:
|
|
1548
|
-
return RelationshipFieldRef(self._parent, self, self._field_names.index(name))
|
|
1549
|
-
if name not in self._relationships:
|
|
1550
|
-
self._relationships[name] = self._get_relationship(name)
|
|
1551
|
-
return self._relationships[name]
|
|
1552
|
-
return super().__getattr__(name)
|
|
1553
|
-
|
|
1554
|
-
def __getitem__(self, arg: str | int | Concept) -> Any:
|
|
1555
|
-
return _get_relationship_item(self, arg)
|
|
1556
|
-
|
|
1557
|
-
def __call__(self, *args: Any, **kwargs) -> Any:
|
|
1558
|
-
return _relationship_call(self, *args, **kwargs)
|
|
1559
|
-
|
|
1560
|
-
def __str__(self):
|
|
1561
|
-
if self._parent and self._short_name:
|
|
1562
|
-
return f"{self._parent}.{self._short_name}"
|
|
1563
|
-
return self._name
|
|
1564
|
-
|
|
1565
|
-
def _short_name_from_madlib(madlib:Any) -> str:
|
|
1566
|
-
# Replace curly braces, colons, and spaces with underscores.
|
|
1567
|
-
# Then strip leading/trailing underscores.
|
|
1568
|
-
return re.sub(r"[{}: ]", "_", str(madlib)).strip("_")
|
|
1569
|
-
|
|
1570
|
-
def _get_relationship_item(rel:Relationship|RelationshipReading, arg:Any) -> Any:
|
|
1571
|
-
if isinstance(arg, int):
|
|
1572
|
-
if arg < 0:
|
|
1573
|
-
raise ValueError(f"Position should be positive, got {arg}")
|
|
1574
|
-
if rel._arity() <= arg:
|
|
1575
|
-
raise ValueError(f"Relationship '{rel._name}' has only {rel._arity()} fields")
|
|
1576
|
-
return RelationshipFieldRef(rel._parent, rel, arg)
|
|
1577
|
-
elif isinstance(arg, str):
|
|
1578
|
-
if arg not in rel._field_names:
|
|
1579
|
-
raise ValueError(f"Relationship '{rel._name}' has only {rel._field_names} fields")
|
|
1580
|
-
return RelationshipFieldRef(rel._parent, rel, rel._field_names.index(arg))
|
|
1581
|
-
elif isinstance(arg, Concept):
|
|
1582
|
-
return _get_relationship_field_ref(rel, arg)
|
|
1583
|
-
elif isinstance(arg, type) and rel._model is not None and issubclass(arg, rel._model.Enum):
|
|
1584
|
-
return _get_relationship_field_ref(rel, arg._concept)
|
|
1585
|
-
else:
|
|
1586
|
-
raise ValueError(f"Unknown argument {arg}")
|
|
1587
|
-
|
|
1588
|
-
def _get_relationship_field_ref(rel:Relationship|RelationshipReading, concept:Concept) -> Any:
|
|
1589
|
-
result: RelationshipFieldRef | None = None
|
|
1590
|
-
for idx, ref in enumerate(rel._field_refs):
|
|
1591
|
-
if result is None and ref._thing._id == concept._id:
|
|
1592
|
-
result = RelationshipFieldRef(rel._parent, rel, idx)
|
|
1593
|
-
else:
|
|
1594
|
-
if ref._thing._id == concept._id:
|
|
1595
|
-
raise ValueError(
|
|
1596
|
-
f"Ambiguous reference to the field: '{concept._name}' presented in more than one field. Use reference by name or position instead")
|
|
1597
|
-
if result is None:
|
|
1598
|
-
raise ValueError(f"Relationship '{rel._name}' does not have '{concept._name}' as a field")
|
|
1599
|
-
return result
|
|
1600
|
-
|
|
1601
|
-
def _relationship_call(rel:Relationship|RelationshipReading, *args: Any, **kwargs) -> Any:
|
|
1602
|
-
if kwargs and args:
|
|
1603
|
-
raise ValueError("Cannot use both positional and keyword arguments")
|
|
1604
|
-
if kwargs:
|
|
1605
|
-
# check that all fields have been provided
|
|
1606
|
-
clean_args = []
|
|
1607
|
-
for ix, field in enumerate(rel._field_names):
|
|
1608
|
-
if field in kwargs:
|
|
1609
|
-
clean_args.append(kwargs.get(field))
|
|
1610
|
-
if ix == 0 and rel._parent:
|
|
1611
|
-
continue
|
|
1612
|
-
if field not in kwargs:
|
|
1613
|
-
raise ValueError(f"Missing argument {field}")
|
|
1614
|
-
else:
|
|
1615
|
-
clean_args = list(args)
|
|
1616
|
-
if len(clean_args) < rel._arity():
|
|
1617
|
-
if rel._parent:
|
|
1618
|
-
clean_args = [rel._parent, *clean_args]
|
|
1619
|
-
if len(clean_args) != rel._arity():
|
|
1620
|
-
raise ValueError(f"Expected {rel._arity()} arguments, got {len(clean_args)}: {rel}")
|
|
1621
|
-
return Expression(rel, *clean_args)
|
|
1622
|
-
|
|
1623
|
-
#--------------------------------------------------
|
|
1624
|
-
# Builtin Relationships
|
|
1625
|
-
#--------------------------------------------------
|
|
1626
|
-
|
|
1627
|
-
for builtin in builtins.builtin_relations + builtins.builtin_annotations:
|
|
1628
|
-
fields = []
|
|
1629
|
-
for field in builtin.fields:
|
|
1630
|
-
field_type = re.sub(r'[\[\{\(]', '', str(field.type)).strip()
|
|
1631
|
-
field_type = str(field.type).strip()
|
|
1632
|
-
fields.append(f"{field.name}:{field_type}")
|
|
1633
|
-
args = ' '.join([f"{{{f}}}" for f in fields])
|
|
1634
|
-
Relationship.builtins[builtin.name] = Relationship(
|
|
1635
|
-
f"{builtin.name} {args}",
|
|
1636
|
-
parent=None,
|
|
1637
|
-
short_name=builtin.name,
|
|
1638
|
-
ir_relation=builtin,
|
|
1639
|
-
)
|
|
1640
|
-
|
|
1641
|
-
RawSource = Relationship.builtins["raw_source"]
|
|
1642
|
-
|
|
1643
|
-
#--------------------------------------------------
|
|
1644
|
-
# Expression
|
|
1645
|
-
#--------------------------------------------------
|
|
1646
|
-
|
|
1647
|
-
infix_ops = ["+", "-", "*", "/", "//", "^", "%", ">", ">=", "<", "<=", "=", "!="]
|
|
1648
|
-
constraints_ops = [builtins.unique.name, builtins.exclusive.name, builtins.anyof.name]
|
|
1649
|
-
|
|
1650
|
-
class Expression(Producer):
|
|
1651
|
-
def __init__(self, op:Relationship|RelationshipReading|Concept, *params:Any):
|
|
1652
|
-
super().__init__(op._model or find_model(params))
|
|
1653
|
-
self._op = op
|
|
1654
|
-
self._params = params
|
|
1655
|
-
self._ignore_root = False
|
|
1656
|
-
self._source = runtime_env.get_source_pos()
|
|
1657
|
-
|
|
1658
|
-
def __str__(self):
|
|
1659
|
-
return f"({self._op} {' '.join(map(str, self._params))})"
|
|
1660
|
-
|
|
1661
|
-
def _pprint(self, indent:int=0) -> str:
|
|
1662
|
-
if self._op._name in infix_ops:
|
|
1663
|
-
a, b = self._params[0], self._params[1]
|
|
1664
|
-
return f"{' ' * indent}{a} {self._op} {b}"
|
|
1665
|
-
elif self._op._name in constraints_ops:
|
|
1666
|
-
args = []
|
|
1667
|
-
for param in flatten(self._params, True):
|
|
1668
|
-
args.append(f"{param._relationship}[\"{param._field_ref._name}\"]" if isinstance(param, RelationshipFieldRef) else str(param))
|
|
1669
|
-
return f"{' ' * indent}{self._op}({', '.join([str(param) for param in args])})"
|
|
1670
|
-
return f"{' ' * indent}{self._op}({' '.join(map(str, self._params))})"
|
|
1671
|
-
|
|
1672
|
-
def _dir_extras(self):
|
|
1673
|
-
attributes = set()
|
|
1674
|
-
last = self._params[-1]
|
|
1675
|
-
if isinstance(self._op, (Relationship, RelationshipRef)) and isinstance(last, Concept):
|
|
1676
|
-
# lookup the last concept in the relationship, and then lookup the attribute in it
|
|
1677
|
-
concept = self._op.__getitem__(last)
|
|
1678
|
-
attributes.update(dir(concept))
|
|
1679
|
-
return attributes
|
|
1680
|
-
|
|
1681
|
-
def _arg_ref(self, idx:int) -> ArgumentRef:
|
|
1682
|
-
if idx < 0:
|
|
1683
|
-
raise ValueError(f"Argument index should be positive, got {idx}")
|
|
1684
|
-
if len(self._params) <= idx:
|
|
1685
|
-
raise ValueError(f"Expression '{self.__str__()}' has only {len(self._params)} arguments")
|
|
1686
|
-
param = self._params[idx]
|
|
1687
|
-
# if param is an Expression then refer the last param of this expression
|
|
1688
|
-
return ArgumentRef(self, param._params[-1] if isinstance(param, Expression) else param)
|
|
1689
|
-
|
|
1690
|
-
def __getattr__(self, name: str):
|
|
1691
|
-
last = self._params[-1]
|
|
1692
|
-
if isinstance(self._op, (Relationship, RelationshipRef)) and isinstance(last, Concept):
|
|
1693
|
-
# lookup the last concept in the relationship, and then lookup the attribute in it
|
|
1694
|
-
concept = self._op.__getitem__(last)
|
|
1695
|
-
return getattr(concept, name)
|
|
1696
|
-
raise AttributeError(f"Expression has no attribute {name}")
|
|
1697
|
-
|
|
1698
|
-
class ConceptExpression(Expression):
|
|
1699
|
-
def __init__(self, con:Concept, identity:Any, kwargs:dict[str, Any]):
|
|
1700
|
-
super().__init__(con, identity, kwargs)
|
|
1701
|
-
for k, _ in kwargs.items():
|
|
1702
|
-
# make sure to create the properties being referenced
|
|
1703
|
-
getattr(self._op, k)
|
|
1704
|
-
self._relationships = {}
|
|
1705
|
-
|
|
1706
|
-
_remove_roots([v for v in kwargs.values() if isinstance(v, Producer)])
|
|
1707
|
-
|
|
1708
|
-
def _construct_args(self, scheme=None) -> dict[Relationship|Concept, Any]:
|
|
1709
|
-
args = {}
|
|
1710
|
-
scheme = scheme or self._op._ref_scheme()
|
|
1711
|
-
[ident, kwargs] = self._params
|
|
1712
|
-
if scheme:
|
|
1713
|
-
for rel in scheme:
|
|
1714
|
-
args[rel] = kwargs[rel._short_name]
|
|
1715
|
-
else:
|
|
1716
|
-
for k, v in kwargs.items():
|
|
1717
|
-
atr = getattr(self._op, k)
|
|
1718
|
-
if atr:
|
|
1719
|
-
args[atr] = v
|
|
1720
|
-
if ident:
|
|
1721
|
-
args[self._op] = ident
|
|
1722
|
-
return args
|
|
1723
|
-
|
|
1724
|
-
def _dir_extras_from_get_relationship(self):
|
|
1725
|
-
return self._op._dir_extras_from_getattr()
|
|
1726
|
-
|
|
1727
|
-
def _get_relationship(self, name: str) -> Relationship|RelationshipRef:
|
|
1728
|
-
parent_rel = getattr(self._op, name)
|
|
1729
|
-
return RelationshipRef(self, parent_rel)
|
|
1730
|
-
|
|
1731
|
-
def _dir_extras_from_getattr(self):
|
|
1732
|
-
return Producer._dir_extras_from_getattr(self)
|
|
1733
|
-
|
|
1734
|
-
def __getattr__(self, name: str):
|
|
1735
|
-
return Producer.__getattr__(self, name)
|
|
1736
|
-
|
|
1737
|
-
class ConceptMember(ConceptExpression):
|
|
1738
|
-
def __init__(self, con:Concept, identity:Any, kwargs:dict[str, Any]):
|
|
1739
|
-
super().__init__(con, identity, kwargs)
|
|
1740
|
-
if identity is None:
|
|
1741
|
-
class_name = con._name
|
|
1742
|
-
raise ValueError(f"Adding or looking up an instance of Concept requires an identity. If you want to create a new identity, use {class_name}.new(..)")
|
|
1743
|
-
# TODO: when we do reference schemes, the identity might be
|
|
1744
|
-
# in a combination of kwargs rather than in the positionals
|
|
1745
|
-
|
|
1746
|
-
|
|
1747
|
-
class ConceptNew(ConceptExpression):
|
|
1748
|
-
def __str__(self):
|
|
1749
|
-
return f"({self._op}.new {' '.join(map(str, self._params))})"
|
|
1750
|
-
|
|
1751
|
-
|
|
1752
|
-
class ConceptConstruct(ConceptExpression):
|
|
1753
|
-
pass
|
|
1754
|
-
|
|
1755
|
-
class ConceptFilter(ConceptExpression):
|
|
1756
|
-
pass
|
|
1757
|
-
|
|
1758
|
-
#--------------------------------------------------
|
|
1759
|
-
# TupleArg
|
|
1760
|
-
#--------------------------------------------------
|
|
1761
|
-
|
|
1762
|
-
# There are some special relations that require an actual tuple as
|
|
1763
|
-
# an argument. We want to differentiate that from a case where a user
|
|
1764
|
-
# _accidentally_ passes a tuple as an argument.
|
|
1765
|
-
|
|
1766
|
-
class TupleArg(tuple):
|
|
1767
|
-
def _compile_lookup(self, compiler:Compiler, ctx:CompilerContext):
|
|
1768
|
-
return TupleArg(flatten([compiler.lookup(item, ctx) for item in self]))
|
|
1769
|
-
|
|
1770
|
-
#--------------------------------------------------
|
|
1771
|
-
# Aggregate
|
|
1772
|
-
#--------------------------------------------------
|
|
1773
|
-
|
|
1774
|
-
class Aggregate(Producer):
|
|
1775
|
-
def __init__(self, op:Relationship, *args: Any):
|
|
1776
|
-
super().__init__(op._model or find_model(args))
|
|
1777
|
-
self._op = op
|
|
1778
|
-
self._args = args
|
|
1779
|
-
_remove_roots(args)
|
|
1780
|
-
self._group = []
|
|
1781
|
-
self._where = where()
|
|
1782
|
-
|
|
1783
|
-
def where(self, *args: Any) -> Aggregate:
|
|
1784
|
-
new = self.clone()
|
|
1785
|
-
if not new._model:
|
|
1786
|
-
new._model = find_model(args)
|
|
1787
|
-
new._where = new._where.where(*args)
|
|
1788
|
-
return new
|
|
1789
|
-
|
|
1790
|
-
def per(self, *args: Any) -> Aggregate:
|
|
1791
|
-
new = self.clone()
|
|
1792
|
-
if not new._model:
|
|
1793
|
-
new._model = find_model(args)
|
|
1794
|
-
new._group.extend(args)
|
|
1795
|
-
return new
|
|
1796
|
-
|
|
1797
|
-
def clone(self) -> Aggregate:
|
|
1798
|
-
clone = Aggregate(self._op, *self._args)
|
|
1799
|
-
clone._group = self._group.copy()
|
|
1800
|
-
clone._where = self._where
|
|
1801
|
-
return clone
|
|
1802
|
-
|
|
1803
|
-
def _dir_extras_from_getattr(self):
|
|
1804
|
-
return set()
|
|
1805
|
-
|
|
1806
|
-
def __getattr__(self, name: str):
|
|
1807
|
-
raise AttributeError(f"Expression has no attribute {name}")
|
|
1808
|
-
|
|
1809
|
-
def __str__(self):
|
|
1810
|
-
args = ', '.join(map(str, self._args))
|
|
1811
|
-
group = ', '.join(map(str, self._group))
|
|
1812
|
-
where = ""
|
|
1813
|
-
if group:
|
|
1814
|
-
group = f" (per {group})"
|
|
1815
|
-
if self._where._where:
|
|
1816
|
-
items = ', '.join(map(str, self._where._where))
|
|
1817
|
-
where = f" (where {items})"
|
|
1818
|
-
return f"({self._op} {args}{group}{where})"
|
|
1819
|
-
|
|
1820
|
-
class Group():
|
|
1821
|
-
def __init__(self, *args: Any):
|
|
1822
|
-
self._group = args
|
|
1823
|
-
|
|
1824
|
-
def __str__(self):
|
|
1825
|
-
args = ', '.join(map(str, self._group))
|
|
1826
|
-
return f"(per {args})"
|
|
1827
|
-
|
|
1828
|
-
#--------------------------------------------------
|
|
1829
|
-
# Agg funcs
|
|
1830
|
-
#--------------------------------------------------
|
|
1831
|
-
|
|
1832
|
-
def count(self, *args: Any) -> Aggregate:
|
|
1833
|
-
return count(*args).per(*self._group)
|
|
1834
|
-
|
|
1835
|
-
def sum(self, *args: Any) -> Aggregate:
|
|
1836
|
-
return sum(*args).per(*self._group)
|
|
1837
|
-
|
|
1838
|
-
def avg(self, *args: Any) -> Aggregate:
|
|
1839
|
-
return avg(*args).per(*self._group)
|
|
1840
|
-
|
|
1841
|
-
def min(self, *args: Any) -> Aggregate:
|
|
1842
|
-
return min(*args).per(*self._group)
|
|
1843
|
-
|
|
1844
|
-
def max(self, *args: Any) -> Aggregate:
|
|
1845
|
-
return max(*args).per(*self._group)
|
|
1846
|
-
|
|
1847
|
-
#--------------------------------------------------
|
|
1848
|
-
# Aggregate builtins
|
|
1849
|
-
#--------------------------------------------------
|
|
1850
|
-
|
|
1851
|
-
def per(*args: Any) -> Group:
|
|
1852
|
-
return Group(*args)
|
|
1853
|
-
|
|
1854
|
-
def count(*args: Any) -> Aggregate:
|
|
1855
|
-
return Aggregate(Relationship.builtins["count"], *args)
|
|
1856
|
-
|
|
1857
|
-
def sum(*args: Any) -> Aggregate:
|
|
1858
|
-
return Aggregate(Relationship.builtins["sum"], *args)
|
|
1859
|
-
|
|
1860
|
-
def avg(*args: Any) -> Aggregate:
|
|
1861
|
-
return Aggregate(Relationship.builtins["avg"], *args)
|
|
1862
|
-
|
|
1863
|
-
def min(*args: Any) -> Aggregate:
|
|
1864
|
-
return Aggregate(Relationship.builtins["min"], *args)
|
|
1865
|
-
|
|
1866
|
-
def max(*args: Any) -> Aggregate:
|
|
1867
|
-
return Aggregate(Relationship.builtins["max"], *args)
|
|
1868
|
-
|
|
1869
|
-
def experimental_warning(feature: str):
|
|
1870
|
-
rich.print(f"[yellow]Warning:[/yellow] Early access feature '[red]{feature}[/red]' is not yet stable.", file=sys.stderr)
|
|
1871
|
-
|
|
1872
|
-
class RankOrder():
|
|
1873
|
-
ASC = True
|
|
1874
|
-
DESC = False
|
|
1875
|
-
|
|
1876
|
-
def __init__(self, is_asc:bool, *args: Any):
|
|
1877
|
-
self._is_asc = is_asc
|
|
1878
|
-
self._args = args
|
|
1879
|
-
|
|
1880
|
-
def __str__(self):
|
|
1881
|
-
return f"({'asc' if self._is_asc else 'desc'} {', '.join(map(str, self._args))})"
|
|
1882
|
-
|
|
1883
|
-
def asc(*args: Any):
|
|
1884
|
-
experimental_warning("asc")
|
|
1885
|
-
return RankOrder(True, *args)
|
|
1886
|
-
|
|
1887
|
-
def desc(*args: Any):
|
|
1888
|
-
experimental_warning("desc")
|
|
1889
|
-
return RankOrder(False, *args)
|
|
1890
|
-
|
|
1891
|
-
def rank(*args: Any) -> Aggregate:
|
|
1892
|
-
experimental_warning("rank")
|
|
1893
|
-
# A relation is needed further down the pipeline, so we create a dummy one here.
|
|
1894
|
-
dummy_ir_relation = f.relation("rank", [f.field("result", types.Int128)])
|
|
1895
|
-
dummy_relation = Relationship(dummy_ir_relation.name, ir_relation=dummy_ir_relation)
|
|
1896
|
-
return Aggregate(dummy_relation, *args)
|
|
1897
|
-
|
|
1898
|
-
#--------------------------------------------------
|
|
1899
|
-
# Alias
|
|
1900
|
-
#--------------------------------------------------
|
|
1901
|
-
|
|
1902
|
-
class Alias(Producer):
|
|
1903
|
-
def __init__(self, thing:Producer, name:str):
|
|
1904
|
-
super().__init__(thing._model)
|
|
1905
|
-
self._thing = thing
|
|
1906
|
-
self._name = name
|
|
1907
|
-
_remove_roots([thing])
|
|
1908
|
-
|
|
1909
|
-
def __str__(self) -> str:
|
|
1910
|
-
return f"{self._thing} as {self._name}"
|
|
1911
|
-
|
|
1912
|
-
def __setattr__(self, name: str, value: Any) -> None:
|
|
1913
|
-
if name.startswith("_"):
|
|
1914
|
-
super().__setattr__(name, value)
|
|
1915
|
-
else:
|
|
1916
|
-
raise AttributeError(f"Cannot set attribute {name} on {type(self).__name__}")
|
|
1917
|
-
|
|
1918
|
-
#--------------------------------------------------
|
|
1919
|
-
# Match
|
|
1920
|
-
#--------------------------------------------------
|
|
1921
|
-
|
|
1922
|
-
class BranchRef(Producer):
|
|
1923
|
-
def __init__(self, match:Match, ix:int):
|
|
1924
|
-
super().__init__(match._model)
|
|
1925
|
-
self._match = match
|
|
1926
|
-
self._ix = ix
|
|
1927
|
-
|
|
1928
|
-
def __str__(self):
|
|
1929
|
-
return f"{self._match}#{self._ix}"
|
|
1930
|
-
|
|
1931
|
-
class Match(Producer):
|
|
1932
|
-
def __init__(self, *args: Any):
|
|
1933
|
-
super().__init__(find_model(args))
|
|
1934
|
-
self._args = list(self._flatten_args(args))
|
|
1935
|
-
if any(isinstance(arg, Fragment) and arg._is_effect() for arg in self._args):
|
|
1936
|
-
_add_root(self)
|
|
1937
|
-
_remove_roots(args)
|
|
1938
|
-
|
|
1939
|
-
# check for validity
|
|
1940
|
-
is_select = None
|
|
1941
|
-
ret_count = 0
|
|
1942
|
-
for arg in self._args:
|
|
1943
|
-
if isinstance(arg, Fragment) and arg._is_effect():
|
|
1944
|
-
if is_select:
|
|
1945
|
-
raise ValueError("Cannot mix expression and effect clauses in a match")
|
|
1946
|
-
is_select = False
|
|
1947
|
-
elif isinstance(arg, Fragment) and not arg._is_effect():
|
|
1948
|
-
if is_select is False:
|
|
1949
|
-
raise ValueError("Cannot mix effect and expression clauses in a match")
|
|
1950
|
-
is_select = True
|
|
1951
|
-
if ret_count == 0:
|
|
1952
|
-
ret_count = len(arg._select)
|
|
1953
|
-
elif ret_count != len(arg._select):
|
|
1954
|
-
raise ValueError("All clauses must have the same number of return values")
|
|
1955
|
-
elif isinstance(arg, PY_LITERAL_TYPES):
|
|
1956
|
-
if is_select is False:
|
|
1957
|
-
raise ValueError("Cannot mix then and select clauses in a match")
|
|
1958
|
-
is_select = True
|
|
1959
|
-
if ret_count == 0:
|
|
1960
|
-
ret_count = 1
|
|
1961
|
-
elif ret_count != 1:
|
|
1962
|
-
raise ValueError("All clauses must have the same number of return values")
|
|
1963
|
-
elif isinstance(arg, Expression) or isinstance(arg, Aggregate):
|
|
1964
|
-
if is_select is None:
|
|
1965
|
-
is_select = True
|
|
1966
|
-
if not arg._op._is_filter():
|
|
1967
|
-
ret_count = 1
|
|
1968
|
-
elif isinstance(arg, Relationship) or isinstance(arg, Ref):
|
|
1969
|
-
if is_select is None:
|
|
1970
|
-
is_select = True
|
|
1971
|
-
ret_count = 1
|
|
1972
|
-
|
|
1973
|
-
self._is_select = is_select
|
|
1974
|
-
self._ret_count = ret_count
|
|
1975
|
-
self._source = runtime_env.get_source_pos()
|
|
1976
|
-
|
|
1977
|
-
def _flatten_args(self, args):
|
|
1978
|
-
for arg in args:
|
|
1979
|
-
if isinstance(arg, Match):
|
|
1980
|
-
for sub_arg in arg._args:
|
|
1981
|
-
yield sub_arg
|
|
1982
|
-
else:
|
|
1983
|
-
yield arg
|
|
1984
|
-
|
|
1985
|
-
def __iter__(self):
|
|
1986
|
-
for ix in range(self._ret_count):
|
|
1987
|
-
yield BranchRef(self, ix)
|
|
1988
|
-
|
|
1989
|
-
def __str__(self):
|
|
1990
|
-
return " | ".join(map(str, self._args))
|
|
1991
|
-
|
|
1992
|
-
#--------------------------------------------------
|
|
1993
|
-
# Union
|
|
1994
|
-
#--------------------------------------------------
|
|
1995
|
-
|
|
1996
|
-
class Union(Match):
|
|
1997
|
-
def __str__(self):
|
|
1998
|
-
return f"union({', '.join(map(str, self._args))})"
|
|
1999
|
-
|
|
2000
|
-
def union(*args: Any) -> Union:
|
|
2001
|
-
if len(args) == 1:
|
|
2002
|
-
return args[0]
|
|
2003
|
-
return Union(*args)
|
|
2004
|
-
|
|
2005
|
-
#--------------------------------------------------
|
|
2006
|
-
# Negation
|
|
2007
|
-
#--------------------------------------------------
|
|
2008
|
-
|
|
2009
|
-
class Not():
|
|
2010
|
-
def __init__(self, *args: Any):
|
|
2011
|
-
self._args = args
|
|
2012
|
-
self._model = find_model(args)
|
|
2013
|
-
_remove_roots(args)
|
|
2014
|
-
|
|
2015
|
-
def clone(self) -> Not:
|
|
2016
|
-
clone = type(self)(*self._args)
|
|
2017
|
-
return clone
|
|
2018
|
-
|
|
2019
|
-
def __or__(self, other) -> Match:
|
|
2020
|
-
return Match(self, other)
|
|
2021
|
-
|
|
2022
|
-
def __and__(self, other) -> Fragment:
|
|
2023
|
-
if isinstance(other, Fragment):
|
|
2024
|
-
return other.where(self)
|
|
2025
|
-
return where(self, other)
|
|
2026
|
-
|
|
2027
|
-
def __str__(self):
|
|
2028
|
-
args_str = '\n '.join(map(str, self._args))
|
|
2029
|
-
return f"(not {args_str})"
|
|
2030
|
-
|
|
2031
|
-
def not_(*args: Any) -> Not:
|
|
2032
|
-
return Not(*args)
|
|
2033
|
-
|
|
2034
|
-
#--------------------------------------------------
|
|
2035
|
-
# Distinct
|
|
2036
|
-
#--------------------------------------------------
|
|
2037
|
-
|
|
2038
|
-
class Distinct():
|
|
2039
|
-
def __init__(self, *args: Any):
|
|
2040
|
-
self._args = args
|
|
2041
|
-
self._model = find_model(args)
|
|
2042
|
-
_remove_roots(args)
|
|
2043
|
-
|
|
2044
|
-
def distinct(*args: Any) -> Distinct:
|
|
2045
|
-
return Distinct(*args)
|
|
2046
|
-
|
|
2047
|
-
#--------------------------------------------------
|
|
2048
|
-
# Enum
|
|
2049
|
-
#--------------------------------------------------
|
|
2050
|
-
|
|
2051
|
-
def create_enum_class(model: Model):
|
|
2052
|
-
|
|
2053
|
-
class ModelEnumMeta(EnumMeta):
|
|
2054
|
-
_concept: Concept
|
|
2055
|
-
def __setattr__(self, name: str, value: Any) -> None:
|
|
2056
|
-
if name.startswith("_") or isinstance(value, self):
|
|
2057
|
-
super().__setattr__(name, value)
|
|
2058
|
-
elif isinstance(value, (Relationship, RelationshipReading)):
|
|
2059
|
-
value._parent = self._concept
|
|
2060
|
-
if not value._passed_short_name:
|
|
2061
|
-
value._passed_short_name = name
|
|
2062
|
-
if name in self._concept._relationships:
|
|
2063
|
-
raise ValueError(
|
|
2064
|
-
f"Cannot set attribute {name} on {type(self).__name__} a second time. Make sure to set the relationship before any usages occur")
|
|
2065
|
-
self._concept._relationships[name] = value
|
|
2066
|
-
else:
|
|
2067
|
-
raise AttributeError(f"Cannot set attribute {name} on {type(self).__name__}")
|
|
2068
|
-
|
|
2069
|
-
class ModelEnum(Enum, metaclass=ModelEnumMeta):
|
|
2070
|
-
def __init_subclass__(cls, **kwargs):
|
|
2071
|
-
super().__init_subclass__(**kwargs)
|
|
2072
|
-
# this is voodoo black magic that is doing meta meta programming where
|
|
2073
|
-
# we are plugging into anytime a new subtype of this class is created
|
|
2074
|
-
# and then creating a concept to represent the enum. This happens both
|
|
2075
|
-
# when you do `class Foo(Enum)` and when you do `Enum("Foo", [a, b, c])`
|
|
2076
|
-
c = model.Concept(
|
|
2077
|
-
cls.__name__,
|
|
2078
|
-
extends=[Concept.builtins["Enum"]],
|
|
2079
|
-
identify_by={"name": Concept.builtins["String"]}
|
|
2080
|
-
)
|
|
2081
|
-
cls._concept = model.enum_concept[cls] = c
|
|
2082
|
-
model.enums[cls.__name__] = cls
|
|
2083
|
-
cls._has_inited_members = False
|
|
2084
|
-
|
|
2085
|
-
# Python 3.10 doesn't correctly populate __members__ by the time it calls
|
|
2086
|
-
# __init_subclass__, so we need to initialize the members lazily when we
|
|
2087
|
-
# encounter the enum for the first time.
|
|
2088
|
-
def _init_members(self):
|
|
2089
|
-
if self._has_inited_members:
|
|
2090
|
-
return
|
|
2091
|
-
cls = self.__class__
|
|
2092
|
-
c = cls._concept
|
|
2093
|
-
# Add the name and value attributes to the hashes we create for the enum
|
|
2094
|
-
members = [
|
|
2095
|
-
c.new(name=name, value=value.value)
|
|
2096
|
-
for name, value in cls.__members__.items()
|
|
2097
|
-
]
|
|
2098
|
-
with root_tracking(True):
|
|
2099
|
-
model.define(*members)
|
|
2100
|
-
cls._has_inited_members = True
|
|
2101
|
-
|
|
2102
|
-
def _compile_lookup(self, compiler:Compiler, ctx:CompilerContext):
|
|
2103
|
-
self._init_members()
|
|
2104
|
-
concept = getattr(self.__class__, "_concept")
|
|
2105
|
-
return compiler.lookup(concept.new(name=self.name), ctx)
|
|
2106
|
-
|
|
2107
|
-
@classmethod
|
|
2108
|
-
def lookup(cls, value:Producer|str):
|
|
2109
|
-
concept = cls._concept
|
|
2110
|
-
return concept.new(name=value)
|
|
2111
|
-
|
|
2112
|
-
return ModelEnum
|
|
2113
|
-
|
|
2114
|
-
#--------------------------------------------------
|
|
2115
|
-
# Data
|
|
2116
|
-
#--------------------------------------------------
|
|
2117
|
-
|
|
2118
|
-
class DataColumn(Producer):
|
|
2119
|
-
def __init__(self, data:Data, _type, name:str):
|
|
2120
|
-
self._data = data
|
|
2121
|
-
self._type = _type
|
|
2122
|
-
self._name = name if isinstance(name, str) else f"v{name}"
|
|
2123
|
-
if pd.api.types.is_datetime64_any_dtype(_type):
|
|
2124
|
-
_type = _np_datetime
|
|
2125
|
-
# dates are objects in pandas
|
|
2126
|
-
elif pd.api.types.is_object_dtype(_type) and self._is_date_column():
|
|
2127
|
-
_type = date
|
|
2128
|
-
self._ref = python_types_to_concepts[_type].ref(self._name)
|
|
2129
|
-
|
|
2130
|
-
def _is_date_column(self) -> bool:
|
|
2131
|
-
sample = self._data._data[self._name].dropna()
|
|
2132
|
-
if sample.empty:
|
|
2133
|
-
return False
|
|
2134
|
-
sample_value = sample.iloc[0]
|
|
2135
|
-
return isinstance(sample_value, date) and not isinstance(sample_value, datetime)
|
|
2136
|
-
|
|
2137
|
-
def __str__(self):
|
|
2138
|
-
return f"DataColumn({self._name}, {self._type})"
|
|
2139
|
-
|
|
2140
|
-
class Data(Producer):
|
|
2141
|
-
def __init__(self, data:DataFrame):
|
|
2142
|
-
super().__init__(None)
|
|
2143
|
-
self._data = data
|
|
2144
|
-
self._relationships = {}
|
|
2145
|
-
self._cols : list[DataColumn] = []
|
|
2146
|
-
self._row_id = Integer.ref("row_id")
|
|
2147
|
-
for col in data.columns:
|
|
2148
|
-
t = data[col].dtype
|
|
2149
|
-
self._cols.append(DataColumn(self, t, col))
|
|
2150
|
-
self._relationships[col] = self._cols[-1]
|
|
2151
|
-
|
|
2152
|
-
def into(self, concept:Concept, keys:list[str]=[]):
|
|
2153
|
-
if keys:
|
|
2154
|
-
new = concept.to_identity(**{k.lower(): getattr(self, k) for k in keys})
|
|
2155
|
-
else:
|
|
2156
|
-
new = concept.to_identity(self._row_id)
|
|
2157
|
-
where(self, new).define(
|
|
2158
|
-
concept(new),
|
|
2159
|
-
*[getattr(concept, col._name)(new, col) for col in self._cols]
|
|
2160
|
-
)
|
|
2161
|
-
|
|
2162
|
-
def __getitem__(self, item: str|int) -> DataColumn:
|
|
2163
|
-
if isinstance(item, int):
|
|
2164
|
-
return self._cols[item]
|
|
2165
|
-
if item in self._relationships:
|
|
2166
|
-
return self._relationships[item]
|
|
2167
|
-
raise KeyError(f"Data has no column {item}")
|
|
2168
|
-
|
|
2169
|
-
def _dir_extras_from_get_relationship(self):
|
|
2170
|
-
return set()
|
|
2171
|
-
|
|
2172
|
-
def _get_relationship(self, name: str) -> Relationship | RelationshipRef | RelationshipFieldRef:
|
|
2173
|
-
raise AttributeError(f"Data has no attribute {name}")
|
|
2174
|
-
|
|
2175
|
-
def __str__(self):
|
|
2176
|
-
return f"Data({len(self._data)} rows, [{', '.join([str(c) for c in self._cols])}])"
|
|
2177
|
-
|
|
2178
|
-
def __hash__(self):
|
|
2179
|
-
return hash(self._id)
|
|
2180
|
-
|
|
2181
|
-
def _to_df(data: DataFrame | list[tuple] | list[dict], columns:list[str]|None) -> DataFrame:
|
|
2182
|
-
if isinstance(data, DataFrame):
|
|
2183
|
-
return data
|
|
2184
|
-
if not data:
|
|
2185
|
-
return DataFrame()
|
|
2186
|
-
if isinstance(data, list):
|
|
2187
|
-
if isinstance(data[0], tuple):
|
|
2188
|
-
# Named tuple check
|
|
2189
|
-
if hasattr(data[0], '_fields'):
|
|
2190
|
-
return DataFrame([t._asdict() for t in data]) #type: ignore
|
|
2191
|
-
return DataFrame(data, columns=columns)
|
|
2192
|
-
elif isinstance(data[0], dict):
|
|
2193
|
-
return DataFrame(data)
|
|
2194
|
-
raise TypeError(f"Cannot convert {type(data)} to DataFrame. Use DataFrame, list of tuples, or list of dicts.")
|
|
2195
|
-
|
|
2196
|
-
def data(data:DataFrame|list[tuple]|list[dict], columns:list[str]|None=None) -> Data:
|
|
2197
|
-
return Data(_to_df(data, columns))
|
|
2198
|
-
|
|
2199
|
-
#--------------------------------------------------
|
|
2200
|
-
# Constraints
|
|
2201
|
-
#--------------------------------------------------
|
|
2202
|
-
class Constraint:
|
|
2203
|
-
"""Base class for constraints"""
|
|
2204
|
-
|
|
2205
|
-
def __init__(self, model:Model|None=None):
|
|
2206
|
-
self._model = model or find_model(fields)
|
|
2207
|
-
if self._model:
|
|
2208
|
-
self._model.constraints.add(self)
|
|
2209
|
-
|
|
2210
|
-
@property
|
|
2211
|
-
def _id(self):
|
|
2212
|
-
"""Returns unique id for this constraint"""
|
|
2213
|
-
raise NotImplementedError(f"`{type(self).__name__}._id` not implemented")
|
|
2214
|
-
|
|
2215
|
-
@property
|
|
2216
|
-
def _relationship(self) -> Relationship:
|
|
2217
|
-
"""Returns builtin relationship for this constraint"""
|
|
2218
|
-
raise NotImplementedError(f"`{type(self).__name__}._relationship` not implemented")
|
|
2219
|
-
|
|
2220
|
-
def to_expressions(self) -> tuple[Expression]:
|
|
2221
|
-
"""Returns Expressions for this constraint"""
|
|
2222
|
-
raise NotImplementedError(f"`{type(self).__name__}.to_expressions` not implemented")
|
|
2223
|
-
|
|
2224
|
-
def __eq__(self, other):
|
|
2225
|
-
if isinstance(other, Constraint):
|
|
2226
|
-
return self._id == other._id
|
|
2227
|
-
return False
|
|
2228
|
-
|
|
2229
|
-
def __hash__(self):
|
|
2230
|
-
return hash(self._id)
|
|
2231
|
-
|
|
2232
|
-
class FieldsConstraint(Constraint):
|
|
2233
|
-
|
|
2234
|
-
def __init__(self, *fields: RelationshipFieldRef, model:Model|None=None):
|
|
2235
|
-
self._init_constraint(*fields)
|
|
2236
|
-
super().__init__(model)
|
|
2237
|
-
|
|
2238
|
-
def _init_constraint(self, *fields: RelationshipFieldRef) -> None:
|
|
2239
|
-
self._fields = fields
|
|
2240
|
-
ids = [field._field_ref._id for field in fields]
|
|
2241
|
-
ids.sort() # ensures order doesn't matter
|
|
2242
|
-
self._stable_id = f'{type(self).__name__}:{ids}'
|
|
2243
|
-
|
|
2244
|
-
@property
|
|
2245
|
-
def _id(self):
|
|
2246
|
-
return self._stable_id
|
|
2247
|
-
|
|
2248
|
-
def _to_field_expressions(self, fields: tuple[RelationshipFieldRef, ...]) -> Expression:
|
|
2249
|
-
return Expression(self._relationship, TupleArg(fields))
|
|
2250
|
-
|
|
2251
|
-
@classmethod
|
|
2252
|
-
def to_identity(cls, *fields: RelationshipFieldRef):
|
|
2253
|
-
obj = cls.__new__(cls)
|
|
2254
|
-
obj._init_constraint(*fields)
|
|
2255
|
-
return obj
|
|
2256
|
-
|
|
2257
|
-
class Unique(FieldsConstraint):
|
|
2258
|
-
|
|
2259
|
-
def __init__(self, *fields: RelationshipFieldRef, model:Model|None=None):
|
|
2260
|
-
first_field_rel = fields[0]._relationship
|
|
2261
|
-
self._internal = all(first_field_rel._is_same_relationship(f._relationship) for f in fields[1:])
|
|
2262
|
-
super().__init__(*fields, model=model)
|
|
2263
|
-
if self._internal:
|
|
2264
|
-
relationship = first_field_rel._alt_of if isinstance(first_field_rel, RelationshipReading) else first_field_rel
|
|
2265
|
-
relationship._internal_constraints.add(self)
|
|
2266
|
-
|
|
2267
|
-
def internal(self) -> bool:
|
|
2268
|
-
return self._internal
|
|
2269
|
-
|
|
2270
|
-
def to_expressions(self) -> tuple[Expression]:
|
|
2271
|
-
if self.internal():
|
|
2272
|
-
expressions = []
|
|
2273
|
-
rel = self._fields[0]._relationship
|
|
2274
|
-
relationship = rel._alt_of if isinstance(rel, RelationshipReading) else rel
|
|
2275
|
-
# create Expression from the relationship
|
|
2276
|
-
expressions.append(self._to_field_expressions(self._find_fields(relationship)))
|
|
2277
|
-
# skip the first implicitly created reading and create Expressions for all other readings
|
|
2278
|
-
for reading in relationship._readings[1:]:
|
|
2279
|
-
expressions.append(self._to_field_expressions(self._find_fields(reading)))
|
|
2280
|
-
return tuple(expressions)
|
|
2281
|
-
else:
|
|
2282
|
-
return (self._to_field_expressions(self._fields),)
|
|
2283
|
-
|
|
2284
|
-
@property
|
|
2285
|
-
def _relationship(self) -> Relationship:
|
|
2286
|
-
return Relationship.builtins[builtins.unique.name]
|
|
2287
|
-
|
|
2288
|
-
def _find_fields(self, rel:Relationship|RelationshipReading) -> tuple[RelationshipFieldRef]:
|
|
2289
|
-
rel_fields = []
|
|
2290
|
-
for field in self._fields:
|
|
2291
|
-
assert isinstance(field._field_ref, Ref) and isinstance(field._field_ref._name, str)
|
|
2292
|
-
rel_fields.append(rel[field._field_ref._name])
|
|
2293
|
-
return tuple(rel_fields)
|
|
2294
|
-
|
|
2295
|
-
class SubtypeConstraint(Constraint):
|
|
2296
|
-
|
|
2297
|
-
def __init__(self, *concepts: Concept, model:Model|None=None):
|
|
2298
|
-
if len(concepts) < 2:
|
|
2299
|
-
raise ValueError("Invalid subtype constraint. A constraint should hold at least 2 concepts")
|
|
2300
|
-
first = concepts[0]
|
|
2301
|
-
first_super_types = [super_type._id for super_type in first._extends]
|
|
2302
|
-
for c in concepts:
|
|
2303
|
-
if len(c._extends) == 0:
|
|
2304
|
-
raise ValueError(f"Invalid subtype constraint. '{c}' is not a subtype")
|
|
2305
|
-
c_super_types = [super_type._id for super_type in c._extends]
|
|
2306
|
-
if first_super_types != c_super_types:
|
|
2307
|
-
raise ValueError(f"Invalid subtype constraint. '{first}' and '{c}' must have the same parents")
|
|
2308
|
-
self._concepts = concepts
|
|
2309
|
-
ids = [c._id for c in concepts]
|
|
2310
|
-
ids.sort() # ensures order doesn't matter
|
|
2311
|
-
self._stable_id = f'{type(self).__name__}:{ids}'
|
|
2312
|
-
super().__init__(model)
|
|
2313
|
-
|
|
2314
|
-
@property
|
|
2315
|
-
def _id(self):
|
|
2316
|
-
return self._stable_id
|
|
2317
|
-
|
|
2318
|
-
def to_expressions(self) -> tuple[Expression]:
|
|
2319
|
-
return (Expression(self._relationship, TupleArg(self._concepts)),)
|
|
2320
|
-
|
|
2321
|
-
class Exclusive(SubtypeConstraint):
|
|
2322
|
-
|
|
2323
|
-
def __init__(self, *concepts: Concept, model:Model|None=None):
|
|
2324
|
-
super().__init__(*concepts, model=model)
|
|
2325
|
-
|
|
2326
|
-
@property
|
|
2327
|
-
def _relationship(self) -> Relationship:
|
|
2328
|
-
return Relationship.builtins[builtins.exclusive.name]
|
|
2329
|
-
|
|
2330
|
-
class Anyof(SubtypeConstraint):
|
|
2331
|
-
|
|
2332
|
-
def __init__(self, *concepts: Concept, model:Model|None=None):
|
|
2333
|
-
super().__init__(*concepts, model=model)
|
|
2334
|
-
|
|
2335
|
-
@property
|
|
2336
|
-
def _relationship(self) -> Relationship:
|
|
2337
|
-
return Relationship.builtins[builtins.anyof.name]
|
|
2338
|
-
|
|
2339
|
-
#--------------------------------------------------
|
|
2340
|
-
# Fragment
|
|
2341
|
-
#--------------------------------------------------
|
|
2342
|
-
|
|
2343
|
-
class Fragment():
|
|
2344
|
-
def __init__(self, parent:Fragment|None=None, model:Model|None=None):
|
|
2345
|
-
self._id = next(_global_id)
|
|
2346
|
-
self._select = []
|
|
2347
|
-
self._where = []
|
|
2348
|
-
self._require = []
|
|
2349
|
-
self._define = []
|
|
2350
|
-
self._order_by = []
|
|
2351
|
-
self._limit = 0
|
|
2352
|
-
self._model = parent._model if parent else model
|
|
2353
|
-
self._parent = parent
|
|
2354
|
-
self._source = runtime_env.get_source_pos()
|
|
2355
|
-
self._is_export = False
|
|
2356
|
-
self._meta = {}
|
|
2357
|
-
self._annotations = []
|
|
2358
|
-
if parent:
|
|
2359
|
-
self._select.extend(parent._select)
|
|
2360
|
-
self._where.extend(parent._where)
|
|
2361
|
-
self._require.extend(parent._require)
|
|
2362
|
-
self._define.extend(parent._define)
|
|
2363
|
-
self._order_by.extend(parent._order_by)
|
|
2364
|
-
self._limit = parent._limit
|
|
2365
|
-
self._meta.update(parent._meta)
|
|
2366
|
-
|
|
2367
|
-
def _add_items(self, items:PySequence[Any], to_attr:list[Any]):
|
|
2368
|
-
# TODO: ensure that you are _either_ a select, require, or then
|
|
2369
|
-
# not a mix of them
|
|
2370
|
-
_remove_roots(items)
|
|
2371
|
-
to_attr.extend(items)
|
|
2372
|
-
|
|
2373
|
-
if self._define or self._require:
|
|
2374
|
-
if self._parent:
|
|
2375
|
-
_remove_roots([self._parent])
|
|
2376
|
-
_add_root(self)
|
|
2377
|
-
|
|
2378
|
-
if not self._model:
|
|
2379
|
-
self._model = find_model(items)
|
|
2380
|
-
return self
|
|
2381
|
-
|
|
2382
|
-
def where(self, *args: Any) -> Fragment:
|
|
2383
|
-
f = Fragment(parent=self)
|
|
2384
|
-
return f._add_items(args, f._where)
|
|
2385
|
-
|
|
2386
|
-
def select(self, *args: Any) -> Fragment:
|
|
2387
|
-
# Check for Not instances in select arguments (including nested)
|
|
2388
|
-
def _contains_not(item: Any) -> bool:
|
|
2389
|
-
if isinstance(item, Not):
|
|
2390
|
-
return True
|
|
2391
|
-
elif isinstance(item, (Match, Union)):
|
|
2392
|
-
return any(_contains_not(arg) for arg in item._args)
|
|
2393
|
-
elif isinstance(item, Fragment):
|
|
2394
|
-
# Check all fragment components for not_
|
|
2395
|
-
return (
|
|
2396
|
-
any(_contains_not(arg) for arg in item._select)
|
|
2397
|
-
or any(_contains_not(arg) for arg in item._require)
|
|
2398
|
-
or any(_contains_not(arg) for arg in item._define)
|
|
2399
|
-
or any(_contains_not(arg) for arg in item._order_by)
|
|
2400
|
-
)
|
|
2401
|
-
elif isinstance(item, Expression):
|
|
2402
|
-
return any(_contains_not(param) for param in item._params)
|
|
2403
|
-
elif isinstance(item, Aggregate):
|
|
2404
|
-
return (
|
|
2405
|
-
any(_contains_not(arg) for arg in item._args)
|
|
2406
|
-
or any(_contains_not(arg) for arg in item._group)
|
|
2407
|
-
or _contains_not(item._where)
|
|
2408
|
-
)
|
|
2409
|
-
elif isinstance(item, Group):
|
|
2410
|
-
return any(_contains_not(arg) for arg in item._group)
|
|
2411
|
-
elif isinstance(item, (list, tuple)):
|
|
2412
|
-
return any(_contains_not(subitem) for subitem in item)
|
|
2413
|
-
else:
|
|
2414
|
-
return False
|
|
2415
|
-
|
|
2416
|
-
for arg in args:
|
|
2417
|
-
if _contains_not(arg):
|
|
2418
|
-
raise ValueError("`not_` is not allowed in `select` fragments")
|
|
2419
|
-
|
|
2420
|
-
f = Fragment(parent=self)
|
|
2421
|
-
return f._add_items(args, f._select)
|
|
2422
|
-
|
|
2423
|
-
def require(self, *args: Any) -> Fragment:
|
|
2424
|
-
f = Fragment(parent=self)
|
|
2425
|
-
# todo: find a better way to pass multi Expressions to require
|
|
2426
|
-
return f._add_items(tuple(flatten(args)), f._require)
|
|
2427
|
-
|
|
2428
|
-
def define(self, *args: Any) -> Fragment:
|
|
2429
|
-
f = Fragment(parent=self)
|
|
2430
|
-
return f._add_items(args, f._define)
|
|
2431
|
-
|
|
2432
|
-
def order_by(self, *args: Any) -> Fragment:
|
|
2433
|
-
experimental_warning("order_by")
|
|
2434
|
-
f = Fragment(parent=self)
|
|
2435
|
-
return f._add_items(args, f._order_by)
|
|
2436
|
-
|
|
2437
|
-
def limit(self, n:int) -> Fragment:
|
|
2438
|
-
experimental_warning("limit")
|
|
2439
|
-
f = Fragment(parent=self)
|
|
2440
|
-
f._limit = n
|
|
2441
|
-
return f
|
|
2442
|
-
|
|
2443
|
-
def meta(self, **kwargs: Any) -> Fragment:
|
|
2444
|
-
"""Add metadata to the query.
|
|
2445
|
-
|
|
2446
|
-
Metadata can be used for debugging and observability purposes.
|
|
2447
|
-
|
|
2448
|
-
Args:
|
|
2449
|
-
**kwargs: Metadata key-value pairs
|
|
2450
|
-
|
|
2451
|
-
Returns:
|
|
2452
|
-
Fragment: Returns self for method chaining
|
|
2453
|
-
|
|
2454
|
-
Example:
|
|
2455
|
-
select(Person.name).meta(workload_name="test", priority=1, enabled=True)
|
|
2456
|
-
"""
|
|
2457
|
-
if not kwargs:
|
|
2458
|
-
raise ValueError("meta() requires at least one argument")
|
|
2459
|
-
|
|
2460
|
-
self._meta.update(kwargs)
|
|
2461
|
-
return self
|
|
2462
|
-
|
|
2463
|
-
|
|
2464
|
-
def annotate(self, *annos:Expression|Relationship|ir.Annotation) -> Fragment:
|
|
2465
|
-
self._annotations.extend(annos)
|
|
2466
|
-
return self
|
|
2467
|
-
|
|
2468
|
-
#--------------------------------------------------
|
|
2469
|
-
# helpers
|
|
2470
|
-
#--------------------------------------------------
|
|
2471
|
-
|
|
2472
|
-
def _is_effect(self) -> bool:
|
|
2473
|
-
return bool(self._define or self._require or (self._parent and self._parent._is_effect()))
|
|
2474
|
-
|
|
2475
|
-
def _is_where_only(self) -> bool:
|
|
2476
|
-
return not self._select and not self._define and not self._require and not self._order_by
|
|
2477
|
-
|
|
2478
|
-
#--------------------------------------------------
|
|
2479
|
-
# And/Or
|
|
2480
|
-
#--------------------------------------------------
|
|
2481
|
-
|
|
2482
|
-
def __or__(self, other) -> Match:
|
|
2483
|
-
return Match(self, other)
|
|
2484
|
-
|
|
2485
|
-
def __and__(self, other) -> Fragment:
|
|
2486
|
-
if isinstance(other, Fragment):
|
|
2487
|
-
return other.where(self)
|
|
2488
|
-
return where(self, other)
|
|
2489
|
-
|
|
2490
|
-
#--------------------------------------------------
|
|
2491
|
-
# Stringify
|
|
2492
|
-
#--------------------------------------------------
|
|
2493
|
-
|
|
2494
|
-
def __str__(self):
|
|
2495
|
-
sections = []
|
|
2496
|
-
if self._select:
|
|
2497
|
-
select = '\n '.join(map(str, self._select))
|
|
2498
|
-
sections.append(f"(select\n {select})")
|
|
2499
|
-
if self._where:
|
|
2500
|
-
where = '\n '.join(map(str, self._where))
|
|
2501
|
-
sections.append(f"(where\n {where})")
|
|
2502
|
-
if self._require:
|
|
2503
|
-
require = '\n '.join(map(str, self._require))
|
|
2504
|
-
sections.append(f"(require\n {require})")
|
|
2505
|
-
if self._define:
|
|
2506
|
-
effects = '\n '.join(map(str, self._define))
|
|
2507
|
-
sections.append(f"(then\n {effects})")
|
|
2508
|
-
if self._order_by:
|
|
2509
|
-
order_by = '\n '.join(map(str, self._order_by))
|
|
2510
|
-
sections.append(f"(order_by\n {order_by})")
|
|
2511
|
-
if self._limit:
|
|
2512
|
-
sections.append(f"(limit {self._limit})")
|
|
2513
|
-
|
|
2514
|
-
return "\n".join(sections)
|
|
2515
|
-
|
|
2516
|
-
#--------------------------------------------------
|
|
2517
|
-
# Execute
|
|
2518
|
-
#--------------------------------------------------
|
|
2519
|
-
|
|
2520
|
-
def __iter__(self):
|
|
2521
|
-
# Iterate over the rows of the fragment's results
|
|
2522
|
-
return self.to_df().itertuples(index=False).__iter__()
|
|
2523
|
-
|
|
2524
|
-
def inspect(self):
|
|
2525
|
-
# @TODO what format? maybe ignore row indices?
|
|
2526
|
-
print(self.to_df(in_inspect=True))
|
|
2527
|
-
|
|
2528
|
-
def to_df(self, in_inspect:bool=False):
|
|
2529
|
-
"""Convert the fragment's results to a pandas DataFrame."""
|
|
2530
|
-
# @TODO currently this code assumes a Rel executor; should dispatch based on config
|
|
2531
|
-
|
|
2532
|
-
# If there are no selects, then there are no results to return
|
|
2533
|
-
if not self._select:
|
|
2534
|
-
return DataFrame()
|
|
2535
|
-
|
|
2536
|
-
qb_model = self._model or Model("anon")
|
|
2537
|
-
ir_model = qb_model._to_ir()
|
|
2538
|
-
self._source = runtime_env.get_source_pos()
|
|
2539
|
-
# @TODO for now we set tag to None but we need to work out how to properly propagate user-provided tag here
|
|
2540
|
-
with debugging.span("query", tag=None, dsl=str(self), **with_source(self), meta=self._meta) as query_span:
|
|
2541
|
-
query_task = qb_model._compiler.fragment(self)
|
|
2542
|
-
results = qb_model._to_executor().execute(ir_model, query_task, meta=self._meta)
|
|
2543
|
-
query_span["results"] = results
|
|
2544
|
-
# For local debugging mostly
|
|
2545
|
-
dry_run = qb_model._dry_run or bool(qb_model._config.get("compiler.dry_run", False))
|
|
2546
|
-
inspect_df = bool(qb_model._config.get("compiler.inspect_df", False))
|
|
2547
|
-
if not in_inspect and not dry_run and inspect_df:
|
|
2548
|
-
print(results)
|
|
2549
|
-
return results
|
|
2550
|
-
|
|
2551
|
-
def to_snowpark(self):
|
|
2552
|
-
"""
|
|
2553
|
-
Convert the fragment's results to a snowflake DataFrame.
|
|
2554
|
-
`snowflake.snowpark.DataFrame` represents a lazily-evaluated relational dataset.
|
|
2555
|
-
The computation is not performed until you call a method that performs an action (e.g. collect(), to_pandas()).
|
|
2556
|
-
"""
|
|
2557
|
-
# If there are no selects, then there are no results to return
|
|
2558
|
-
if not self._select:
|
|
2559
|
-
return SnowparkDataFrame()
|
|
2560
|
-
|
|
2561
|
-
qb_model = self._model or Model("anon")
|
|
2562
|
-
clone = Fragment(parent=self)
|
|
2563
|
-
clone._is_export = True
|
|
2564
|
-
ir_model = qb_model._to_ir()
|
|
2565
|
-
clone._source = runtime_env.get_source_pos()
|
|
2566
|
-
# @TODO for now we set tag to None but we need to work out how to properly propagate user-provided tag here
|
|
2567
|
-
with debugging.span("query", tag=None, dsl=str(clone), **with_source(clone), meta=clone._meta) as query_span:
|
|
2568
|
-
query_task = qb_model._compiler.fragment(clone)
|
|
2569
|
-
results = qb_model._to_executor().execute(ir_model, query_task, format="snowpark", meta=clone._meta)
|
|
2570
|
-
query_span["alt_format_results"] = results
|
|
2571
|
-
return results
|
|
2572
|
-
|
|
2573
|
-
def into(self, table:Any, update:bool=False) -> None:
|
|
2574
|
-
from .snowflake import Table
|
|
2575
|
-
assert isinstance(table, Table), "Only Snowflake tables are supported for now"
|
|
2576
|
-
|
|
2577
|
-
clone = Fragment(parent=self)
|
|
2578
|
-
clone._is_export = True
|
|
2579
|
-
qb_model = clone._model or Model("anon")
|
|
2580
|
-
ir_model = qb_model._to_ir()
|
|
2581
|
-
clone._source = runtime_env.get_source_pos()
|
|
2582
|
-
with debugging.span("query", dsl=str(clone), **with_source(clone), meta=clone._meta):
|
|
2583
|
-
query_task = qb_model._compiler.fragment(clone)
|
|
2584
|
-
qb_model._to_executor().execute(ir_model, query_task, export_to=table, update=update, meta=clone._meta)
|
|
2585
|
-
|
|
2586
|
-
#--------------------------------------------------
|
|
2587
|
-
# Select / Where
|
|
2588
|
-
#--------------------------------------------------
|
|
2589
|
-
|
|
2590
|
-
def select(*args: Any, parent:Fragment|None=None, model:Model|None=None) -> Fragment:
|
|
2591
|
-
return Fragment(model=model).select(*args)
|
|
2592
|
-
|
|
2593
|
-
def where(*args: Any, parent:Fragment|None=None, model:Model|None=None) -> Fragment:
|
|
2594
|
-
return Fragment(model=model).where(*args)
|
|
2595
|
-
|
|
2596
|
-
def require(*args: Any, parent:Fragment|None=None, model:Model|None=None) -> Fragment:
|
|
2597
|
-
return Fragment(model=model).require(*args)
|
|
2598
|
-
|
|
2599
|
-
def define(*args: Any, parent:Fragment|None=None, model:Model|None=None) -> Fragment:
|
|
2600
|
-
return Fragment(model=model).define(*args)
|
|
2601
|
-
|
|
2602
|
-
#--------------------------------------------------
|
|
2603
|
-
# Model
|
|
2604
|
-
#--------------------------------------------------
|
|
2605
|
-
|
|
2606
|
-
class Model():
|
|
2607
|
-
def __init__(
|
|
2608
|
-
self,
|
|
2609
|
-
name: str,
|
|
2610
|
-
dry_run: bool = False,
|
|
2611
|
-
keep_model: bool = True,
|
|
2612
|
-
use_lqp: bool | None = None,
|
|
2613
|
-
use_sql: bool = False,
|
|
2614
|
-
strict: bool = False,
|
|
2615
|
-
wide_outputs: bool = False,
|
|
2616
|
-
enable_otel_handler: bool | None = None,
|
|
2617
|
-
connection: Session | None = None,
|
|
2618
|
-
config: Config | None = None,
|
|
2619
|
-
):
|
|
2620
|
-
self._id = next(_global_id)
|
|
2621
|
-
self.name = f"{name}{overrides('model_suffix', '')}"
|
|
2622
|
-
self._dry_run = cast(bool, overrides('dry_run', dry_run))
|
|
2623
|
-
self._keep_model = cast(bool, overrides('keep_model', keep_model))
|
|
2624
|
-
self._use_sql = cast(bool, overrides('use_sql', use_sql))
|
|
2625
|
-
self._wide_outputs = cast(bool, overrides('wide_outputs', wide_outputs))
|
|
2626
|
-
self._config = config or Config()
|
|
2627
|
-
config_overrides = overrides('config', {})
|
|
2628
|
-
for k, v in config_overrides.items():
|
|
2629
|
-
self._config.set(k, v)
|
|
2630
|
-
self._intrinsic_overrides = get_intrinsic_overrides()
|
|
2631
|
-
self._strict = cast(bool, overrides('strict', strict))
|
|
2632
|
-
self._use_lqp = overridable_flag('reasoner.rule.use_lqp', self._config, use_lqp, default=not self._use_sql)
|
|
2633
|
-
self._enable_otel_handler = overridable_flag('enable_otel_handler', self._config, enable_otel_handler, default=False)
|
|
2634
|
-
if isinstance(runtime_env, SessionEnvironment):
|
|
2635
|
-
self._connection = runtime_env.configure_session(self._config, connection)
|
|
2636
|
-
else:
|
|
2637
|
-
self._connection = connection
|
|
2638
|
-
self.concepts:dict[str, list[Concept]] = {}
|
|
2639
|
-
self.relationships:list[Relationship] = []
|
|
2640
|
-
self.enums:dict[str, Type[Enum]] = {}
|
|
2641
|
-
self.enum_concept:dict[Type[Enum], Concept] = {}
|
|
2642
|
-
self.constraints:set[Constraint] = set()
|
|
2643
|
-
|
|
2644
|
-
# Compiler
|
|
2645
|
-
self._compiler = Compiler()
|
|
2646
|
-
self._root_version = _global_roots.version()
|
|
2647
|
-
self._last_compilation = None
|
|
2648
|
-
|
|
2649
|
-
# Executor
|
|
2650
|
-
self._executor = None
|
|
2651
|
-
|
|
2652
|
-
# Enum
|
|
2653
|
-
self.Enum = create_enum_class(self)
|
|
2654
|
-
|
|
2655
|
-
def _to_ir(self):
|
|
2656
|
-
if not _global_roots.has_changed(self._root_version) and self._last_compilation:
|
|
2657
|
-
return self._last_compilation
|
|
2658
|
-
self._last_compilation = self._compiler.model(self)
|
|
2659
|
-
self._root_version = _global_roots.version()
|
|
2660
|
-
return self._last_compilation
|
|
2661
|
-
|
|
2662
|
-
def _to_executor(self):
|
|
2663
|
-
if not self._executor:
|
|
2664
|
-
if self._use_lqp:
|
|
2665
|
-
self._executor = LQPExecutor(
|
|
2666
|
-
self.name,
|
|
2667
|
-
dry_run=self._dry_run,
|
|
2668
|
-
keep_model=self._keep_model,
|
|
2669
|
-
wide_outputs=self._wide_outputs,
|
|
2670
|
-
connection=self._connection,
|
|
2671
|
-
config=self._config,
|
|
2672
|
-
intrinsic_overrides=self._intrinsic_overrides,
|
|
2673
|
-
)
|
|
2674
|
-
elif self._use_sql:
|
|
2675
|
-
self._executor = SnowflakeExecutor(
|
|
2676
|
-
self.name,
|
|
2677
|
-
self.name,
|
|
2678
|
-
dry_run=self._dry_run,
|
|
2679
|
-
config=self._config,
|
|
2680
|
-
skip_denormalization=True,
|
|
2681
|
-
connection=self._connection,
|
|
2682
|
-
)
|
|
2683
|
-
else:
|
|
2684
|
-
self._executor = RelExecutor(
|
|
2685
|
-
self.name,
|
|
2686
|
-
dry_run=self._dry_run,
|
|
2687
|
-
keep_model=self._keep_model,
|
|
2688
|
-
wide_outputs=self._wide_outputs,
|
|
2689
|
-
connection=self._connection,
|
|
2690
|
-
config=self._config,
|
|
2691
|
-
)
|
|
2692
|
-
configure_otel(self._enable_otel_handler, self._config, self._executor.resources)
|
|
2693
|
-
return self._executor
|
|
2694
|
-
|
|
2695
|
-
def Concept(self, name:str, extends:list[Concept|Any]=[], identify_by:dict[str, Any]={}) -> Concept:
|
|
2696
|
-
concept = Concept(name, model=self, extends=extends, identify_by=identify_by)
|
|
2697
|
-
if name not in self.concepts:
|
|
2698
|
-
self.concepts[name] = list()
|
|
2699
|
-
self.concepts[name].append(concept)
|
|
2700
|
-
return concept
|
|
2701
|
-
|
|
2702
|
-
def Relationship(self, *args, short_name:str="") -> Relationship:
|
|
2703
|
-
return Relationship(*args, parent=None, short_name=short_name, model=self)
|
|
2704
|
-
|
|
2705
|
-
def Property(self, *args, short_name:str="") -> Property:
|
|
2706
|
-
return Property(*args, parent=None, short_name=short_name, model=self)
|
|
2707
|
-
|
|
2708
|
-
def define(self, *args: Any) -> Fragment:
|
|
2709
|
-
return define(*args, model=self)
|
|
2710
|
-
|
|
2711
|
-
#--------------------------------------------------
|
|
2712
|
-
# Compile
|
|
2713
|
-
#--------------------------------------------------
|
|
2714
|
-
|
|
2715
|
-
class CompilerContext():
|
|
2716
|
-
def __init__(self, compiler:Compiler, parent:CompilerContext|None=None):
|
|
2717
|
-
self.compiler = compiler
|
|
2718
|
-
self.parent = parent
|
|
2719
|
-
self.value_map:dict[Any, ir.Value|list[ir.Var]] = parent.value_map.copy() if parent else {}
|
|
2720
|
-
self.items:OrderedSet[ir.Task] = OrderedSet()
|
|
2721
|
-
self.into_vars:list[ir.Var] = parent.into_vars.copy() if parent else []
|
|
2722
|
-
self.global_value_map:dict[Any, ir.Value|list[ir.Var]] = parent.global_value_map if parent else {}
|
|
2723
|
-
|
|
2724
|
-
def to_value(self, item:Any, or_value=None, is_global_or_value=True) -> ir.Value|list[ir.Var]:
|
|
2725
|
-
if item not in self.value_map:
|
|
2726
|
-
if item in self.global_value_map:
|
|
2727
|
-
self.value_map[item] = self.global_value_map[item]
|
|
2728
|
-
elif or_value is not None:
|
|
2729
|
-
if is_global_or_value:
|
|
2730
|
-
# when or_value is global save it in global_value_map as well
|
|
2731
|
-
self.map_var(item, or_value)
|
|
2732
|
-
else:
|
|
2733
|
-
self.value_map[item] = or_value
|
|
2734
|
-
else:
|
|
2735
|
-
name = to_name(item)
|
|
2736
|
-
qb_type = to_type(item)
|
|
2737
|
-
type = self.compiler.to_type(qb_type) if qb_type else types.Any
|
|
2738
|
-
self.map_var(item, f.var(name, type))
|
|
2739
|
-
return self.value_map[item]
|
|
2740
|
-
|
|
2741
|
-
def map_var(self, item:Any, value:ir.Value|list[ir.Var]):
|
|
2742
|
-
self.global_value_map[item] = value
|
|
2743
|
-
self.value_map[item] = value
|
|
2744
|
-
return value
|
|
2745
|
-
|
|
2746
|
-
def fetch_var(self, item:Any):
|
|
2747
|
-
if item in self.value_map:
|
|
2748
|
-
return self.value_map[item]
|
|
2749
|
-
elif item in self.global_value_map:
|
|
2750
|
-
return self.global_value_map[item]
|
|
2751
|
-
return None
|
|
2752
|
-
|
|
2753
|
-
def _has_item(self, item:ir.Task) -> bool:
|
|
2754
|
-
return bool(item in self.items or (self.parent and self.parent._has_item(item)))
|
|
2755
|
-
|
|
2756
|
-
def add(self, item:ir.Task):
|
|
2757
|
-
if not self._has_item(item):
|
|
2758
|
-
self.items.add(item)
|
|
2759
|
-
|
|
2760
|
-
def try_merge_hoists(self, required: PySequence[ir.VarOrDefault], available: PySequence[ir.VarOrDefault]) -> list[ir.VarOrDefault] | None:
|
|
2761
|
-
avail_map = {(item.var if isinstance(item, ir.Default) else item): item for item in available}
|
|
2762
|
-
result = []
|
|
2763
|
-
for req in required:
|
|
2764
|
-
var = req.var if isinstance(req, ir.Default) else req
|
|
2765
|
-
if var not in avail_map:
|
|
2766
|
-
return None
|
|
2767
|
-
# prefer the available default as it would've bubbled up and overridden
|
|
2768
|
-
# the required one, otherwise take the required
|
|
2769
|
-
result.append(avail_map[var] if isinstance(avail_map[var], ir.Default) else req)
|
|
2770
|
-
return result
|
|
2771
|
-
|
|
2772
|
-
def safe_wrap(self, required_hoists:PySequence[ir.VarOrDefault]) -> ir.Task:
|
|
2773
|
-
first = self.items[0]
|
|
2774
|
-
if len(self.items) == 1 and isinstance(first, ir.Logical):
|
|
2775
|
-
merged = self.try_merge_hoists(required_hoists, first.hoisted)
|
|
2776
|
-
if merged is not None:
|
|
2777
|
-
return f.logical(list(first.body), merged)
|
|
2778
|
-
return f.logical(list(self.items), required_hoists)
|
|
2779
|
-
|
|
2780
|
-
def is_hoisted(self, var: ir.Var):
|
|
2781
|
-
return any(isinstance(i, helpers.COMPOSITES) and var in helpers.hoisted_vars(i.hoisted) for i in self.items)
|
|
2782
|
-
|
|
2783
|
-
def clone(self):
|
|
2784
|
-
return CompilerContext(self.compiler, self)
|
|
2785
|
-
|
|
2786
|
-
|
|
2787
|
-
# map literal, python native types to IR types
|
|
2788
|
-
PY_LITERAL_TYPE_MAPPING = {
|
|
2789
|
-
str: types.String,
|
|
2790
|
-
bool: types.Bool,
|
|
2791
|
-
int: types.Int128,
|
|
2792
|
-
float: types.Float,
|
|
2793
|
-
PyDecimal: types.Decimal,
|
|
2794
|
-
date: types.Date,
|
|
2795
|
-
datetime: types.DateTime,
|
|
2796
|
-
}
|
|
2797
|
-
|
|
2798
|
-
PY_LITERAL_TYPES = (str, bool, int, float, date, datetime, PyDecimal)
|
|
2799
|
-
|
|
2800
|
-
def literal_value_to_type(value) -> ir.Type:
|
|
2801
|
-
literal_type = type(value)
|
|
2802
|
-
if literal_type in PY_LITERAL_TYPE_MAPPING:
|
|
2803
|
-
return PY_LITERAL_TYPE_MAPPING[literal_type]
|
|
2804
|
-
raise TypeError(f"Cannot determine type for value: {value} of type {type(value).__name__}")
|
|
2805
|
-
|
|
2806
|
-
class Compiler():
|
|
2807
|
-
def __init__(self):
|
|
2808
|
-
self.types:dict[Concept, ir.ScalarType] = {}
|
|
2809
|
-
self.name_to_type:dict[str, ir.Type] = {}
|
|
2810
|
-
self.relations:dict[Relationship|Concept|ConceptMember|RelationshipRef|RelationshipReading|ir.Relation, ir.Relation] = {}
|
|
2811
|
-
# cache box_type relations
|
|
2812
|
-
self.box_type_relations:dict[tuple[ir.Type, ir.Type], ir.Relation] = {}
|
|
2813
|
-
|
|
2814
|
-
#--------------------------------------------------
|
|
2815
|
-
# Type/Relation conversion
|
|
2816
|
-
#--------------------------------------------------
|
|
2817
|
-
|
|
2818
|
-
def to_annos(self, item:Concept|Relationship|RelationshipReading|Fragment) -> list[ir.Annotation]:
|
|
2819
|
-
annos = []
|
|
2820
|
-
items = item._annotations
|
|
2821
|
-
for item in items:
|
|
2822
|
-
if isinstance(item, Expression):
|
|
2823
|
-
ctx = CompilerContext(self)
|
|
2824
|
-
annos.append(f.annotation(self.to_relation(item._op), flatten([self.lookup(p, ctx) for p in item._params])))
|
|
2825
|
-
elif isinstance(item, Relationship):
|
|
2826
|
-
annos.append(f.annotation(self.to_relation(item), []))
|
|
2827
|
-
elif isinstance(item, ir.Annotation):
|
|
2828
|
-
annos.append(item)
|
|
2829
|
-
else:
|
|
2830
|
-
raise ValueError(f"Cannot convert {type(item).__name__} to annotation")
|
|
2831
|
-
return annos
|
|
2832
|
-
|
|
2833
|
-
def to_type(self, concept:Concept) -> ir.ScalarType:
|
|
2834
|
-
if concept not in self.types:
|
|
2835
|
-
if is_decimal(concept):
|
|
2836
|
-
self.types[concept] = types.decimal_by_type_str(concept._name)
|
|
2837
|
-
elif concept in Concept.builtin_concepts:
|
|
2838
|
-
self.types[concept] = types.builtin_scalar_types_by_name[concept._name]
|
|
2839
|
-
else:
|
|
2840
|
-
parent_types = [self.to_type(parent) for parent in concept._extends]
|
|
2841
|
-
self.types[concept] = f.scalar_type(concept._name, parent_types, annos=self.to_annos(concept))
|
|
2842
|
-
self.name_to_type[concept._name] = self.types[concept]
|
|
2843
|
-
return self.types[concept]
|
|
2844
|
-
|
|
2845
|
-
def to_relation(self, item:Concept|Relationship|RelationshipReading|RelationshipRef|ir.Relation) -> ir.Relation:
|
|
2846
|
-
if item not in self.relations:
|
|
2847
|
-
if isinstance(item, Concept):
|
|
2848
|
-
fields = [f.field(item._name.lower(), self.to_type(item))]
|
|
2849
|
-
annos = self.to_annos(item)
|
|
2850
|
-
builtins.builtin_annotations_by_name
|
|
2851
|
-
annos.append(builtins.concept_relation_annotation)
|
|
2852
|
-
relation = f.relation(item._name, fields, annos=annos)
|
|
2853
|
-
elif isinstance(item, Relationship):
|
|
2854
|
-
if item._ir_relation:
|
|
2855
|
-
relation = item._ir_relation
|
|
2856
|
-
for overload in relation.overloads:
|
|
2857
|
-
self.to_relation(overload)
|
|
2858
|
-
else:
|
|
2859
|
-
fields = []
|
|
2860
|
-
for cur in item._field_refs:
|
|
2861
|
-
assert isinstance(cur._thing, Concept)
|
|
2862
|
-
fields.append(f.field(to_name(cur), self.to_type(cur._thing)))
|
|
2863
|
-
overloads = []
|
|
2864
|
-
if item._unresolved:
|
|
2865
|
-
overloads = [v for k, v in self.relations.items()
|
|
2866
|
-
if isinstance(k, Relationship)
|
|
2867
|
-
and not k._unresolved
|
|
2868
|
-
and k._name == item._name]
|
|
2869
|
-
relation = f.relation(item._name, fields, annos=self.to_annos(item), overloads=overloads)
|
|
2870
|
-
# skip the first reading since it's the same as the Relationship
|
|
2871
|
-
for red in item._readings[1:]:
|
|
2872
|
-
self.to_relation(red)
|
|
2873
|
-
elif isinstance(item, RelationshipReading):
|
|
2874
|
-
fields = []
|
|
2875
|
-
for cur in item._field_refs:
|
|
2876
|
-
assert isinstance(cur._thing, Concept)
|
|
2877
|
-
fields.append(f.field(to_name(cur), self.to_type(cur._thing)))
|
|
2878
|
-
# todo: should we look for overloads in case alt_of Relationship is unresolved?
|
|
2879
|
-
relation = f.relation(item._name, fields, annos=self.to_annos(item))
|
|
2880
|
-
elif isinstance(item, RelationshipRef):
|
|
2881
|
-
relation = self.to_relation(item._relationship)
|
|
2882
|
-
elif isinstance(item, ir.Relation):
|
|
2883
|
-
for overload in item.overloads:
|
|
2884
|
-
self.to_relation(overload)
|
|
2885
|
-
relation = item
|
|
2886
|
-
self.relations[item] = relation
|
|
2887
|
-
return relation
|
|
2888
|
-
else:
|
|
2889
|
-
return self.relations[item]
|
|
2890
|
-
|
|
2891
|
-
#--------------------------------------------------
|
|
2892
|
-
# Model
|
|
2893
|
-
#--------------------------------------------------
|
|
2894
|
-
|
|
2895
|
-
@roots(enabled=False)
|
|
2896
|
-
def model(self, model:Model) -> ir.Model:
|
|
2897
|
-
rules = []
|
|
2898
|
-
for concepts in model.concepts.values():
|
|
2899
|
-
for concept in concepts:
|
|
2900
|
-
if concept not in self.types:
|
|
2901
|
-
self.to_type(concept)
|
|
2902
|
-
self.to_relation(concept)
|
|
2903
|
-
rule = self.concept_inheritance_rule(concept)
|
|
2904
|
-
if rule:
|
|
2905
|
-
rules.append(rule)
|
|
2906
|
-
unresolved = []
|
|
2907
|
-
for relationship in model.relationships:
|
|
2908
|
-
if relationship not in self.relations:
|
|
2909
|
-
if relationship._unresolved:
|
|
2910
|
-
unresolved.append(relationship)
|
|
2911
|
-
else:
|
|
2912
|
-
self.to_relation(relationship)
|
|
2913
|
-
for relationship in unresolved:
|
|
2914
|
-
self.to_relation(relationship)
|
|
2915
|
-
with debugging.span("rule_batch"):
|
|
2916
|
-
for idx, rule in enumerate(_global_roots):
|
|
2917
|
-
if not rule._model or rule._model == model:
|
|
2918
|
-
meta = rule._meta if isinstance(rule, Fragment) else {}
|
|
2919
|
-
with debugging.span("rule", name=f"rule{idx}", dsl=str(rule), **with_source(rule), meta=meta) as rule_span:
|
|
2920
|
-
rule_ir = self.compile_task(rule)
|
|
2921
|
-
rules.append(rule_ir)
|
|
2922
|
-
rule_span["metamodel"] = str(rule_ir)
|
|
2923
|
-
root = f.logical(rules)
|
|
2924
|
-
engines = ordered_set()
|
|
2925
|
-
relations = OrderedSet.from_iterable(self.relations.values())
|
|
2926
|
-
types = OrderedSet.from_iterable(self.types.values())
|
|
2927
|
-
return f.model(engines, relations, types, root)
|
|
2928
|
-
|
|
2929
|
-
#--------------------------------------------------
|
|
2930
|
-
# Compile
|
|
2931
|
-
#--------------------------------------------------
|
|
2932
|
-
|
|
2933
|
-
@roots(enabled=False)
|
|
2934
|
-
def compile_task(self, thing:Expression|Fragment) -> ir.Task:
|
|
2935
|
-
if isinstance(thing, (Expression, Match, Union)):
|
|
2936
|
-
return self.root_expression(thing)
|
|
2937
|
-
elif isinstance(thing, Fragment):
|
|
2938
|
-
return self.fragment(thing)
|
|
2939
|
-
|
|
2940
|
-
#--------------------------------------------------
|
|
2941
|
-
# Root expression
|
|
2942
|
-
#--------------------------------------------------
|
|
2943
|
-
|
|
2944
|
-
@roots(enabled=False)
|
|
2945
|
-
def root_expression(self, item:Expression) -> ir.Task:
|
|
2946
|
-
ctx = CompilerContext(self)
|
|
2947
|
-
self.update(item, ctx)
|
|
2948
|
-
return f.logical(list(ctx.items))
|
|
2949
|
-
|
|
2950
|
-
#--------------------------------------------------
|
|
2951
|
-
# Fragment
|
|
2952
|
-
#--------------------------------------------------
|
|
2953
|
-
|
|
2954
|
-
def _is_rank(self, item) -> bool:
|
|
2955
|
-
return isinstance(item, Aggregate) and item._op._name == "rank"
|
|
2956
|
-
|
|
2957
|
-
def _process_rank(self, items:PySequence[Expression], rank_ctx:CompilerContext):
|
|
2958
|
-
args_to_process = ordered_set()
|
|
2959
|
-
arg_is_ascending = []
|
|
2960
|
-
for item in items:
|
|
2961
|
-
if isinstance(item, RankOrder):
|
|
2962
|
-
args_to_process.update(item._args)
|
|
2963
|
-
arg_is_ascending.extend([item._is_asc] * len(item._args))
|
|
2964
|
-
else:
|
|
2965
|
-
args_to_process.add(item)
|
|
2966
|
-
arg_is_ascending.append(RankOrder.ASC)
|
|
2967
|
-
|
|
2968
|
-
keys = ordered_set()
|
|
2969
|
-
for arg in args_to_process:
|
|
2970
|
-
if isinstance(arg, Distinct):
|
|
2971
|
-
continue
|
|
2972
|
-
keys.update(find_keys(arg))
|
|
2973
|
-
# Expressions go into the rank args if asked directly.
|
|
2974
|
-
# Otherwise they go into the projection if they are keys.
|
|
2975
|
-
projection = OrderedSet.from_iterable(
|
|
2976
|
-
flatten([self.lookup(key, rank_ctx) for key in keys], flatten_tuples=True)
|
|
2977
|
-
)
|
|
2978
|
-
args = OrderedSet.from_iterable(
|
|
2979
|
-
flatten([self.lookup(arg, rank_ctx) for arg in args_to_process], flatten_tuples=True)
|
|
2980
|
-
)
|
|
2981
|
-
return projection, args, arg_is_ascending
|
|
2982
|
-
|
|
2983
|
-
@roots(enabled=False)
|
|
2984
|
-
def fragment(self, fragment:Fragment, parent_ctx:CompilerContext|None=None, into_vars:list[ir.Var] = []) -> ir.Task:
|
|
2985
|
-
ctx = CompilerContext(self, parent_ctx)
|
|
2986
|
-
if fragment._require:
|
|
2987
|
-
self.require(fragment, fragment._require, ctx)
|
|
2988
|
-
else:
|
|
2989
|
-
rank_var = self.order_by_or_limit(fragment, ctx)
|
|
2990
|
-
self.where(fragment, fragment._where, ctx)
|
|
2991
|
-
self.define(fragment, fragment._define, ctx)
|
|
2992
|
-
self.select(fragment, fragment._select, ctx, rank_var)
|
|
2993
|
-
return f.logical(list(ctx.items), ctx.into_vars, annos=self.to_annos(fragment))
|
|
2994
|
-
|
|
2995
|
-
def order_by_or_limit(self, fragment:Fragment, ctx:CompilerContext):
|
|
2996
|
-
if fragment._limit == 0 and not fragment._order_by:
|
|
2997
|
-
return None
|
|
2998
|
-
if fragment._define:
|
|
2999
|
-
raise NotImplementedError("Order_by and/or limit are not supported on define")
|
|
3000
|
-
|
|
3001
|
-
limit_ctx = ctx.clone()
|
|
3002
|
-
inner_ctx = limit_ctx.clone()
|
|
3003
|
-
|
|
3004
|
-
# If there is an order-by, then the limit is applied on the fields there. Otherwise,
|
|
3005
|
-
# the limit is applied on the fields in the select (with a default ranking order).
|
|
3006
|
-
items = fragment._order_by if fragment._order_by else fragment._select
|
|
3007
|
-
|
|
3008
|
-
projection, args, arg_is_ascending = self._process_rank(items, inner_ctx)
|
|
3009
|
-
|
|
3010
|
-
limit_ctx.add(inner_ctx.safe_wrap([]))
|
|
3011
|
-
|
|
3012
|
-
rank_var = f.var("v", types.Int128)
|
|
3013
|
-
limit_ctx.add(f.rank(list(projection), [], list(args), arg_is_ascending, rank_var, fragment._limit))
|
|
3014
|
-
ctx.add(f.logical(list(limit_ctx.items), [rank_var]))
|
|
3015
|
-
return rank_var
|
|
3016
|
-
|
|
3017
|
-
def where(self, fragment:Fragment, items:PySequence[Expression], ctx:CompilerContext):
|
|
3018
|
-
for item in items:
|
|
3019
|
-
self.lookup(item, ctx)
|
|
3020
|
-
|
|
3021
|
-
def select(self, fragment:Fragment, items:PySequence[Expression], ctx:CompilerContext, rank_var:ir.Var|None=None):
|
|
3022
|
-
if not items:
|
|
3023
|
-
return
|
|
3024
|
-
|
|
3025
|
-
namer = NameCache(use_underscore=False)
|
|
3026
|
-
aggregate_keys:OrderedSet[ir.Var] = OrderedSet()
|
|
3027
|
-
out_var_to_keys = {}
|
|
3028
|
-
fields = []
|
|
3029
|
-
if rank_var:
|
|
3030
|
-
fields.append((namer.get_name(len(fields), "rank"), rank_var))
|
|
3031
|
-
keys_present = has_keys(items)
|
|
3032
|
-
for ix, item in enumerate(items):
|
|
3033
|
-
# allow primitive to be a key when at least one key is present and primitive is not the last item
|
|
3034
|
-
# this is needed to avoid cross products in output
|
|
3035
|
-
enable_primitive_key = ix != len(items) - 1 if keys_present else False
|
|
3036
|
-
keys = find_select_keys(item, enable_primitive_key=enable_primitive_key)
|
|
3037
|
-
|
|
3038
|
-
key_vars:list[ir.Var] = []
|
|
3039
|
-
for idx, key in enumerate(keys):
|
|
3040
|
-
# don't add lookups for the keys played by Concepts if they are not on the first position
|
|
3041
|
-
if idx > 0 and isinstance(key.val, Concept):
|
|
3042
|
-
key_var = ctx.to_value(key.val)
|
|
3043
|
-
else:
|
|
3044
|
-
key_var = self.lookup(key.val, ctx)
|
|
3045
|
-
assert isinstance(key_var, ir.Var)
|
|
3046
|
-
key_vars.append(key_var)
|
|
3047
|
-
if key.is_group:
|
|
3048
|
-
aggregate_keys.add(key_var)
|
|
3049
|
-
|
|
3050
|
-
sub_ctx = ctx.clone()
|
|
3051
|
-
result_vars = []
|
|
3052
|
-
# If we lookup a property or a relationship, without a parent (a bare one)
|
|
3053
|
-
# don't assume some value can be None. Add the lookup directly to the parent ctx
|
|
3054
|
-
if isinstance(item, RelationshipRef) and isinstance(item._parent, Property) and not item._parent._parent:
|
|
3055
|
-
self.lookup(item._parent, ctx)
|
|
3056
|
-
result = self.lookup(item, sub_ctx)
|
|
3057
|
-
elif isinstance(item, (Property, Relationship, RelationshipFieldRef)) and not item._parent:
|
|
3058
|
-
result = self.lookup(item, ctx)
|
|
3059
|
-
else:
|
|
3060
|
-
result = self.lookup(item, sub_ctx)
|
|
3061
|
-
if isinstance(result, list):
|
|
3062
|
-
assert all(isinstance(v, ir.Var) for v in result)
|
|
3063
|
-
result_vars.extend(result)
|
|
3064
|
-
else:
|
|
3065
|
-
result_vars.append(result)
|
|
3066
|
-
|
|
3067
|
-
# normalize result vars through fetch_var if available
|
|
3068
|
-
result_vars = [
|
|
3069
|
-
fetched if isinstance(fetched := ctx.fetch_var(v), ir.Var) else v
|
|
3070
|
-
for v in result_vars
|
|
3071
|
-
]
|
|
3072
|
-
|
|
3073
|
-
extra_nullable_keys: OrderedSet[ir.Var] = OrderedSet()
|
|
3074
|
-
# check if whether we actually added a lookup resulting in the key, in the sub-context
|
|
3075
|
-
# the lookup might have already existed in the parent context, in which case the key is not nullable.
|
|
3076
|
-
# E.g.,
|
|
3077
|
-
# attends(course)
|
|
3078
|
-
# course = ..
|
|
3079
|
-
# Logical ^[..]
|
|
3080
|
-
#
|
|
3081
|
-
# vs
|
|
3082
|
-
#
|
|
3083
|
-
# Logical ^[.., course=None]
|
|
3084
|
-
# attends(course)
|
|
3085
|
-
for it in sub_ctx.items:
|
|
3086
|
-
if isinstance(it, ir.Lookup):
|
|
3087
|
-
vars = helpers.vars(it.args)
|
|
3088
|
-
if vars[-1] in key_vars:
|
|
3089
|
-
extra_nullable_keys.add(vars[-1])
|
|
3090
|
-
|
|
3091
|
-
if len(sub_ctx.items) > 0:
|
|
3092
|
-
args = list(result_vars)
|
|
3093
|
-
for k in extra_nullable_keys:
|
|
3094
|
-
if k not in args:
|
|
3095
|
-
args.append(k)
|
|
3096
|
-
hoisted:list[ir.VarOrDefault] = [ir.Default(v, None) for v in args if isinstance(v, ir.Var)]
|
|
3097
|
-
ctx.add(sub_ctx.safe_wrap(hoisted))
|
|
3098
|
-
|
|
3099
|
-
for v in result_vars:
|
|
3100
|
-
name = "v"
|
|
3101
|
-
if isinstance(item, Alias):
|
|
3102
|
-
name = item._name
|
|
3103
|
-
elif isinstance(v, ir.Var):
|
|
3104
|
-
name = v.name
|
|
3105
|
-
out_var_to_keys[v] = key_vars
|
|
3106
|
-
|
|
3107
|
-
# if this is a nested select that is populating variables rather
|
|
3108
|
-
# than outputting
|
|
3109
|
-
if ctx.into_vars:
|
|
3110
|
-
relation = self.to_relation(builtins.eq)
|
|
3111
|
-
ctx.add(f.lookup(relation, [ctx.into_vars[ix], v]))
|
|
3112
|
-
else:
|
|
3113
|
-
fields.append((namer.get_name(len(fields), name), v))
|
|
3114
|
-
|
|
3115
|
-
if fields:
|
|
3116
|
-
annos = fragment._annotations
|
|
3117
|
-
if fragment._is_export:
|
|
3118
|
-
annos += [builtins.export_annotation]
|
|
3119
|
-
|
|
3120
|
-
# If one of the vars in our output is itself a key, and it's the key of an
|
|
3121
|
-
# aggregation, then we should ignore its keys. This fixes the case where we
|
|
3122
|
-
# return the group of an aggregate and should ignore the keys of the group variables.
|
|
3123
|
-
final_keys = ordered_set()
|
|
3124
|
-
for v, keys in out_var_to_keys.items():
|
|
3125
|
-
if v in aggregate_keys:
|
|
3126
|
-
final_keys.add(v)
|
|
3127
|
-
else:
|
|
3128
|
-
final_keys.update(keys)
|
|
3129
|
-
|
|
3130
|
-
# If we are exporting into a table, we need to add a key to the output
|
|
3131
|
-
# We hash all the values to create a key
|
|
3132
|
-
if not final_keys and fragment._is_export:
|
|
3133
|
-
tmp_var = ctx.to_value(self)
|
|
3134
|
-
assert isinstance(tmp_var, ir.Var)
|
|
3135
|
-
key_var = f.var(tmp_var.name, types.Hash)
|
|
3136
|
-
assert isinstance(key_var, ir.Var)
|
|
3137
|
-
final_keys.add(key_var)
|
|
3138
|
-
values = [ir.Literal(types.String, "NO_KEYS")]
|
|
3139
|
-
for fld in fields:
|
|
3140
|
-
values.append(fld[1])
|
|
3141
|
-
con = ir.Construct(None, tuple(values), key_var, FrozenOrderedSet([]))
|
|
3142
|
-
ctx.add(con)
|
|
3143
|
-
|
|
3144
|
-
ctx.add(f.output(fields, keys=list(final_keys), annos=annos))
|
|
3145
|
-
|
|
3146
|
-
def require(self, fragment:Fragment, items:PySequence[Expression], ctx:CompilerContext):
|
|
3147
|
-
domain_ctx = ctx.clone()
|
|
3148
|
-
self.where(fragment, fragment._where, domain_ctx)
|
|
3149
|
-
domain_vars = OrderedSet.from_iterable(flatten(list(domain_ctx.value_map.values()), flatten_tuples=True))
|
|
3150
|
-
to_hoist = OrderedSet()
|
|
3151
|
-
checks = []
|
|
3152
|
-
for item in items:
|
|
3153
|
-
if isinstance(item, Expression) and item._op is Relationship.builtins[builtins.anyof.name] and not domain_vars:
|
|
3154
|
-
raise ValueError("'anyof' and 'oneof' are not allowed without a domain")
|
|
3155
|
-
|
|
3156
|
-
req_ctx = domain_ctx.clone()
|
|
3157
|
-
self.lookup(item, req_ctx)
|
|
3158
|
-
req_body = f.logical(list(req_ctx.items))
|
|
3159
|
-
|
|
3160
|
-
err_ctx = domain_ctx.clone()
|
|
3161
|
-
item_str = item._pprint() if isinstance(item, Producer) else str(item)
|
|
3162
|
-
keys = {to_name(k): k for k in find_keys(item)}
|
|
3163
|
-
source = item._source if hasattr(item, "_source") else fragment._source
|
|
3164
|
-
e = Error.new(message=f"Requirement not met: {item_str}", **keys, _source=source, _model=fragment._model)
|
|
3165
|
-
self.update(e, err_ctx)
|
|
3166
|
-
err_body = f.logical(list(err_ctx.items))
|
|
3167
|
-
checks.append(f.check(req_body, err_body))
|
|
3168
|
-
|
|
3169
|
-
# find vars that overlap between domain and check/error and hoist them
|
|
3170
|
-
all_values = flatten(list(req_ctx.value_map.values()) + list(err_ctx.value_map.values()))
|
|
3171
|
-
to_hoist.update(domain_vars & OrderedSet.from_iterable(all_values))
|
|
3172
|
-
|
|
3173
|
-
domain = f.logical(list(domain_ctx.items), list(to_hoist))
|
|
3174
|
-
req = f.require(domain, checks)
|
|
3175
|
-
ctx.add(req)
|
|
3176
|
-
|
|
3177
|
-
def define(self, fragment:Fragment, items:PySequence[Expression], ctx:CompilerContext):
|
|
3178
|
-
def _check_item(item: Expression):
|
|
3179
|
-
if isinstance(item, ConceptFilter):
|
|
3180
|
-
raise ValueError("'filter_by' is not allowed in definitions")
|
|
3181
|
-
|
|
3182
|
-
if len(items) == 1:
|
|
3183
|
-
item = items[0]
|
|
3184
|
-
_check_item(item)
|
|
3185
|
-
self.update(item, ctx)
|
|
3186
|
-
return
|
|
3187
|
-
|
|
3188
|
-
for item in items:
|
|
3189
|
-
_check_item(item)
|
|
3190
|
-
sub_ctx = ctx.clone()
|
|
3191
|
-
self.update(item, sub_ctx)
|
|
3192
|
-
if len(sub_ctx.items) > 1:
|
|
3193
|
-
ctx.add(f.logical(list(sub_ctx.items)))
|
|
3194
|
-
elif len(sub_ctx.items) == 1:
|
|
3195
|
-
ctx.add(sub_ctx.items[0])
|
|
3196
|
-
|
|
3197
|
-
#--------------------------------------------------
|
|
3198
|
-
# Reference schemes and concept inheritance
|
|
3199
|
-
#--------------------------------------------------
|
|
3200
|
-
|
|
3201
|
-
def concept_inheritance_rule(self, concept:Concept) -> Optional[ir.Task]:
|
|
3202
|
-
"""
|
|
3203
|
-
If the concept extends non-primitive concepts, generate a rule where the body is a
|
|
3204
|
-
lookup for this concept and the head are derives into all non-primitive direct super
|
|
3205
|
-
types.
|
|
3206
|
-
"""
|
|
3207
|
-
# filter extends to get only non-primitive parents
|
|
3208
|
-
parents = []
|
|
3209
|
-
for parent in concept._extends:
|
|
3210
|
-
if not parent._is_primitive() and parent is not AnyEntity:
|
|
3211
|
-
parents.append(parent)
|
|
3212
|
-
# always extend AnyEntity for non-primitive types that are not built-in
|
|
3213
|
-
if not concept._is_primitive() and concept not in Concept.builtin_concepts:
|
|
3214
|
-
parents.append(AnyEntity)
|
|
3215
|
-
# only extends primitive types, no need for inheritance rules
|
|
3216
|
-
if not parents:
|
|
3217
|
-
return None
|
|
3218
|
-
# generate the rule
|
|
3219
|
-
ctx = CompilerContext(self)
|
|
3220
|
-
var = self.lookup(concept, ctx)
|
|
3221
|
-
assert isinstance(var, ir.Var)
|
|
3222
|
-
return f.logical([
|
|
3223
|
-
*list(ctx.items),
|
|
3224
|
-
*[f.derive(self.to_relation(parent), [var]) for parent in parents]
|
|
3225
|
-
])
|
|
3226
|
-
|
|
3227
|
-
def concept_any_entity_rule(self, entities:list[Concept]):
|
|
3228
|
-
"""
|
|
3229
|
-
Generate an inheritance rule for all these entities to AnyEntity.
|
|
3230
|
-
"""
|
|
3231
|
-
any_entity_relation = self.to_relation(AnyEntity)
|
|
3232
|
-
var = f.var("v", types.Any)
|
|
3233
|
-
return f.logical([
|
|
3234
|
-
f.union([f.lookup(self.to_relation(e), [var]) for e in entities]),
|
|
3235
|
-
f.derive(any_entity_relation, [var])
|
|
3236
|
-
])
|
|
3237
|
-
|
|
3238
|
-
def relation_dict(self, items:dict[Relationship|Concept, Producer], ctx:CompilerContext) -> dict[ir.Relation, list[ir.Var]]:
|
|
3239
|
-
return {self.to_relation(k): unwrap_list(self.lookup(v, ctx)) for k, v in items.items()}
|
|
3240
|
-
|
|
3241
|
-
def construct_relation_dict(self, items: dict[Relationship | Concept, Producer], ctx: CompilerContext) -> dict[ir.Relation, list[ir.Var]]:
|
|
3242
|
-
result = {}
|
|
3243
|
-
for k, v in items.items():
|
|
3244
|
-
relation = self.to_relation(k)
|
|
3245
|
-
value = unwrap_list(self.lookup(v, ctx))
|
|
3246
|
-
|
|
3247
|
-
# We are able to check types for the `construct` only when we are using binary relations
|
|
3248
|
-
# in the other case we just pass the original value
|
|
3249
|
-
if len(relation.fields) == 2:
|
|
3250
|
-
field = relation.fields[-1]
|
|
3251
|
-
if field and field.type != types.Any and types.is_value_type(field.type):
|
|
3252
|
-
field_base = typer.to_base_primitive(field.type)
|
|
3253
|
-
value_base = typer.to_base_primitive(value.type)
|
|
3254
|
-
|
|
3255
|
-
if field_base != value_base:
|
|
3256
|
-
# cast to expected field type
|
|
3257
|
-
new_out = f.var(helpers.sanitize(to_name(k)), field.type)
|
|
3258
|
-
ctx.add(f.lookup(builtins.cast, [field.type, value, new_out]))
|
|
3259
|
-
result[relation] = new_out
|
|
3260
|
-
self.relations[relation] = builtins.cast
|
|
3261
|
-
continue
|
|
3262
|
-
|
|
3263
|
-
result[relation] = value
|
|
3264
|
-
|
|
3265
|
-
return result
|
|
3266
|
-
|
|
3267
|
-
def explode_ref_schemes(self, item:ConceptExpression, ctx:CompilerContext, update=False):
|
|
3268
|
-
hierarchy = item._op._ref_scheme_hierarchy()
|
|
3269
|
-
if not hierarchy:
|
|
3270
|
-
out = ctx.to_value(item)
|
|
3271
|
-
assert isinstance(out, ir.Var)
|
|
3272
|
-
ctx.add(f.construct(out, self.construct_relation_dict(item._construct_args(), ctx)))
|
|
3273
|
-
return out
|
|
3274
|
-
|
|
3275
|
-
# if we're just doing a lookup, then we only need the last reference scheme
|
|
3276
|
-
if not update:
|
|
3277
|
-
hierarchy = hierarchy[-1:]
|
|
3278
|
-
|
|
3279
|
-
out = None
|
|
3280
|
-
for ix, info in enumerate(hierarchy):
|
|
3281
|
-
concept = info["concept"]
|
|
3282
|
-
scheme = info["scheme"]
|
|
3283
|
-
# the "out" variable, which is constructed for the top-most concept in the
|
|
3284
|
-
# hierarchy and is used to key the generated derives, should be typed with the
|
|
3285
|
-
# most specific type, i.e. the type of the concept expression.
|
|
3286
|
-
# so if this is the top most (out is None) we set or_value_type to the type of
|
|
3287
|
-
# the item instead of the concept.
|
|
3288
|
-
if out is None:
|
|
3289
|
-
x = to_type(item)
|
|
3290
|
-
or_value_type = self.to_type(x) if x else self.to_type(concept)
|
|
3291
|
-
else:
|
|
3292
|
-
or_value_type = self.to_type(concept)
|
|
3293
|
-
|
|
3294
|
-
or_value = f.var(to_name(concept), or_value_type)
|
|
3295
|
-
# or_value is global only when it's the last Concept in hierarchy
|
|
3296
|
-
is_global_or_value = ix == len(hierarchy) - 1
|
|
3297
|
-
cur = ctx.to_value(concept, or_value, is_global_or_value) if isinstance(item, ConceptFilter) \
|
|
3298
|
-
else ctx.to_value((item, ix), or_value, is_global_or_value)
|
|
3299
|
-
assert isinstance(cur, ir.Var)
|
|
3300
|
-
ctx.add(f.construct(cur, self.construct_relation_dict(item._construct_args(scheme), ctx), prefix=[self.to_type(concept)]))
|
|
3301
|
-
if not out:
|
|
3302
|
-
out = cur
|
|
3303
|
-
if r := info.get("mapping"):
|
|
3304
|
-
rel = self.to_relation(r)
|
|
3305
|
-
if out is cur:
|
|
3306
|
-
out = ctx.to_value(item, f.var(to_name(concept), self.to_type(concept)))
|
|
3307
|
-
assert isinstance(out, ir.Var)
|
|
3308
|
-
if update:
|
|
3309
|
-
ctx.add(f.derive(rel, [cur, out]))
|
|
3310
|
-
else:
|
|
3311
|
-
ctx.add(f.lookup(rel, [cur, out]))
|
|
3312
|
-
|
|
3313
|
-
assert out is not None
|
|
3314
|
-
return out
|
|
3315
|
-
|
|
3316
|
-
#--------------------------------------------------
|
|
3317
|
-
# Lookup
|
|
3318
|
-
#--------------------------------------------------
|
|
3319
|
-
|
|
3320
|
-
def lookup(self, item:Any, ctx:CompilerContext) -> ir.Value|list[ir.Var]:
|
|
3321
|
-
if isinstance(item, ConceptExpression):
|
|
3322
|
-
assert isinstance(item._op, Concept)
|
|
3323
|
-
concept = item._op
|
|
3324
|
-
relation = self.to_relation(concept)
|
|
3325
|
-
(ident, kwargs) = item._params
|
|
3326
|
-
|
|
3327
|
-
# If this is a member lookup, check that the identity is a member
|
|
3328
|
-
# and add all the kwargs as lookups
|
|
3329
|
-
if isinstance(item, ConceptMember):
|
|
3330
|
-
out = self.lookup(ident, ctx)
|
|
3331
|
-
if isinstance(out, PY_LITERAL_TYPES):
|
|
3332
|
-
out = f.literal(out, self.to_type(concept))
|
|
3333
|
-
assert isinstance(out, (ir.Var, ir.Literal))
|
|
3334
|
-
if not concept._is_primitive():
|
|
3335
|
-
ctx.add(f.lookup(relation, [out]))
|
|
3336
|
-
rels = {self.to_relation(getattr(concept, k)): unwrap_list(self.lookup(v, ctx))
|
|
3337
|
-
for k, v in kwargs.items()}
|
|
3338
|
-
for k, v in rels.items():
|
|
3339
|
-
assert not isinstance(v, list)
|
|
3340
|
-
ctx.add(f.lookup(k, [out, v]))
|
|
3341
|
-
|
|
3342
|
-
# Boxing operation on value types
|
|
3343
|
-
# E.g., SSN(str_var), box a String to an SSN in the IR
|
|
3344
|
-
op_type = self.to_type(concept)
|
|
3345
|
-
if types.is_value_type(op_type):
|
|
3346
|
-
inner_type = out.type
|
|
3347
|
-
if inner_type == op_type:
|
|
3348
|
-
return out
|
|
3349
|
-
|
|
3350
|
-
# immediately transform string literals in symbol literals if necessary
|
|
3351
|
-
if isinstance(out, ir.Literal) and inner_type == types.String and op_type == types.Symbol:
|
|
3352
|
-
return f.literal(out.value, types.Symbol)
|
|
3353
|
-
|
|
3354
|
-
if ctx.fetch_var(item):
|
|
3355
|
-
new_out = ctx.fetch_var(item)
|
|
3356
|
-
assert not isinstance(new_out, list)
|
|
3357
|
-
else:
|
|
3358
|
-
new_out = f.var(helpers.sanitize(to_name(concept)), op_type)
|
|
3359
|
-
ctx.map_var(item, new_out)
|
|
3360
|
-
|
|
3361
|
-
ctx.add(f.lookup(builtins.cast, [op_type, out, new_out]))
|
|
3362
|
-
self.relations[item] = builtins.cast
|
|
3363
|
-
out = new_out
|
|
3364
|
-
|
|
3365
|
-
return out
|
|
3366
|
-
|
|
3367
|
-
# There are 3 types of kwargs usage:
|
|
3368
|
-
# 1. Only ref schema attributes - generate construct + population relation
|
|
3369
|
-
# 2. Ref schema attributes + some other relations - generate construct + population relation + lookups
|
|
3370
|
-
# 3. Not full ref schema or any other relation - generate set of lookups
|
|
3371
|
-
if isinstance(item, ConceptFilter):
|
|
3372
|
-
scheme = concept._ref_scheme()
|
|
3373
|
-
|
|
3374
|
-
out = None
|
|
3375
|
-
args = kwargs
|
|
3376
|
-
|
|
3377
|
-
if scheme:
|
|
3378
|
-
# Collect expected keys from the reference scheme
|
|
3379
|
-
ks = [rel._short_name for rel in scheme]
|
|
3380
|
-
|
|
3381
|
-
# Check if all scheme keys are present in kwargs
|
|
3382
|
-
full_ref_scheme_present = all(k in kwargs for k in ks)
|
|
3383
|
-
|
|
3384
|
-
if full_ref_scheme_present:
|
|
3385
|
-
# Explode full reference scheme
|
|
3386
|
-
out = self.explode_ref_schemes(item, ctx, update=False)
|
|
3387
|
-
if not concept._is_primitive():
|
|
3388
|
-
ctx.add(f.lookup(relation, [out]))
|
|
3389
|
-
|
|
3390
|
-
# Remove scheme keys from arguments
|
|
3391
|
-
args = {k: v for k, v in args.items() if k not in ks}
|
|
3392
|
-
|
|
3393
|
-
# If nothing left, return early
|
|
3394
|
-
if not args:
|
|
3395
|
-
return out
|
|
3396
|
-
|
|
3397
|
-
# Fallback: simple lookup if no scheme matched
|
|
3398
|
-
if out is None:
|
|
3399
|
-
out = self.lookup(concept, ctx)
|
|
3400
|
-
|
|
3401
|
-
assert isinstance(out, ir.Var)
|
|
3402
|
-
|
|
3403
|
-
# Generate relation lookups for remaining args
|
|
3404
|
-
rels = {self.to_relation(getattr(concept, k)): unwrap_list(self.lookup(v, ctx))
|
|
3405
|
-
for k, v in args.items()}
|
|
3406
|
-
|
|
3407
|
-
for k, v in rels.items():
|
|
3408
|
-
assert not isinstance(v, list)
|
|
3409
|
-
ctx.add(f.lookup(k, [out, v]))
|
|
3410
|
-
|
|
3411
|
-
return out
|
|
3412
|
-
|
|
3413
|
-
# otherwise we have to construct one
|
|
3414
|
-
out = self.explode_ref_schemes(item, ctx, update=False)
|
|
3415
|
-
return out
|
|
3416
|
-
|
|
3417
|
-
elif isinstance(item, Expression):
|
|
3418
|
-
params = [self.lookup(p, ctx) for p in item._params]
|
|
3419
|
-
relation = self.to_relation(item._op)
|
|
3420
|
-
ctx.add(f.lookup(relation, flatten(params)))
|
|
3421
|
-
return params[-1]
|
|
3422
|
-
|
|
3423
|
-
elif isinstance(item, Concept):
|
|
3424
|
-
v = ctx.to_value(item)
|
|
3425
|
-
if not item._isa(Primitive):
|
|
3426
|
-
assert isinstance(v, ir.Var)
|
|
3427
|
-
relation = self.to_relation(item)
|
|
3428
|
-
ctx.add(f.lookup(relation, [v]))
|
|
3429
|
-
return v
|
|
3430
|
-
|
|
3431
|
-
elif isinstance(item, (Relationship, RelationshipRef, RelationshipReading)):
|
|
3432
|
-
params = item._field_refs
|
|
3433
|
-
if item._parent:
|
|
3434
|
-
params = [item._parent] + params[1:]
|
|
3435
|
-
return self.lookup(item(*params), ctx)
|
|
3436
|
-
|
|
3437
|
-
elif isinstance(item, RelationshipFieldRef):
|
|
3438
|
-
rel = item._relationship
|
|
3439
|
-
params = list(rel._field_refs)
|
|
3440
|
-
if item._parent:
|
|
3441
|
-
params = [item._parent] + params[1:]
|
|
3442
|
-
self.lookup(rel(*params), ctx)
|
|
3443
|
-
return self.lookup(params[item._field_ix], ctx)
|
|
3444
|
-
|
|
3445
|
-
elif isinstance(item, Ref):
|
|
3446
|
-
if item._no_lookup:
|
|
3447
|
-
return ctx.to_value(item)
|
|
3448
|
-
|
|
3449
|
-
root = item._thing
|
|
3450
|
-
prev_mapping = ctx.to_value(root)
|
|
3451
|
-
out = ctx.to_value(item)
|
|
3452
|
-
ctx.map_var(root, out)
|
|
3453
|
-
self.lookup(root, ctx)
|
|
3454
|
-
ctx.map_var(root, prev_mapping)
|
|
3455
|
-
return out
|
|
3456
|
-
|
|
3457
|
-
elif isinstance(item, TypeRef):
|
|
3458
|
-
if isinstance(item._thing, Relationship):
|
|
3459
|
-
return self.to_relation(item._thing)
|
|
3460
|
-
concept = to_type(item)
|
|
3461
|
-
if not concept:
|
|
3462
|
-
raise ValueError(f"Cannot find concept for {item}, {type(item)}")
|
|
3463
|
-
return self.to_type(concept)
|
|
3464
|
-
|
|
3465
|
-
elif isinstance(item, ArgumentRef):
|
|
3466
|
-
self.lookup(item._expr, ctx)
|
|
3467
|
-
return ctx.to_value(item._arg)
|
|
3468
|
-
|
|
3469
|
-
elif isinstance(item, Alias):
|
|
3470
|
-
return self.lookup(item._thing, ctx)
|
|
3471
|
-
|
|
3472
|
-
elif isinstance(item, Aggregate):
|
|
3473
|
-
relation = self.to_relation(item._op)
|
|
3474
|
-
|
|
3475
|
-
group = [self.lookup(g, ctx) for g in item._group]
|
|
3476
|
-
group = [item for item in flatten(group, flatten_tuples=True) if isinstance(item, ir.Var)]
|
|
3477
|
-
|
|
3478
|
-
agg_ctx = ctx.clone()
|
|
3479
|
-
|
|
3480
|
-
# additional wheres
|
|
3481
|
-
self.where(item._where, item._where._where, agg_ctx)
|
|
3482
|
-
|
|
3483
|
-
if self._is_rank(item):
|
|
3484
|
-
# The rank output is always an int
|
|
3485
|
-
out = agg_ctx.to_value(item, f.var(to_name(item), types.Int128))
|
|
3486
|
-
assert isinstance(out, ir.Var)
|
|
3487
|
-
|
|
3488
|
-
projection, args, arg_is_ascending = self._process_rank(item._args, agg_ctx)
|
|
3489
|
-
internal_vars = ordered_set()
|
|
3490
|
-
|
|
3491
|
-
ir_node = f.rank(projection.get_list(), group, args.get_list(), arg_is_ascending, out)
|
|
3492
|
-
|
|
3493
|
-
else:
|
|
3494
|
-
out = agg_ctx.to_value(item)
|
|
3495
|
-
assert isinstance(out, ir.Var)
|
|
3496
|
-
arg_count = len(relation.fields) - 1 # skip the result
|
|
3497
|
-
raw_args = flatten([self.lookup(a, agg_ctx) for a in item._args])
|
|
3498
|
-
|
|
3499
|
-
# the projection includes all keys for the args
|
|
3500
|
-
projection = [self.lookup(key, agg_ctx) for key in find_keys(item._args)]
|
|
3501
|
-
# the projection is also all raw_args that aren't consumed by the agg
|
|
3502
|
-
projection += raw_args[:-arg_count] if arg_count else raw_args
|
|
3503
|
-
projection = flatten(projection, flatten_tuples=True)
|
|
3504
|
-
projection = list(dict.fromkeys([item for item in projection if isinstance(item, ir.Var)]))
|
|
3505
|
-
|
|
3506
|
-
# agg args + result var
|
|
3507
|
-
args = raw_args[-arg_count:] if arg_count else []
|
|
3508
|
-
args.append(out)
|
|
3509
|
-
|
|
3510
|
-
internal_vars = set(flatten(raw_args + projection, flatten_tuples=True))
|
|
3511
|
-
ir_node = f.aggregate(relation, projection, group, args)
|
|
3512
|
-
|
|
3513
|
-
final_ctx = ctx.clone()
|
|
3514
|
-
if agg_ctx.items:
|
|
3515
|
-
internal = internal_vars - set(flatten(list(ctx.value_map.values()), flatten_tuples=True))
|
|
3516
|
-
hoisted = [ir.Default(v, None) for v in internal if isinstance(v, ir.Var)]
|
|
3517
|
-
hoisted.sort(key=lambda x: x.var.name)
|
|
3518
|
-
final_ctx.add(f.logical(list(agg_ctx.items), list(hoisted)))
|
|
3519
|
-
final_ctx.add(ir_node)
|
|
3520
|
-
ctx.add(f.logical(list(final_ctx.items), [out]))
|
|
3521
|
-
return out
|
|
3522
|
-
|
|
3523
|
-
elif isinstance(item, Not):
|
|
3524
|
-
not_ctx = ctx.clone()
|
|
3525
|
-
for a in item._args:
|
|
3526
|
-
self.lookup(a, not_ctx)
|
|
3527
|
-
ctx.add(f.not_(f.logical(list(not_ctx.items))))
|
|
3528
|
-
|
|
3529
|
-
elif isinstance(item, Fragment):
|
|
3530
|
-
if item._is_where_only():
|
|
3531
|
-
for where in item._where:
|
|
3532
|
-
self.lookup(where, ctx)
|
|
3533
|
-
return None
|
|
3534
|
-
|
|
3535
|
-
sub_ctx = ctx.clone()
|
|
3536
|
-
|
|
3537
|
-
# if we encounter a select and we aren't already trying to write
|
|
3538
|
-
# it into vars, add some
|
|
3539
|
-
into_vars = ctx.into_vars
|
|
3540
|
-
if not len(into_vars) and item._select:
|
|
3541
|
-
into_vars = sub_ctx.into_vars = flatten([ctx.to_value(s) for s in item._select])
|
|
3542
|
-
|
|
3543
|
-
ctx.add(self.fragment(item, sub_ctx))
|
|
3544
|
-
out = None
|
|
3545
|
-
if len(into_vars) == 1:
|
|
3546
|
-
out = into_vars[0]
|
|
3547
|
-
elif len(into_vars) > 1:
|
|
3548
|
-
out = into_vars
|
|
3549
|
-
elif len(item._select) == 1:
|
|
3550
|
-
out = sub_ctx.to_value(item._select[0])
|
|
3551
|
-
elif len(item._select) > 1:
|
|
3552
|
-
out = flatten([sub_ctx.to_value(s) for s in item._select])
|
|
3553
|
-
|
|
3554
|
-
return out
|
|
3555
|
-
|
|
3556
|
-
elif isinstance(item, (Match, Union)):
|
|
3557
|
-
branches = []
|
|
3558
|
-
vars = []
|
|
3559
|
-
if item._is_select:
|
|
3560
|
-
vars = ctx.fetch_var(item)
|
|
3561
|
-
if isinstance(vars, ir.Var):
|
|
3562
|
-
vars = [vars]
|
|
3563
|
-
elif not vars:
|
|
3564
|
-
vars = [f.var(f"v{i}") for i in range(item._ret_count)]
|
|
3565
|
-
ctx.map_var(item, vars)
|
|
3566
|
-
assert isinstance(vars, list)
|
|
3567
|
-
for branch in item._args:
|
|
3568
|
-
branch_ctx = ctx.clone()
|
|
3569
|
-
branch_ctx.into_vars = vars
|
|
3570
|
-
v = self.lookup(branch, branch_ctx)
|
|
3571
|
-
if not isinstance(v, list):
|
|
3572
|
-
v = [v]
|
|
3573
|
-
for var, ret in zip(vars, v):
|
|
3574
|
-
if var is not ret:
|
|
3575
|
-
relation = self.to_relation(builtins.eq)
|
|
3576
|
-
branch_ctx.add(f.lookup(relation, [var, ret]))
|
|
3577
|
-
# map in parent context the union/match branch var to an original var
|
|
3578
|
-
ctx.map_var(ret, var)
|
|
3579
|
-
branches.append(branch_ctx.safe_wrap(vars))
|
|
3580
|
-
elif all(isinstance(branch, Concept) or isinstance(branch, PY_LITERAL_TYPES) for branch in item._args):
|
|
3581
|
-
vars = [f.var(to_name(item), types.Any)]
|
|
3582
|
-
for branch in item._args:
|
|
3583
|
-
branch_ctx = ctx.clone()
|
|
3584
|
-
v = self.lookup(branch, branch_ctx)
|
|
3585
|
-
assert isinstance(v, (ir.Var, ir.Literal))
|
|
3586
|
-
branch_ctx.add(f.lookup(builtins.eq, [vars[0], v]))
|
|
3587
|
-
branches.append(branch_ctx.safe_wrap(vars))
|
|
3588
|
-
else:
|
|
3589
|
-
for branch in item._args:
|
|
3590
|
-
branch_ctx = ctx.clone()
|
|
3591
|
-
self.update(branch, branch_ctx)
|
|
3592
|
-
branches.append(branch_ctx.safe_wrap([]))
|
|
3593
|
-
if isinstance(item, Union):
|
|
3594
|
-
ctx.add(f.union(branches, vars))
|
|
3595
|
-
else:
|
|
3596
|
-
ctx.add(f.match(branches, vars))
|
|
3597
|
-
|
|
3598
|
-
return vars or None
|
|
3599
|
-
|
|
3600
|
-
elif isinstance(item, BranchRef):
|
|
3601
|
-
vars = ctx.value_map.get(item._match)
|
|
3602
|
-
if not vars:
|
|
3603
|
-
self.lookup(item._match, ctx)
|
|
3604
|
-
vars = ctx.fetch_var(item._match)
|
|
3605
|
-
assert isinstance(vars, list)
|
|
3606
|
-
return vars[item._ix]
|
|
3607
|
-
|
|
3608
|
-
elif isinstance(item, Group):
|
|
3609
|
-
for g in item._group:
|
|
3610
|
-
self.lookup(g, ctx)
|
|
3611
|
-
|
|
3612
|
-
elif isinstance(item, Distinct):
|
|
3613
|
-
vs = [self.lookup(v, ctx) for v in item._args]
|
|
3614
|
-
return flatten(vs)
|
|
3615
|
-
|
|
3616
|
-
elif isinstance(item, Data):
|
|
3617
|
-
refs = [item._row_id] + [i._ref for i in item._cols]
|
|
3618
|
-
vars = flatten([self.lookup(v, ctx) for v in refs])
|
|
3619
|
-
ctx.add(f.data(item._data, vars))
|
|
3620
|
-
return vars[0]
|
|
3621
|
-
|
|
3622
|
-
elif isinstance(item, DataColumn):
|
|
3623
|
-
self.lookup(item._data, ctx)
|
|
3624
|
-
return ctx.to_value(item._ref)
|
|
3625
|
-
|
|
3626
|
-
elif isinstance(item, PY_LITERAL_TYPES):
|
|
3627
|
-
return f.literal(item, literal_value_to_type(item))
|
|
3628
|
-
|
|
3629
|
-
elif item is None:
|
|
3630
|
-
return None
|
|
3631
|
-
|
|
3632
|
-
elif isinstance(item, (ir.Var, ir.Literal)):
|
|
3633
|
-
return item
|
|
3634
|
-
|
|
3635
|
-
elif hasattr(item, "_compile_lookup"):
|
|
3636
|
-
return item._compile_lookup(self, ctx)
|
|
3637
|
-
|
|
3638
|
-
else:
|
|
3639
|
-
raise ValueError(f"Cannot lookup {item}, {type(item)}")
|
|
3640
|
-
|
|
3641
|
-
#--------------------------------------------------
|
|
3642
|
-
# Update
|
|
3643
|
-
#--------------------------------------------------
|
|
3644
|
-
|
|
3645
|
-
def update(self, item:Expression|Match|Union, ctx:CompilerContext) -> ir.Value|list[ir.Var]:
|
|
3646
|
-
if isinstance(item, ConceptExpression):
|
|
3647
|
-
assert isinstance(item._op, Concept)
|
|
3648
|
-
relation = self.to_relation(item._op)
|
|
3649
|
-
(ident, kwargs) = item._params
|
|
3650
|
-
out = ctx.to_value(item)
|
|
3651
|
-
assert isinstance(out, ir.Var)
|
|
3652
|
-
|
|
3653
|
-
# if this is a member lookup, then our out var is just the identity passed in
|
|
3654
|
-
if isinstance(item, ConceptMember):
|
|
3655
|
-
out = self.lookup(ident, ctx)
|
|
3656
|
-
assert not isinstance(out, list)
|
|
3657
|
-
# otherwise we have to construct one
|
|
3658
|
-
else:
|
|
3659
|
-
out = self.explode_ref_schemes(item, ctx, update=True)
|
|
3660
|
-
|
|
3661
|
-
ctx.add(f.derive(relation, [out]))
|
|
3662
|
-
# derive the membership and all the relationships
|
|
3663
|
-
rels = self.relation_dict({attr: v for k, v in kwargs.items() if (attr := getattr(item._op, k, None)) is not None}, ctx)
|
|
3664
|
-
for k, v in rels.items():
|
|
3665
|
-
assert not isinstance(v, list)
|
|
3666
|
-
ctx.add(f.derive(k, [out, v]))
|
|
3667
|
-
return out
|
|
3668
|
-
|
|
3669
|
-
elif isinstance(item, Expression) and item._op is Relationship.builtins["="]:
|
|
3670
|
-
if isinstance(item._params[0], (Relationship, RelationshipRef)):
|
|
3671
|
-
return self.update(item._params[0](item._params[1]), ctx)
|
|
3672
|
-
elif isinstance(item._params[1], (Relationship, RelationshipRef)):
|
|
3673
|
-
return self.update(item._params[1](item._params[0]), ctx)
|
|
3674
|
-
elif isinstance(item._params[0], RelationshipFieldRef) or isinstance(item._params[1], RelationshipFieldRef):
|
|
3675
|
-
raise ValueError("Cannot set fields of a multi-field relationship individually")
|
|
3676
|
-
else:
|
|
3677
|
-
raise ValueError("Cannot set a non-relationship via ==")
|
|
3678
|
-
|
|
3679
|
-
elif isinstance(item, Expression):
|
|
3680
|
-
op = item._op
|
|
3681
|
-
params = flatten([self.lookup(p, ctx) for p in item._params])
|
|
3682
|
-
# the case when root a relationship populated thought a reading
|
|
3683
|
-
if isinstance(op, RelationshipReading) and not item._ignore_root:
|
|
3684
|
-
op = item._op._alt_of
|
|
3685
|
-
# reuse params for the root relationship
|
|
3686
|
-
ref_2_param = {ref: param for ref, param in zip(item._op._field_refs, params)}
|
|
3687
|
-
params = flatten([ref_2_param[ref] for ref in op._field_refs])
|
|
3688
|
-
relation = self.to_relation(op)
|
|
3689
|
-
ctx.add(f.derive(relation, params))
|
|
3690
|
-
return params[-1]
|
|
3691
|
-
|
|
3692
|
-
elif isinstance(item, Relationship) and item._arity() == 1:
|
|
3693
|
-
# implicit update of unary relationships
|
|
3694
|
-
params = flatten([self.lookup(item._parent, ctx)])
|
|
3695
|
-
# normalize parameters through fetch_var if available
|
|
3696
|
-
params = [
|
|
3697
|
-
fetched if isinstance(fetched := ctx.fetch_var(p), ir.Var) else p
|
|
3698
|
-
for p in params
|
|
3699
|
-
]
|
|
3700
|
-
relation = self.to_relation(item)
|
|
3701
|
-
ctx.add(f.derive(relation, params))
|
|
3702
|
-
return params[-1]
|
|
3703
|
-
|
|
3704
|
-
elif isinstance(item, Fragment):
|
|
3705
|
-
self.lookup(item, ctx)
|
|
3706
|
-
|
|
3707
|
-
elif isinstance(item, (Match, Union)):
|
|
3708
|
-
self.lookup(item, ctx)
|
|
3709
|
-
|
|
3710
|
-
elif hasattr(item, "_compile_update"):
|
|
3711
|
-
return item._compile_update(self, ctx)
|
|
3712
|
-
|
|
3713
|
-
else:
|
|
3714
|
-
raise ValueError(f"Cannot update {item}")
|
|
3715
|
-
|
|
3716
|
-
__all__ = ["select", "where", "require", "define", "distinct", "per", "count", "sum", "min", "max", "avg"]
|
|
3717
|
-
|
|
3718
|
-
#--------------------------------------------------
|
|
3719
|
-
# Todo
|
|
3720
|
-
#--------------------------------------------------
|
|
3721
|
-
"""
|
|
3722
|
-
- Syntax
|
|
3723
|
-
✔ construct
|
|
3724
|
-
✔ static data handling
|
|
3725
|
-
✔ Fix fragments to not be chained
|
|
3726
|
-
✔ Extends
|
|
3727
|
-
✔ Quantifiers
|
|
3728
|
-
✔ not
|
|
3729
|
-
✔ exists
|
|
3730
|
-
✔ forall
|
|
3731
|
-
✔ Aggregates
|
|
3732
|
-
✔ Require
|
|
3733
|
-
✘ Multi-step chaining
|
|
3734
|
-
✔ ref
|
|
3735
|
-
✔ alias
|
|
3736
|
-
✔ match
|
|
3737
|
-
✔ union
|
|
3738
|
-
✔ capture all rules
|
|
3739
|
-
✔ implement aliasing
|
|
3740
|
-
✔ support defining relationships via madlibs Relationship("{Person} was born on {birthday:Date}")
|
|
3741
|
-
✔ distinct
|
|
3742
|
-
☐ nested fragments
|
|
3743
|
-
✔ handle relationships with multiple name fields being accessed via prop:
|
|
3744
|
-
Package.shipment = Relationship("{Package} in {Shipment} on {Date}")
|
|
3745
|
-
Package.shipment.date, Package.shipment.shipment, Package.shipment.package
|
|
3746
|
-
☐ sources
|
|
3747
|
-
☐ table
|
|
3748
|
-
☐ csv
|
|
3749
|
-
|
|
3750
|
-
- Compilation
|
|
3751
|
-
✔ simple expressions
|
|
3752
|
-
✔ select
|
|
3753
|
-
✔ then
|
|
3754
|
-
✔ Quantifiers
|
|
3755
|
-
✔ exists
|
|
3756
|
-
✔ forall
|
|
3757
|
-
✔ not
|
|
3758
|
-
✔ Aggregates
|
|
3759
|
-
✔ Determine agg keys from inputs
|
|
3760
|
-
✔ Group
|
|
3761
|
-
✔ Require
|
|
3762
|
-
✔ Alias
|
|
3763
|
-
✔ Ref
|
|
3764
|
-
✔ Match
|
|
3765
|
-
✔ Union
|
|
3766
|
-
✔ whole model
|
|
3767
|
-
✔ distinct
|
|
3768
|
-
✔ add Else to hoists
|
|
3769
|
-
✔ where(..).define(Person.coolness == 10)
|
|
3770
|
-
☐ extends
|
|
3771
|
-
☐ nominals
|
|
3772
|
-
✔ have require find keys and return the keys in the error
|
|
3773
|
-
☐ Match/union with multiple branch refs in a select, duplicates the whole match
|
|
3774
|
-
☐ nested fragments
|
|
3775
|
-
|
|
3776
|
-
☐ Execution
|
|
3777
|
-
✔ basic queries
|
|
3778
|
-
✔ query when iterating over a select
|
|
3779
|
-
☐ debugger hookup
|
|
3780
|
-
☐ table sources
|
|
3781
|
-
☐ graph index
|
|
3782
|
-
☐ exports
|
|
3783
|
-
☐ config overhaul
|
|
3784
|
-
|
|
3785
|
-
"""
|