relationalai 0.12.13__py3-none-any.whl → 0.13.0.dev0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- relationalai/__init__.py +1 -209
- relationalai/config/__init__.py +56 -0
- relationalai/config/config.py +289 -0
- relationalai/config/config_fields.py +86 -0
- relationalai/config/connections/__init__.py +46 -0
- relationalai/config/connections/base.py +23 -0
- relationalai/config/connections/duckdb.py +29 -0
- relationalai/config/connections/snowflake.py +243 -0
- relationalai/config/external/__init__.py +17 -0
- relationalai/config/external/dbt_converter.py +101 -0
- relationalai/config/external/dbt_models.py +93 -0
- relationalai/config/external/snowflake_converter.py +41 -0
- relationalai/config/external/snowflake_models.py +85 -0
- relationalai/config/external/utils.py +19 -0
- relationalai/semantics/__init__.py +146 -22
- relationalai/semantics/backends/lqp/annotations.py +11 -0
- relationalai/semantics/backends/sql/sql_compiler.py +327 -0
- relationalai/semantics/frontend/base.py +1707 -0
- relationalai/semantics/frontend/core.py +179 -0
- relationalai/semantics/frontend/front_compiler.py +1313 -0
- relationalai/semantics/frontend/pprint.py +408 -0
- relationalai/semantics/metamodel/__init__.py +6 -40
- relationalai/semantics/metamodel/builtins.py +205 -769
- relationalai/semantics/metamodel/metamodel.py +437 -0
- relationalai/semantics/metamodel/metamodel_analyzer.py +519 -0
- relationalai/semantics/metamodel/pprint.py +412 -0
- relationalai/semantics/metamodel/rewriter.py +266 -0
- relationalai/semantics/metamodel/typer.py +1378 -0
- relationalai/semantics/std/__init__.py +60 -40
- relationalai/semantics/std/aggregates.py +149 -0
- relationalai/semantics/std/common.py +44 -0
- relationalai/semantics/std/constraints.py +37 -43
- relationalai/semantics/std/datetime.py +246 -135
- relationalai/semantics/std/decimals.py +45 -52
- relationalai/semantics/std/floats.py +13 -5
- relationalai/semantics/std/integers.py +26 -11
- relationalai/semantics/std/math.py +183 -112
- relationalai/semantics/std/numbers.py +86 -0
- relationalai/semantics/std/re.py +80 -62
- relationalai/semantics/std/strings.py +117 -60
- relationalai/shims/executor.py +147 -0
- relationalai/shims/helpers.py +126 -0
- relationalai/shims/hoister.py +221 -0
- relationalai/shims/mm2v0.py +1290 -0
- relationalai/tools/cli/__init__.py +6 -0
- relationalai/tools/cli/cli.py +90 -0
- relationalai/tools/cli/components/__init__.py +5 -0
- relationalai/tools/cli/components/progress_reader.py +1524 -0
- relationalai/tools/cli/components/utils.py +58 -0
- relationalai/tools/cli/config_template.py +45 -0
- relationalai/tools/cli/dev.py +19 -0
- relationalai/tools/debugger.py +289 -183
- relationalai/tools/typer_debugger.py +93 -0
- relationalai/util/dataclasses.py +43 -0
- relationalai/util/docutils.py +40 -0
- relationalai/util/error.py +199 -0
- relationalai/util/format.py +48 -106
- relationalai/util/naming.py +145 -0
- relationalai/util/python.py +35 -0
- relationalai/util/runtime.py +156 -0
- relationalai/util/schema.py +197 -0
- relationalai/util/source.py +185 -0
- relationalai/util/structures.py +163 -0
- relationalai/util/tracing.py +261 -0
- relationalai-0.13.0.dev0.dist-info/METADATA +46 -0
- relationalai-0.13.0.dev0.dist-info/RECORD +488 -0
- relationalai-0.13.0.dev0.dist-info/WHEEL +5 -0
- relationalai-0.13.0.dev0.dist-info/entry_points.txt +3 -0
- relationalai-0.13.0.dev0.dist-info/top_level.txt +2 -0
- v0/relationalai/__init__.py +216 -0
- v0/relationalai/clients/azure.py +477 -0
- v0/relationalai/clients/client.py +912 -0
- v0/relationalai/clients/config.py +673 -0
- v0/relationalai/clients/direct_access_client.py +118 -0
- v0/relationalai/clients/hash_util.py +31 -0
- v0/relationalai/clients/local.py +571 -0
- v0/relationalai/clients/profile_polling.py +73 -0
- v0/relationalai/clients/result_helpers.py +420 -0
- v0/relationalai/clients/snowflake.py +3869 -0
- v0/relationalai/clients/types.py +113 -0
- v0/relationalai/clients/use_index_poller.py +980 -0
- v0/relationalai/clients/util.py +356 -0
- v0/relationalai/debugging.py +389 -0
- v0/relationalai/dsl.py +1749 -0
- v0/relationalai/early_access/builder/__init__.py +30 -0
- v0/relationalai/early_access/builder/builder/__init__.py +35 -0
- v0/relationalai/early_access/builder/snowflake/__init__.py +12 -0
- v0/relationalai/early_access/builder/std/__init__.py +25 -0
- v0/relationalai/early_access/builder/std/decimals/__init__.py +12 -0
- v0/relationalai/early_access/builder/std/integers/__init__.py +12 -0
- v0/relationalai/early_access/builder/std/math/__init__.py +12 -0
- v0/relationalai/early_access/builder/std/strings/__init__.py +14 -0
- v0/relationalai/early_access/devtools/__init__.py +12 -0
- v0/relationalai/early_access/devtools/benchmark_lqp/__init__.py +12 -0
- v0/relationalai/early_access/devtools/extract_lqp/__init__.py +12 -0
- v0/relationalai/early_access/dsl/adapters/orm/adapter_qb.py +427 -0
- v0/relationalai/early_access/dsl/adapters/orm/parser.py +636 -0
- v0/relationalai/early_access/dsl/adapters/owl/adapter.py +176 -0
- v0/relationalai/early_access/dsl/adapters/owl/parser.py +160 -0
- v0/relationalai/early_access/dsl/bindings/common.py +402 -0
- v0/relationalai/early_access/dsl/bindings/csv.py +170 -0
- v0/relationalai/early_access/dsl/bindings/legacy/binding_models.py +143 -0
- v0/relationalai/early_access/dsl/bindings/snowflake.py +64 -0
- v0/relationalai/early_access/dsl/codegen/binder.py +411 -0
- v0/relationalai/early_access/dsl/codegen/common.py +79 -0
- v0/relationalai/early_access/dsl/codegen/helpers.py +23 -0
- v0/relationalai/early_access/dsl/codegen/relations.py +700 -0
- v0/relationalai/early_access/dsl/codegen/weaver.py +417 -0
- v0/relationalai/early_access/dsl/core/builders/__init__.py +47 -0
- v0/relationalai/early_access/dsl/core/builders/logic.py +19 -0
- v0/relationalai/early_access/dsl/core/builders/scalar_constraint.py +11 -0
- v0/relationalai/early_access/dsl/core/constraints/predicate/atomic.py +455 -0
- v0/relationalai/early_access/dsl/core/constraints/predicate/universal.py +73 -0
- v0/relationalai/early_access/dsl/core/constraints/scalar.py +310 -0
- v0/relationalai/early_access/dsl/core/context.py +13 -0
- v0/relationalai/early_access/dsl/core/cset.py +132 -0
- v0/relationalai/early_access/dsl/core/exprs/__init__.py +116 -0
- v0/relationalai/early_access/dsl/core/exprs/relational.py +18 -0
- v0/relationalai/early_access/dsl/core/exprs/scalar.py +412 -0
- v0/relationalai/early_access/dsl/core/instances.py +44 -0
- v0/relationalai/early_access/dsl/core/logic/__init__.py +193 -0
- v0/relationalai/early_access/dsl/core/logic/aggregation.py +98 -0
- v0/relationalai/early_access/dsl/core/logic/exists.py +223 -0
- v0/relationalai/early_access/dsl/core/logic/helper.py +163 -0
- v0/relationalai/early_access/dsl/core/namespaces.py +32 -0
- v0/relationalai/early_access/dsl/core/relations.py +276 -0
- v0/relationalai/early_access/dsl/core/rules.py +112 -0
- v0/relationalai/early_access/dsl/core/std/__init__.py +45 -0
- v0/relationalai/early_access/dsl/core/temporal/recall.py +6 -0
- v0/relationalai/early_access/dsl/core/types/__init__.py +270 -0
- v0/relationalai/early_access/dsl/core/types/concepts.py +128 -0
- v0/relationalai/early_access/dsl/core/types/constrained/__init__.py +267 -0
- v0/relationalai/early_access/dsl/core/types/constrained/nominal.py +143 -0
- v0/relationalai/early_access/dsl/core/types/constrained/subtype.py +124 -0
- v0/relationalai/early_access/dsl/core/types/standard.py +92 -0
- v0/relationalai/early_access/dsl/core/types/unconstrained.py +50 -0
- v0/relationalai/early_access/dsl/core/types/variables.py +203 -0
- v0/relationalai/early_access/dsl/ir/compiler.py +318 -0
- v0/relationalai/early_access/dsl/ir/executor.py +260 -0
- v0/relationalai/early_access/dsl/ontologies/constraints.py +88 -0
- v0/relationalai/early_access/dsl/ontologies/export.py +30 -0
- v0/relationalai/early_access/dsl/ontologies/models.py +453 -0
- v0/relationalai/early_access/dsl/ontologies/python_printer.py +303 -0
- v0/relationalai/early_access/dsl/ontologies/readings.py +60 -0
- v0/relationalai/early_access/dsl/ontologies/relationships.py +322 -0
- v0/relationalai/early_access/dsl/ontologies/roles.py +87 -0
- v0/relationalai/early_access/dsl/ontologies/subtyping.py +55 -0
- v0/relationalai/early_access/dsl/orm/constraints.py +438 -0
- v0/relationalai/early_access/dsl/orm/measures/dimensions.py +200 -0
- v0/relationalai/early_access/dsl/orm/measures/initializer.py +16 -0
- v0/relationalai/early_access/dsl/orm/measures/measure_rules.py +275 -0
- v0/relationalai/early_access/dsl/orm/measures/measures.py +299 -0
- v0/relationalai/early_access/dsl/orm/measures/role_exprs.py +268 -0
- v0/relationalai/early_access/dsl/orm/models.py +256 -0
- v0/relationalai/early_access/dsl/orm/object_oriented_printer.py +344 -0
- v0/relationalai/early_access/dsl/orm/printer.py +469 -0
- v0/relationalai/early_access/dsl/orm/reasoners.py +480 -0
- v0/relationalai/early_access/dsl/orm/relations.py +19 -0
- v0/relationalai/early_access/dsl/orm/relationships.py +251 -0
- v0/relationalai/early_access/dsl/orm/types.py +42 -0
- v0/relationalai/early_access/dsl/orm/utils.py +79 -0
- v0/relationalai/early_access/dsl/orm/verb.py +204 -0
- v0/relationalai/early_access/dsl/physical_metadata/tables.py +133 -0
- v0/relationalai/early_access/dsl/relations.py +170 -0
- v0/relationalai/early_access/dsl/rulesets.py +69 -0
- v0/relationalai/early_access/dsl/schemas/__init__.py +450 -0
- v0/relationalai/early_access/dsl/schemas/builder.py +48 -0
- v0/relationalai/early_access/dsl/schemas/comp_names.py +51 -0
- v0/relationalai/early_access/dsl/schemas/components.py +203 -0
- v0/relationalai/early_access/dsl/schemas/contexts.py +156 -0
- v0/relationalai/early_access/dsl/schemas/exprs.py +89 -0
- v0/relationalai/early_access/dsl/schemas/fragments.py +464 -0
- v0/relationalai/early_access/dsl/serialization.py +79 -0
- v0/relationalai/early_access/dsl/serialize/exporter.py +163 -0
- v0/relationalai/early_access/dsl/snow/api.py +104 -0
- v0/relationalai/early_access/dsl/snow/common.py +76 -0
- v0/relationalai/early_access/dsl/state_mgmt/__init__.py +129 -0
- v0/relationalai/early_access/dsl/state_mgmt/state_charts.py +125 -0
- v0/relationalai/early_access/dsl/state_mgmt/transitions.py +130 -0
- v0/relationalai/early_access/dsl/types/__init__.py +40 -0
- v0/relationalai/early_access/dsl/types/concepts.py +12 -0
- v0/relationalai/early_access/dsl/types/entities.py +135 -0
- v0/relationalai/early_access/dsl/types/values.py +17 -0
- v0/relationalai/early_access/dsl/utils.py +102 -0
- v0/relationalai/early_access/graphs/__init__.py +13 -0
- v0/relationalai/early_access/lqp/__init__.py +12 -0
- v0/relationalai/early_access/lqp/compiler/__init__.py +12 -0
- v0/relationalai/early_access/lqp/constructors/__init__.py +18 -0
- v0/relationalai/early_access/lqp/executor/__init__.py +12 -0
- v0/relationalai/early_access/lqp/ir/__init__.py +12 -0
- v0/relationalai/early_access/lqp/passes/__init__.py +12 -0
- v0/relationalai/early_access/lqp/pragmas/__init__.py +12 -0
- v0/relationalai/early_access/lqp/primitives/__init__.py +12 -0
- v0/relationalai/early_access/lqp/types/__init__.py +12 -0
- v0/relationalai/early_access/lqp/utils/__init__.py +12 -0
- v0/relationalai/early_access/lqp/validators/__init__.py +12 -0
- v0/relationalai/early_access/metamodel/__init__.py +58 -0
- v0/relationalai/early_access/metamodel/builtins/__init__.py +12 -0
- v0/relationalai/early_access/metamodel/compiler/__init__.py +12 -0
- v0/relationalai/early_access/metamodel/dependency/__init__.py +12 -0
- v0/relationalai/early_access/metamodel/factory/__init__.py +17 -0
- v0/relationalai/early_access/metamodel/helpers/__init__.py +12 -0
- v0/relationalai/early_access/metamodel/ir/__init__.py +14 -0
- v0/relationalai/early_access/metamodel/rewrite/__init__.py +7 -0
- v0/relationalai/early_access/metamodel/typer/__init__.py +3 -0
- v0/relationalai/early_access/metamodel/typer/typer/__init__.py +12 -0
- v0/relationalai/early_access/metamodel/types/__init__.py +15 -0
- v0/relationalai/early_access/metamodel/util/__init__.py +15 -0
- v0/relationalai/early_access/metamodel/visitor/__init__.py +12 -0
- v0/relationalai/early_access/rel/__init__.py +12 -0
- v0/relationalai/early_access/rel/executor/__init__.py +12 -0
- v0/relationalai/early_access/rel/rel_utils/__init__.py +12 -0
- v0/relationalai/early_access/rel/rewrite/__init__.py +7 -0
- v0/relationalai/early_access/solvers/__init__.py +19 -0
- v0/relationalai/early_access/sql/__init__.py +11 -0
- v0/relationalai/early_access/sql/executor/__init__.py +3 -0
- v0/relationalai/early_access/sql/rewrite/__init__.py +3 -0
- v0/relationalai/early_access/tests/logging/__init__.py +12 -0
- v0/relationalai/early_access/tests/test_snapshot_base/__init__.py +12 -0
- v0/relationalai/early_access/tests/utils/__init__.py +12 -0
- v0/relationalai/environments/__init__.py +35 -0
- v0/relationalai/environments/base.py +381 -0
- v0/relationalai/environments/colab.py +14 -0
- v0/relationalai/environments/generic.py +71 -0
- v0/relationalai/environments/ipython.py +68 -0
- v0/relationalai/environments/jupyter.py +9 -0
- v0/relationalai/environments/snowbook.py +169 -0
- v0/relationalai/errors.py +2455 -0
- v0/relationalai/experimental/SF.py +38 -0
- v0/relationalai/experimental/inspect.py +47 -0
- v0/relationalai/experimental/pathfinder/__init__.py +158 -0
- v0/relationalai/experimental/pathfinder/api.py +160 -0
- v0/relationalai/experimental/pathfinder/automaton.py +584 -0
- v0/relationalai/experimental/pathfinder/bridge.py +226 -0
- v0/relationalai/experimental/pathfinder/compiler.py +416 -0
- v0/relationalai/experimental/pathfinder/datalog.py +214 -0
- v0/relationalai/experimental/pathfinder/diagnostics.py +56 -0
- v0/relationalai/experimental/pathfinder/filter.py +236 -0
- v0/relationalai/experimental/pathfinder/glushkov.py +439 -0
- v0/relationalai/experimental/pathfinder/options.py +265 -0
- v0/relationalai/experimental/pathfinder/rpq.py +344 -0
- v0/relationalai/experimental/pathfinder/transition.py +200 -0
- v0/relationalai/experimental/pathfinder/utils.py +26 -0
- v0/relationalai/experimental/paths/api.py +143 -0
- v0/relationalai/experimental/paths/benchmarks/grid_graph.py +37 -0
- v0/relationalai/experimental/paths/examples/basic_example.py +40 -0
- v0/relationalai/experimental/paths/examples/minimal_engine_warmup.py +3 -0
- v0/relationalai/experimental/paths/examples/movie_example.py +77 -0
- v0/relationalai/experimental/paths/examples/paths_benchmark.py +115 -0
- v0/relationalai/experimental/paths/examples/paths_example.py +116 -0
- v0/relationalai/experimental/paths/examples/pattern_to_automaton.py +28 -0
- v0/relationalai/experimental/paths/find_paths_via_automaton.py +85 -0
- v0/relationalai/experimental/paths/graph.py +185 -0
- v0/relationalai/experimental/paths/path_algorithms/find_paths.py +280 -0
- v0/relationalai/experimental/paths/path_algorithms/one_sided_ball_repetition.py +26 -0
- v0/relationalai/experimental/paths/path_algorithms/one_sided_ball_upto.py +111 -0
- v0/relationalai/experimental/paths/path_algorithms/single.py +59 -0
- v0/relationalai/experimental/paths/path_algorithms/two_sided_balls_repetition.py +39 -0
- v0/relationalai/experimental/paths/path_algorithms/two_sided_balls_upto.py +103 -0
- v0/relationalai/experimental/paths/path_algorithms/usp-old.py +130 -0
- v0/relationalai/experimental/paths/path_algorithms/usp-tuple.py +183 -0
- v0/relationalai/experimental/paths/path_algorithms/usp.py +150 -0
- v0/relationalai/experimental/paths/product_graph.py +93 -0
- v0/relationalai/experimental/paths/rpq/automaton.py +584 -0
- v0/relationalai/experimental/paths/rpq/diagnostics.py +56 -0
- v0/relationalai/experimental/paths/rpq/rpq.py +378 -0
- v0/relationalai/experimental/paths/tests/tests_limit_sp_max_length.py +90 -0
- v0/relationalai/experimental/paths/tests/tests_limit_sp_multiple.py +119 -0
- v0/relationalai/experimental/paths/tests/tests_limit_sp_single.py +104 -0
- v0/relationalai/experimental/paths/tests/tests_limit_walks_multiple.py +113 -0
- v0/relationalai/experimental/paths/tests/tests_limit_walks_single.py +149 -0
- v0/relationalai/experimental/paths/tests/tests_one_sided_ball_repetition_multiple.py +70 -0
- v0/relationalai/experimental/paths/tests/tests_one_sided_ball_repetition_single.py +64 -0
- v0/relationalai/experimental/paths/tests/tests_one_sided_ball_upto_multiple.py +115 -0
- v0/relationalai/experimental/paths/tests/tests_one_sided_ball_upto_single.py +75 -0
- v0/relationalai/experimental/paths/tests/tests_single_paths.py +152 -0
- v0/relationalai/experimental/paths/tests/tests_single_walks.py +208 -0
- v0/relationalai/experimental/paths/tests/tests_single_walks_undirected.py +297 -0
- v0/relationalai/experimental/paths/tests/tests_two_sided_balls_repetition_multiple.py +107 -0
- v0/relationalai/experimental/paths/tests/tests_two_sided_balls_repetition_single.py +76 -0
- v0/relationalai/experimental/paths/tests/tests_two_sided_balls_upto_multiple.py +76 -0
- v0/relationalai/experimental/paths/tests/tests_two_sided_balls_upto_single.py +110 -0
- v0/relationalai/experimental/paths/tests/tests_usp_nsp_multiple.py +229 -0
- v0/relationalai/experimental/paths/tests/tests_usp_nsp_single.py +108 -0
- v0/relationalai/experimental/paths/tree_agg.py +168 -0
- v0/relationalai/experimental/paths/utilities/iterators.py +27 -0
- v0/relationalai/experimental/paths/utilities/prefix_sum.py +91 -0
- v0/relationalai/experimental/solvers.py +1087 -0
- v0/relationalai/loaders/__init__.py +0 -0
- v0/relationalai/loaders/csv.py +195 -0
- v0/relationalai/loaders/loader.py +177 -0
- v0/relationalai/loaders/types.py +23 -0
- v0/relationalai/rel_emitter.py +373 -0
- v0/relationalai/rel_utils.py +185 -0
- v0/relationalai/semantics/__init__.py +29 -0
- v0/relationalai/semantics/devtools/benchmark_lqp.py +536 -0
- v0/relationalai/semantics/devtools/compilation_manager.py +294 -0
- v0/relationalai/semantics/devtools/extract_lqp.py +110 -0
- v0/relationalai/semantics/internal/internal.py +3785 -0
- v0/relationalai/semantics/internal/snowflake.py +324 -0
- v0/relationalai/semantics/lqp/builtins.py +16 -0
- v0/relationalai/semantics/lqp/compiler.py +22 -0
- v0/relationalai/semantics/lqp/constructors.py +68 -0
- v0/relationalai/semantics/lqp/executor.py +469 -0
- v0/relationalai/semantics/lqp/intrinsics.py +24 -0
- v0/relationalai/semantics/lqp/model2lqp.py +839 -0
- v0/relationalai/semantics/lqp/passes.py +680 -0
- v0/relationalai/semantics/lqp/primitives.py +252 -0
- v0/relationalai/semantics/lqp/result_helpers.py +202 -0
- v0/relationalai/semantics/lqp/rewrite/annotate_constraints.py +57 -0
- v0/relationalai/semantics/lqp/rewrite/cdc.py +216 -0
- v0/relationalai/semantics/lqp/rewrite/extract_common.py +338 -0
- v0/relationalai/semantics/lqp/rewrite/extract_keys.py +449 -0
- v0/relationalai/semantics/lqp/rewrite/function_annotations.py +114 -0
- v0/relationalai/semantics/lqp/rewrite/functional_dependencies.py +314 -0
- v0/relationalai/semantics/lqp/rewrite/quantify_vars.py +296 -0
- v0/relationalai/semantics/lqp/rewrite/splinter.py +76 -0
- v0/relationalai/semantics/lqp/types.py +101 -0
- v0/relationalai/semantics/lqp/utils.py +160 -0
- v0/relationalai/semantics/lqp/validators.py +57 -0
- v0/relationalai/semantics/metamodel/__init__.py +40 -0
- v0/relationalai/semantics/metamodel/builtins.py +774 -0
- v0/relationalai/semantics/metamodel/compiler.py +133 -0
- v0/relationalai/semantics/metamodel/dependency.py +862 -0
- v0/relationalai/semantics/metamodel/executor.py +61 -0
- v0/relationalai/semantics/metamodel/factory.py +287 -0
- v0/relationalai/semantics/metamodel/helpers.py +361 -0
- v0/relationalai/semantics/metamodel/rewrite/discharge_constraints.py +39 -0
- v0/relationalai/semantics/metamodel/rewrite/dnf_union_splitter.py +210 -0
- v0/relationalai/semantics/metamodel/rewrite/extract_nested_logicals.py +78 -0
- v0/relationalai/semantics/metamodel/rewrite/flatten.py +549 -0
- v0/relationalai/semantics/metamodel/rewrite/format_outputs.py +165 -0
- v0/relationalai/semantics/metamodel/typer/checker.py +353 -0
- v0/relationalai/semantics/metamodel/typer/typer.py +1395 -0
- v0/relationalai/semantics/reasoners/__init__.py +10 -0
- v0/relationalai/semantics/reasoners/graph/__init__.py +37 -0
- v0/relationalai/semantics/reasoners/graph/core.py +9020 -0
- v0/relationalai/semantics/reasoners/optimization/__init__.py +68 -0
- v0/relationalai/semantics/reasoners/optimization/common.py +88 -0
- v0/relationalai/semantics/reasoners/optimization/solvers_dev.py +568 -0
- v0/relationalai/semantics/reasoners/optimization/solvers_pb.py +1163 -0
- v0/relationalai/semantics/rel/builtins.py +40 -0
- v0/relationalai/semantics/rel/compiler.py +989 -0
- v0/relationalai/semantics/rel/executor.py +359 -0
- v0/relationalai/semantics/rel/rel.py +482 -0
- v0/relationalai/semantics/rel/rel_utils.py +276 -0
- v0/relationalai/semantics/snowflake/__init__.py +3 -0
- v0/relationalai/semantics/sql/compiler.py +2503 -0
- v0/relationalai/semantics/sql/executor/duck_db.py +52 -0
- v0/relationalai/semantics/sql/executor/result_helpers.py +64 -0
- v0/relationalai/semantics/sql/executor/snowflake.py +145 -0
- v0/relationalai/semantics/sql/rewrite/denormalize.py +222 -0
- v0/relationalai/semantics/sql/rewrite/double_negation.py +49 -0
- v0/relationalai/semantics/sql/rewrite/recursive_union.py +127 -0
- v0/relationalai/semantics/sql/rewrite/sort_output_query.py +246 -0
- v0/relationalai/semantics/sql/sql.py +504 -0
- v0/relationalai/semantics/std/__init__.py +54 -0
- v0/relationalai/semantics/std/constraints.py +43 -0
- v0/relationalai/semantics/std/datetime.py +363 -0
- v0/relationalai/semantics/std/decimals.py +62 -0
- v0/relationalai/semantics/std/floats.py +7 -0
- v0/relationalai/semantics/std/integers.py +22 -0
- v0/relationalai/semantics/std/math.py +141 -0
- v0/relationalai/semantics/std/pragmas.py +11 -0
- v0/relationalai/semantics/std/re.py +83 -0
- v0/relationalai/semantics/std/std.py +14 -0
- v0/relationalai/semantics/std/strings.py +63 -0
- v0/relationalai/semantics/tests/__init__.py +0 -0
- v0/relationalai/semantics/tests/test_snapshot_abstract.py +143 -0
- v0/relationalai/semantics/tests/test_snapshot_base.py +9 -0
- v0/relationalai/semantics/tests/utils.py +46 -0
- v0/relationalai/std/__init__.py +70 -0
- v0/relationalai/tools/__init__.py +0 -0
- v0/relationalai/tools/cli.py +1940 -0
- v0/relationalai/tools/cli_controls.py +1826 -0
- v0/relationalai/tools/cli_helpers.py +390 -0
- v0/relationalai/tools/debugger.py +183 -0
- v0/relationalai/tools/debugger_client.py +109 -0
- v0/relationalai/tools/debugger_server.py +302 -0
- v0/relationalai/tools/dev.py +685 -0
- v0/relationalai/tools/qb_debugger.py +425 -0
- v0/relationalai/util/clean_up_databases.py +95 -0
- v0/relationalai/util/format.py +123 -0
- v0/relationalai/util/list_databases.py +9 -0
- v0/relationalai/util/otel_configuration.py +25 -0
- v0/relationalai/util/otel_handler.py +484 -0
- v0/relationalai/util/snowflake_handler.py +88 -0
- v0/relationalai/util/span_format_test.py +43 -0
- v0/relationalai/util/span_tracker.py +207 -0
- v0/relationalai/util/spans_file_handler.py +72 -0
- v0/relationalai/util/tracing_handler.py +34 -0
- frontend/debugger/dist/.gitignore +0 -2
- frontend/debugger/dist/assets/favicon-Dy0ZgA6N.png +0 -0
- frontend/debugger/dist/assets/index-Cssla-O7.js +0 -208
- frontend/debugger/dist/assets/index-DlHsYx1V.css +0 -9
- frontend/debugger/dist/index.html +0 -17
- relationalai/clients/azure.py +0 -477
- relationalai/clients/client.py +0 -912
- relationalai/clients/config.py +0 -673
- relationalai/clients/direct_access_client.py +0 -118
- relationalai/clients/export_procedure.py.jinja +0 -249
- relationalai/clients/hash_util.py +0 -31
- relationalai/clients/local.py +0 -571
- relationalai/clients/profile_polling.py +0 -73
- relationalai/clients/result_helpers.py +0 -420
- relationalai/clients/snowflake.py +0 -3869
- relationalai/clients/types.py +0 -113
- relationalai/clients/use_index_poller.py +0 -980
- relationalai/clients/util.py +0 -356
- relationalai/debugging.py +0 -389
- relationalai/dsl.py +0 -1749
- relationalai/early_access/builder/__init__.py +0 -30
- relationalai/early_access/builder/builder/__init__.py +0 -35
- relationalai/early_access/builder/snowflake/__init__.py +0 -12
- relationalai/early_access/builder/std/__init__.py +0 -25
- relationalai/early_access/builder/std/decimals/__init__.py +0 -12
- relationalai/early_access/builder/std/integers/__init__.py +0 -12
- relationalai/early_access/builder/std/math/__init__.py +0 -12
- relationalai/early_access/builder/std/strings/__init__.py +0 -14
- relationalai/early_access/devtools/__init__.py +0 -12
- relationalai/early_access/devtools/benchmark_lqp/__init__.py +0 -12
- relationalai/early_access/devtools/extract_lqp/__init__.py +0 -12
- relationalai/early_access/dsl/adapters/orm/adapter_qb.py +0 -427
- relationalai/early_access/dsl/adapters/orm/parser.py +0 -636
- relationalai/early_access/dsl/adapters/owl/adapter.py +0 -176
- relationalai/early_access/dsl/adapters/owl/parser.py +0 -160
- relationalai/early_access/dsl/bindings/common.py +0 -402
- relationalai/early_access/dsl/bindings/csv.py +0 -170
- relationalai/early_access/dsl/bindings/legacy/binding_models.py +0 -143
- relationalai/early_access/dsl/bindings/snowflake.py +0 -64
- relationalai/early_access/dsl/codegen/binder.py +0 -411
- relationalai/early_access/dsl/codegen/common.py +0 -79
- relationalai/early_access/dsl/codegen/helpers.py +0 -23
- relationalai/early_access/dsl/codegen/relations.py +0 -700
- relationalai/early_access/dsl/codegen/weaver.py +0 -417
- relationalai/early_access/dsl/core/builders/__init__.py +0 -47
- relationalai/early_access/dsl/core/builders/logic.py +0 -19
- relationalai/early_access/dsl/core/builders/scalar_constraint.py +0 -11
- relationalai/early_access/dsl/core/constraints/predicate/atomic.py +0 -455
- relationalai/early_access/dsl/core/constraints/predicate/universal.py +0 -73
- relationalai/early_access/dsl/core/constraints/scalar.py +0 -310
- relationalai/early_access/dsl/core/context.py +0 -13
- relationalai/early_access/dsl/core/cset.py +0 -132
- relationalai/early_access/dsl/core/exprs/__init__.py +0 -116
- relationalai/early_access/dsl/core/exprs/relational.py +0 -18
- relationalai/early_access/dsl/core/exprs/scalar.py +0 -412
- relationalai/early_access/dsl/core/instances.py +0 -44
- relationalai/early_access/dsl/core/logic/__init__.py +0 -193
- relationalai/early_access/dsl/core/logic/aggregation.py +0 -98
- relationalai/early_access/dsl/core/logic/exists.py +0 -223
- relationalai/early_access/dsl/core/logic/helper.py +0 -163
- relationalai/early_access/dsl/core/namespaces.py +0 -32
- relationalai/early_access/dsl/core/relations.py +0 -276
- relationalai/early_access/dsl/core/rules.py +0 -112
- relationalai/early_access/dsl/core/std/__init__.py +0 -45
- relationalai/early_access/dsl/core/temporal/recall.py +0 -6
- relationalai/early_access/dsl/core/types/__init__.py +0 -270
- relationalai/early_access/dsl/core/types/concepts.py +0 -128
- relationalai/early_access/dsl/core/types/constrained/__init__.py +0 -267
- relationalai/early_access/dsl/core/types/constrained/nominal.py +0 -143
- relationalai/early_access/dsl/core/types/constrained/subtype.py +0 -124
- relationalai/early_access/dsl/core/types/standard.py +0 -92
- relationalai/early_access/dsl/core/types/unconstrained.py +0 -50
- relationalai/early_access/dsl/core/types/variables.py +0 -203
- relationalai/early_access/dsl/ir/compiler.py +0 -318
- relationalai/early_access/dsl/ir/executor.py +0 -260
- relationalai/early_access/dsl/ontologies/constraints.py +0 -88
- relationalai/early_access/dsl/ontologies/export.py +0 -30
- relationalai/early_access/dsl/ontologies/models.py +0 -453
- relationalai/early_access/dsl/ontologies/python_printer.py +0 -303
- relationalai/early_access/dsl/ontologies/readings.py +0 -60
- relationalai/early_access/dsl/ontologies/relationships.py +0 -322
- relationalai/early_access/dsl/ontologies/roles.py +0 -87
- relationalai/early_access/dsl/ontologies/subtyping.py +0 -55
- relationalai/early_access/dsl/orm/constraints.py +0 -438
- relationalai/early_access/dsl/orm/measures/dimensions.py +0 -200
- relationalai/early_access/dsl/orm/measures/initializer.py +0 -16
- relationalai/early_access/dsl/orm/measures/measure_rules.py +0 -275
- relationalai/early_access/dsl/orm/measures/measures.py +0 -299
- relationalai/early_access/dsl/orm/measures/role_exprs.py +0 -268
- relationalai/early_access/dsl/orm/models.py +0 -256
- relationalai/early_access/dsl/orm/object_oriented_printer.py +0 -344
- relationalai/early_access/dsl/orm/printer.py +0 -469
- relationalai/early_access/dsl/orm/reasoners.py +0 -480
- relationalai/early_access/dsl/orm/relations.py +0 -19
- relationalai/early_access/dsl/orm/relationships.py +0 -251
- relationalai/early_access/dsl/orm/types.py +0 -42
- relationalai/early_access/dsl/orm/utils.py +0 -79
- relationalai/early_access/dsl/orm/verb.py +0 -204
- relationalai/early_access/dsl/physical_metadata/tables.py +0 -133
- relationalai/early_access/dsl/relations.py +0 -170
- relationalai/early_access/dsl/rulesets.py +0 -69
- relationalai/early_access/dsl/schemas/__init__.py +0 -450
- relationalai/early_access/dsl/schemas/builder.py +0 -48
- relationalai/early_access/dsl/schemas/comp_names.py +0 -51
- relationalai/early_access/dsl/schemas/components.py +0 -203
- relationalai/early_access/dsl/schemas/contexts.py +0 -156
- relationalai/early_access/dsl/schemas/exprs.py +0 -89
- relationalai/early_access/dsl/schemas/fragments.py +0 -464
- relationalai/early_access/dsl/serialization.py +0 -79
- relationalai/early_access/dsl/serialize/exporter.py +0 -163
- relationalai/early_access/dsl/snow/api.py +0 -104
- relationalai/early_access/dsl/snow/common.py +0 -76
- relationalai/early_access/dsl/state_mgmt/__init__.py +0 -129
- relationalai/early_access/dsl/state_mgmt/state_charts.py +0 -125
- relationalai/early_access/dsl/state_mgmt/transitions.py +0 -130
- relationalai/early_access/dsl/types/__init__.py +0 -40
- relationalai/early_access/dsl/types/concepts.py +0 -12
- relationalai/early_access/dsl/types/entities.py +0 -135
- relationalai/early_access/dsl/types/values.py +0 -17
- relationalai/early_access/dsl/utils.py +0 -102
- relationalai/early_access/graphs/__init__.py +0 -13
- relationalai/early_access/lqp/__init__.py +0 -12
- relationalai/early_access/lqp/compiler/__init__.py +0 -12
- relationalai/early_access/lqp/constructors/__init__.py +0 -18
- relationalai/early_access/lqp/executor/__init__.py +0 -12
- relationalai/early_access/lqp/ir/__init__.py +0 -12
- relationalai/early_access/lqp/passes/__init__.py +0 -12
- relationalai/early_access/lqp/pragmas/__init__.py +0 -12
- relationalai/early_access/lqp/primitives/__init__.py +0 -12
- relationalai/early_access/lqp/types/__init__.py +0 -12
- relationalai/early_access/lqp/utils/__init__.py +0 -12
- relationalai/early_access/lqp/validators/__init__.py +0 -12
- relationalai/early_access/metamodel/__init__.py +0 -58
- relationalai/early_access/metamodel/builtins/__init__.py +0 -12
- relationalai/early_access/metamodel/compiler/__init__.py +0 -12
- relationalai/early_access/metamodel/dependency/__init__.py +0 -12
- relationalai/early_access/metamodel/factory/__init__.py +0 -17
- relationalai/early_access/metamodel/helpers/__init__.py +0 -12
- relationalai/early_access/metamodel/ir/__init__.py +0 -14
- relationalai/early_access/metamodel/rewrite/__init__.py +0 -7
- relationalai/early_access/metamodel/typer/__init__.py +0 -3
- relationalai/early_access/metamodel/typer/typer/__init__.py +0 -12
- relationalai/early_access/metamodel/types/__init__.py +0 -15
- relationalai/early_access/metamodel/util/__init__.py +0 -15
- relationalai/early_access/metamodel/visitor/__init__.py +0 -12
- relationalai/early_access/rel/__init__.py +0 -12
- relationalai/early_access/rel/executor/__init__.py +0 -12
- relationalai/early_access/rel/rel_utils/__init__.py +0 -12
- relationalai/early_access/rel/rewrite/__init__.py +0 -7
- relationalai/early_access/solvers/__init__.py +0 -19
- relationalai/early_access/sql/__init__.py +0 -11
- relationalai/early_access/sql/executor/__init__.py +0 -3
- relationalai/early_access/sql/rewrite/__init__.py +0 -3
- relationalai/early_access/tests/logging/__init__.py +0 -12
- relationalai/early_access/tests/test_snapshot_base/__init__.py +0 -12
- relationalai/early_access/tests/utils/__init__.py +0 -12
- relationalai/environments/__init__.py +0 -35
- relationalai/environments/base.py +0 -381
- relationalai/environments/colab.py +0 -14
- relationalai/environments/generic.py +0 -71
- relationalai/environments/ipython.py +0 -68
- relationalai/environments/jupyter.py +0 -9
- relationalai/environments/snowbook.py +0 -169
- relationalai/errors.py +0 -2455
- relationalai/experimental/SF.py +0 -38
- relationalai/experimental/inspect.py +0 -47
- relationalai/experimental/pathfinder/__init__.py +0 -158
- relationalai/experimental/pathfinder/api.py +0 -160
- relationalai/experimental/pathfinder/automaton.py +0 -584
- relationalai/experimental/pathfinder/bridge.py +0 -226
- relationalai/experimental/pathfinder/compiler.py +0 -416
- relationalai/experimental/pathfinder/datalog.py +0 -214
- relationalai/experimental/pathfinder/diagnostics.py +0 -56
- relationalai/experimental/pathfinder/filter.py +0 -236
- relationalai/experimental/pathfinder/glushkov.py +0 -439
- relationalai/experimental/pathfinder/options.py +0 -265
- relationalai/experimental/pathfinder/pathfinder-v0.7.0.rel +0 -1951
- relationalai/experimental/pathfinder/rpq.py +0 -344
- relationalai/experimental/pathfinder/transition.py +0 -200
- relationalai/experimental/pathfinder/utils.py +0 -26
- relationalai/experimental/paths/README.md +0 -107
- relationalai/experimental/paths/api.py +0 -143
- relationalai/experimental/paths/benchmarks/grid_graph.py +0 -37
- relationalai/experimental/paths/code_organization.md +0 -2
- relationalai/experimental/paths/examples/Movies.ipynb +0 -16328
- relationalai/experimental/paths/examples/basic_example.py +0 -40
- relationalai/experimental/paths/examples/minimal_engine_warmup.py +0 -3
- relationalai/experimental/paths/examples/movie_example.py +0 -77
- relationalai/experimental/paths/examples/movies_data/actedin.csv +0 -193
- relationalai/experimental/paths/examples/movies_data/directed.csv +0 -45
- relationalai/experimental/paths/examples/movies_data/follows.csv +0 -7
- relationalai/experimental/paths/examples/movies_data/movies.csv +0 -39
- relationalai/experimental/paths/examples/movies_data/person.csv +0 -134
- relationalai/experimental/paths/examples/movies_data/produced.csv +0 -16
- relationalai/experimental/paths/examples/movies_data/ratings.csv +0 -10
- relationalai/experimental/paths/examples/movies_data/wrote.csv +0 -11
- relationalai/experimental/paths/examples/paths_benchmark.py +0 -115
- relationalai/experimental/paths/examples/paths_example.py +0 -116
- relationalai/experimental/paths/examples/pattern_to_automaton.py +0 -28
- relationalai/experimental/paths/find_paths_via_automaton.py +0 -85
- relationalai/experimental/paths/graph.py +0 -185
- relationalai/experimental/paths/path_algorithms/find_paths.py +0 -280
- relationalai/experimental/paths/path_algorithms/one_sided_ball_repetition.py +0 -26
- relationalai/experimental/paths/path_algorithms/one_sided_ball_upto.py +0 -111
- relationalai/experimental/paths/path_algorithms/single.py +0 -59
- relationalai/experimental/paths/path_algorithms/two_sided_balls_repetition.py +0 -39
- relationalai/experimental/paths/path_algorithms/two_sided_balls_upto.py +0 -103
- relationalai/experimental/paths/path_algorithms/usp-old.py +0 -130
- relationalai/experimental/paths/path_algorithms/usp-tuple.py +0 -183
- relationalai/experimental/paths/path_algorithms/usp.py +0 -150
- relationalai/experimental/paths/product_graph.py +0 -93
- relationalai/experimental/paths/rpq/automaton.py +0 -584
- relationalai/experimental/paths/rpq/diagnostics.py +0 -56
- relationalai/experimental/paths/rpq/rpq.py +0 -378
- relationalai/experimental/paths/tests/tests_limit_sp_max_length.py +0 -90
- relationalai/experimental/paths/tests/tests_limit_sp_multiple.py +0 -119
- relationalai/experimental/paths/tests/tests_limit_sp_single.py +0 -104
- relationalai/experimental/paths/tests/tests_limit_walks_multiple.py +0 -113
- relationalai/experimental/paths/tests/tests_limit_walks_single.py +0 -149
- relationalai/experimental/paths/tests/tests_one_sided_ball_repetition_multiple.py +0 -70
- relationalai/experimental/paths/tests/tests_one_sided_ball_repetition_single.py +0 -64
- relationalai/experimental/paths/tests/tests_one_sided_ball_upto_multiple.py +0 -115
- relationalai/experimental/paths/tests/tests_one_sided_ball_upto_single.py +0 -75
- relationalai/experimental/paths/tests/tests_single_paths.py +0 -152
- relationalai/experimental/paths/tests/tests_single_walks.py +0 -208
- relationalai/experimental/paths/tests/tests_single_walks_undirected.py +0 -297
- relationalai/experimental/paths/tests/tests_two_sided_balls_repetition_multiple.py +0 -107
- relationalai/experimental/paths/tests/tests_two_sided_balls_repetition_single.py +0 -76
- relationalai/experimental/paths/tests/tests_two_sided_balls_upto_multiple.py +0 -76
- relationalai/experimental/paths/tests/tests_two_sided_balls_upto_single.py +0 -110
- relationalai/experimental/paths/tests/tests_usp_nsp_multiple.py +0 -229
- relationalai/experimental/paths/tests/tests_usp_nsp_single.py +0 -108
- relationalai/experimental/paths/tree_agg.py +0 -168
- relationalai/experimental/paths/utilities/iterators.py +0 -27
- relationalai/experimental/paths/utilities/prefix_sum.py +0 -91
- relationalai/experimental/solvers.py +0 -1087
- relationalai/loaders/csv.py +0 -195
- relationalai/loaders/loader.py +0 -177
- relationalai/loaders/types.py +0 -23
- relationalai/rel_emitter.py +0 -373
- relationalai/rel_utils.py +0 -185
- relationalai/semantics/designs/query_builder/identify_by.md +0 -106
- relationalai/semantics/devtools/benchmark_lqp.py +0 -536
- relationalai/semantics/devtools/compilation_manager.py +0 -294
- relationalai/semantics/devtools/extract_lqp.py +0 -110
- relationalai/semantics/internal/internal.py +0 -3785
- relationalai/semantics/internal/snowflake.py +0 -324
- relationalai/semantics/lqp/README.md +0 -34
- relationalai/semantics/lqp/builtins.py +0 -16
- relationalai/semantics/lqp/compiler.py +0 -22
- relationalai/semantics/lqp/constructors.py +0 -68
- relationalai/semantics/lqp/executor.py +0 -469
- relationalai/semantics/lqp/intrinsics.py +0 -24
- relationalai/semantics/lqp/model2lqp.py +0 -839
- relationalai/semantics/lqp/passes.py +0 -680
- relationalai/semantics/lqp/primitives.py +0 -252
- relationalai/semantics/lqp/result_helpers.py +0 -202
- relationalai/semantics/lqp/rewrite/annotate_constraints.py +0 -57
- relationalai/semantics/lqp/rewrite/cdc.py +0 -216
- relationalai/semantics/lqp/rewrite/extract_common.py +0 -338
- relationalai/semantics/lqp/rewrite/extract_keys.py +0 -449
- relationalai/semantics/lqp/rewrite/function_annotations.py +0 -114
- relationalai/semantics/lqp/rewrite/functional_dependencies.py +0 -314
- relationalai/semantics/lqp/rewrite/quantify_vars.py +0 -296
- relationalai/semantics/lqp/rewrite/splinter.py +0 -76
- relationalai/semantics/lqp/types.py +0 -101
- relationalai/semantics/lqp/utils.py +0 -160
- relationalai/semantics/lqp/validators.py +0 -57
- relationalai/semantics/metamodel/compiler.py +0 -133
- relationalai/semantics/metamodel/dependency.py +0 -862
- relationalai/semantics/metamodel/executor.py +0 -61
- relationalai/semantics/metamodel/factory.py +0 -287
- relationalai/semantics/metamodel/helpers.py +0 -361
- relationalai/semantics/metamodel/rewrite/discharge_constraints.py +0 -39
- relationalai/semantics/metamodel/rewrite/dnf_union_splitter.py +0 -210
- relationalai/semantics/metamodel/rewrite/extract_nested_logicals.py +0 -78
- relationalai/semantics/metamodel/rewrite/flatten.py +0 -549
- relationalai/semantics/metamodel/rewrite/format_outputs.py +0 -165
- relationalai/semantics/metamodel/typer/checker.py +0 -353
- relationalai/semantics/metamodel/typer/typer.py +0 -1395
- relationalai/semantics/reasoners/__init__.py +0 -10
- relationalai/semantics/reasoners/graph/README.md +0 -620
- relationalai/semantics/reasoners/graph/__init__.py +0 -37
- relationalai/semantics/reasoners/graph/core.py +0 -9020
- relationalai/semantics/reasoners/graph/design/beyond_demand_transform.md +0 -797
- relationalai/semantics/reasoners/graph/tests/README.md +0 -21
- relationalai/semantics/reasoners/optimization/__init__.py +0 -68
- relationalai/semantics/reasoners/optimization/common.py +0 -88
- relationalai/semantics/reasoners/optimization/solvers_dev.py +0 -568
- relationalai/semantics/reasoners/optimization/solvers_pb.py +0 -1163
- relationalai/semantics/rel/builtins.py +0 -40
- relationalai/semantics/rel/compiler.py +0 -989
- relationalai/semantics/rel/executor.py +0 -359
- relationalai/semantics/rel/rel.py +0 -482
- relationalai/semantics/rel/rel_utils.py +0 -276
- relationalai/semantics/snowflake/__init__.py +0 -3
- relationalai/semantics/sql/compiler.py +0 -2503
- relationalai/semantics/sql/executor/duck_db.py +0 -52
- relationalai/semantics/sql/executor/result_helpers.py +0 -64
- relationalai/semantics/sql/executor/snowflake.py +0 -145
- relationalai/semantics/sql/rewrite/denormalize.py +0 -222
- relationalai/semantics/sql/rewrite/double_negation.py +0 -49
- relationalai/semantics/sql/rewrite/recursive_union.py +0 -127
- relationalai/semantics/sql/rewrite/sort_output_query.py +0 -246
- relationalai/semantics/sql/sql.py +0 -504
- relationalai/semantics/std/pragmas.py +0 -11
- relationalai/semantics/std/std.py +0 -14
- relationalai/semantics/tests/test_snapshot_abstract.py +0 -143
- relationalai/semantics/tests/test_snapshot_base.py +0 -9
- relationalai/semantics/tests/utils.py +0 -46
- relationalai/std/__init__.py +0 -70
- relationalai/tools/cli.py +0 -1940
- relationalai/tools/cli_controls.py +0 -1826
- relationalai/tools/cli_helpers.py +0 -390
- relationalai/tools/debugger_client.py +0 -109
- relationalai/tools/debugger_server.py +0 -302
- relationalai/tools/dev.py +0 -685
- relationalai/tools/notes +0 -7
- relationalai/tools/qb_debugger.py +0 -425
- relationalai/util/clean_up_databases.py +0 -95
- relationalai/util/list_databases.py +0 -9
- relationalai/util/otel_configuration.py +0 -25
- relationalai/util/otel_handler.py +0 -484
- relationalai/util/snowflake_handler.py +0 -88
- relationalai/util/span_format_test.py +0 -43
- relationalai/util/span_tracker.py +0 -207
- relationalai/util/spans_file_handler.py +0 -72
- relationalai/util/tracing_handler.py +0 -34
- relationalai-0.12.13.dist-info/METADATA +0 -74
- relationalai-0.12.13.dist-info/RECORD +0 -449
- relationalai-0.12.13.dist-info/WHEEL +0 -4
- relationalai-0.12.13.dist-info/entry_points.txt +0 -3
- relationalai-0.12.13.dist-info/licenses/LICENSE +0 -202
- relationalai_test_util/__init__.py +0 -4
- relationalai_test_util/fixtures.py +0 -228
- relationalai_test_util/snapshot.py +0 -252
- relationalai_test_util/traceback.py +0 -118
- /relationalai/{analysis → semantics/frontend}/__init__.py +0 -0
- /relationalai/{auth/__init__.py → semantics/metamodel/metamodel_compiler.py} +0 -0
- /relationalai/{early_access → shims}/__init__.py +0 -0
- {relationalai/early_access/dsl/adapters → v0/relationalai/analysis}/__init__.py +0 -0
- {relationalai → v0/relationalai}/analysis/mechanistic.py +0 -0
- {relationalai → v0/relationalai}/analysis/whynot.py +0 -0
- {relationalai/early_access/dsl/adapters/orm → v0/relationalai/auth}/__init__.py +0 -0
- {relationalai → v0/relationalai}/auth/jwt_generator.py +0 -0
- {relationalai → v0/relationalai}/auth/oauth_callback_server.py +0 -0
- {relationalai → v0/relationalai}/auth/token_handler.py +0 -0
- {relationalai → v0/relationalai}/auth/util.py +0 -0
- {relationalai → v0/relationalai}/clients/__init__.py +0 -0
- {relationalai → v0/relationalai}/clients/cache_store.py +0 -0
- {relationalai → v0/relationalai}/compiler.py +0 -0
- {relationalai → v0/relationalai}/dependencies.py +0 -0
- {relationalai → v0/relationalai}/docutils.py +0 -0
- {relationalai/early_access/dsl/adapters/owl → v0/relationalai/early_access}/__init__.py +0 -0
- {relationalai → v0/relationalai}/early_access/dsl/__init__.py +0 -0
- {relationalai/early_access/dsl/bindings → v0/relationalai/early_access/dsl/adapters}/__init__.py +0 -0
- {relationalai/early_access/dsl/bindings/legacy → v0/relationalai/early_access/dsl/adapters/orm}/__init__.py +0 -0
- {relationalai → v0/relationalai}/early_access/dsl/adapters/orm/model.py +0 -0
- {relationalai/early_access/dsl/codegen → v0/relationalai/early_access/dsl/adapters/owl}/__init__.py +0 -0
- {relationalai → v0/relationalai}/early_access/dsl/adapters/owl/model.py +0 -0
- {relationalai/early_access/dsl/core/temporal → v0/relationalai/early_access/dsl/bindings}/__init__.py +0 -0
- {relationalai/early_access/dsl/ir → v0/relationalai/early_access/dsl/bindings/legacy}/__init__.py +0 -0
- {relationalai/early_access/dsl/ontologies → v0/relationalai/early_access/dsl/codegen}/__init__.py +0 -0
- {relationalai → v0/relationalai}/early_access/dsl/constants.py +0 -0
- {relationalai → v0/relationalai}/early_access/dsl/core/__init__.py +0 -0
- {relationalai → v0/relationalai}/early_access/dsl/core/constraints/__init__.py +0 -0
- {relationalai → v0/relationalai}/early_access/dsl/core/constraints/predicate/__init__.py +0 -0
- {relationalai → v0/relationalai}/early_access/dsl/core/stack.py +0 -0
- {relationalai/early_access/dsl/orm → v0/relationalai/early_access/dsl/core/temporal}/__init__.py +0 -0
- {relationalai → v0/relationalai}/early_access/dsl/core/utils.py +0 -0
- {relationalai/early_access/dsl/orm/measures → v0/relationalai/early_access/dsl/ir}/__init__.py +0 -0
- {relationalai/early_access/dsl/physical_metadata → v0/relationalai/early_access/dsl/ontologies}/__init__.py +0 -0
- {relationalai → v0/relationalai}/early_access/dsl/ontologies/raw_source.py +0 -0
- {relationalai/early_access/dsl/serialize → v0/relationalai/early_access/dsl/orm}/__init__.py +0 -0
- {relationalai/early_access/dsl/snow → v0/relationalai/early_access/dsl/orm/measures}/__init__.py +0 -0
- {relationalai → v0/relationalai}/early_access/dsl/orm/reasoner_errors.py +0 -0
- {relationalai/loaders → v0/relationalai/early_access/dsl/physical_metadata}/__init__.py +0 -0
- {relationalai/semantics/tests → v0/relationalai/early_access/dsl/serialize}/__init__.py +0 -0
- {relationalai → v0/relationalai}/early_access/dsl/serialize/binding_model.py +0 -0
- {relationalai → v0/relationalai}/early_access/dsl/serialize/model.py +0 -0
- {relationalai/tools → v0/relationalai/early_access/dsl/snow}/__init__.py +0 -0
- {relationalai → v0/relationalai}/early_access/tests/__init__.py +0 -0
- {relationalai → v0/relationalai}/environments/ci.py +0 -0
- {relationalai → v0/relationalai}/environments/hex.py +0 -0
- {relationalai → v0/relationalai}/environments/terminal.py +0 -0
- {relationalai → v0/relationalai}/experimental/__init__.py +0 -0
- {relationalai → v0/relationalai}/experimental/graphs.py +0 -0
- {relationalai → v0/relationalai}/experimental/paths/__init__.py +0 -0
- {relationalai → v0/relationalai}/experimental/paths/benchmarks/__init__.py +0 -0
- {relationalai → v0/relationalai}/experimental/paths/path_algorithms/__init__.py +0 -0
- {relationalai → v0/relationalai}/experimental/paths/rpq/__init__.py +0 -0
- {relationalai → v0/relationalai}/experimental/paths/rpq/filter.py +0 -0
- {relationalai → v0/relationalai}/experimental/paths/rpq/glushkov.py +0 -0
- {relationalai → v0/relationalai}/experimental/paths/rpq/transition.py +0 -0
- {relationalai → v0/relationalai}/experimental/paths/utilities/__init__.py +0 -0
- {relationalai → v0/relationalai}/experimental/paths/utilities/utilities.py +0 -0
- {relationalai → v0/relationalai}/metagen.py +0 -0
- {relationalai → v0/relationalai}/metamodel.py +0 -0
- {relationalai → v0/relationalai}/rel.py +0 -0
- {relationalai → v0/relationalai}/semantics/devtools/__init__.py +0 -0
- {relationalai → v0/relationalai}/semantics/internal/__init__.py +0 -0
- {relationalai → v0/relationalai}/semantics/internal/annotations.py +0 -0
- {relationalai → v0/relationalai}/semantics/lqp/__init__.py +0 -0
- {relationalai → v0/relationalai}/semantics/lqp/ir.py +0 -0
- {relationalai → v0/relationalai}/semantics/lqp/pragmas.py +0 -0
- {relationalai → v0/relationalai}/semantics/lqp/rewrite/__init__.py +0 -0
- {relationalai → v0/relationalai}/semantics/metamodel/dataflow.py +0 -0
- {relationalai → v0/relationalai}/semantics/metamodel/ir.py +0 -0
- {relationalai → v0/relationalai}/semantics/metamodel/rewrite/__init__.py +0 -0
- {relationalai → v0/relationalai}/semantics/metamodel/typer/__init__.py +0 -0
- {relationalai → v0/relationalai}/semantics/metamodel/types.py +0 -0
- {relationalai → v0/relationalai}/semantics/metamodel/util.py +0 -0
- {relationalai → v0/relationalai}/semantics/metamodel/visitor.py +0 -0
- {relationalai → v0/relationalai}/semantics/reasoners/experimental/__init__.py +0 -0
- {relationalai → v0/relationalai}/semantics/rel/__init__.py +0 -0
- {relationalai → v0/relationalai}/semantics/sql/__init__.py +0 -0
- {relationalai → v0/relationalai}/semantics/sql/executor/__init__.py +0 -0
- {relationalai → v0/relationalai}/semantics/sql/rewrite/__init__.py +0 -0
- {relationalai → v0/relationalai}/semantics/tests/logging.py +0 -0
- {relationalai → v0/relationalai}/std/aggregates.py +0 -0
- {relationalai → v0/relationalai}/std/dates.py +0 -0
- {relationalai → v0/relationalai}/std/graphs.py +0 -0
- {relationalai → v0/relationalai}/std/inspect.py +0 -0
- {relationalai → v0/relationalai}/std/math.py +0 -0
- {relationalai → v0/relationalai}/std/re.py +0 -0
- {relationalai → v0/relationalai}/std/strings.py +0 -0
- {relationalai → v0/relationalai}/tools/cleanup_snapshots.py +0 -0
- {relationalai → v0/relationalai}/tools/constants.py +0 -0
- {relationalai → v0/relationalai}/tools/query_utils.py +0 -0
- {relationalai → v0/relationalai}/tools/snapshot_viewer.py +0 -0
- {relationalai → v0/relationalai}/util/__init__.py +0 -0
- {relationalai → v0/relationalai}/util/constants.py +0 -0
- {relationalai → v0/relationalai}/util/graph.py +0 -0
- {relationalai → v0/relationalai}/util/timeout.py +0 -0
|
@@ -0,0 +1,1378 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from collections import defaultdict
|
|
4
|
+
from dataclasses import dataclass
|
|
5
|
+
from typing import Optional, Union, Iterable, TypeVar, Tuple
|
|
6
|
+
|
|
7
|
+
from ...util import tracing
|
|
8
|
+
from ...util.error import err, exc, Source, Part
|
|
9
|
+
from ...util.naming import sanitize
|
|
10
|
+
|
|
11
|
+
from . import metamodel as mm, builtins as bt
|
|
12
|
+
from .rewriter import Walker, Rewriter, NO_WALK
|
|
13
|
+
from .builtins import builtins as b
|
|
14
|
+
from ...util.structures import OrderedSet
|
|
15
|
+
|
|
16
|
+
#--------------------------------------------------
|
|
17
|
+
# Typer pass
|
|
18
|
+
#--------------------------------------------------
|
|
19
|
+
|
|
20
|
+
T = TypeVar("T", bound=mm.Model | mm.Task)
|
|
21
|
+
|
|
22
|
+
class Typer:
|
|
23
|
+
def __init__(self, enforce=False):
|
|
24
|
+
self.enforce = enforce
|
|
25
|
+
self.model_net: Optional[PropagationNetwork] = None
|
|
26
|
+
# TODO: remove this once we are using spans for the debugger
|
|
27
|
+
self.last_net: Optional[PropagationNetwork] = None
|
|
28
|
+
|
|
29
|
+
def infer_model(self, model: mm.Model) -> mm.Model:
|
|
30
|
+
"""
|
|
31
|
+
Infer types for the given model, returning a new model with updated types and
|
|
32
|
+
storing the results within this typer so that subsequent calls can reuse them.
|
|
33
|
+
"""
|
|
34
|
+
# create a brand new network with data from this model
|
|
35
|
+
self.model_net = PropagationNetwork(model)
|
|
36
|
+
return self._infer(model, self.model_net)
|
|
37
|
+
|
|
38
|
+
def infer_query(self, query: mm.Task) -> mm.Task:
|
|
39
|
+
"""
|
|
40
|
+
Infer types for the given query, returning a new query with updated types. If the
|
|
41
|
+
typer was used to infer a model previously, the information from that model analysis
|
|
42
|
+
will be reused.
|
|
43
|
+
"""
|
|
44
|
+
if self.model_net is not None:
|
|
45
|
+
net = PropagationNetwork(self.model_net.model)
|
|
46
|
+
net.load_types(self.model_net.resolved_types)
|
|
47
|
+
else:
|
|
48
|
+
net = PropagationNetwork(mm.Model(root=query))
|
|
49
|
+
self.last_net = net
|
|
50
|
+
return self._infer(query, net)
|
|
51
|
+
|
|
52
|
+
#--------------------------------------------------
|
|
53
|
+
# Internal implementation
|
|
54
|
+
#--------------------------------------------------
|
|
55
|
+
|
|
56
|
+
def _infer(self, node: T, net: PropagationNetwork) -> T:
|
|
57
|
+
# build the propagation network by analyzing the model
|
|
58
|
+
with tracing.span("typer.analyze"):
|
|
59
|
+
Analyzer(net).analyze(node)
|
|
60
|
+
|
|
61
|
+
# propagate the types through the network
|
|
62
|
+
with tracing.span("typer.propagate") as span:
|
|
63
|
+
net.propagate()
|
|
64
|
+
# span["type_graph"] = net.to_mermaid()
|
|
65
|
+
# span["type_report"] = net.to_fish()
|
|
66
|
+
|
|
67
|
+
# replace the fields in the model with the new types
|
|
68
|
+
with tracing.span("typer.replace"):
|
|
69
|
+
replacer = Replacer(net)
|
|
70
|
+
final = replacer.rewrite(node)
|
|
71
|
+
|
|
72
|
+
# report any errors found during typing
|
|
73
|
+
if net.errors:
|
|
74
|
+
for error in net.errors:
|
|
75
|
+
error.report()
|
|
76
|
+
if self.enforce:
|
|
77
|
+
exc("TyperError", "Type errors detected during type inference.")
|
|
78
|
+
|
|
79
|
+
return final
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
#--------------------------------------------------
|
|
83
|
+
# Propagation Network
|
|
84
|
+
#--------------------------------------------------
|
|
85
|
+
|
|
86
|
+
# Nodes in the network can be:
|
|
87
|
+
#
|
|
88
|
+
# Literal: always have a concrete type, so types flow from them.
|
|
89
|
+
# Var: used to move values (and types) between different tasks.
|
|
90
|
+
# Field:
|
|
91
|
+
# - abstract field in an update to a model relation: we need to resolve the type of the
|
|
92
|
+
# field, so it is the target of an edge from the arg
|
|
93
|
+
# - output field being looked up into an abstract var: the field is the source of an
|
|
94
|
+
# edge (even if the field is abstract; we may figure this out eventually)
|
|
95
|
+
# Task: only tasks that reference relations are represented in the network (lookup, update,
|
|
96
|
+
# and aggregate). If a task is in the network it means we need to figure something out
|
|
97
|
+
# about the relation it references. A relation can fall into one of these categories:
|
|
98
|
+
# - placeholder: the relation is just a placeholder that needs to replaced by concrete
|
|
99
|
+
# relations from the model that have the same name and arity. The task will be the
|
|
100
|
+
# target of edges from its args, because they all need to be resolved before deciding
|
|
101
|
+
# which concrete relation(s) to use.
|
|
102
|
+
# - overload: the relation has multiple overloads, so we need to decide which one to use.
|
|
103
|
+
# Similarly, the task will be the target of edges from its args.
|
|
104
|
+
# - abstract: the relation has abstract types in its fields, so we need to resolve which
|
|
105
|
+
# concrete types to use. This includes relations that have type variables, and we
|
|
106
|
+
# need to ensure that concrete types are used consistently. The task will be the
|
|
107
|
+
# target of edges from the args that are abstract.
|
|
108
|
+
# - concrete: the relation is concrete, but we still need to ensure that types match.
|
|
109
|
+
#
|
|
110
|
+
# - type preserver:
|
|
111
|
+
# The task can be:
|
|
112
|
+
# - target: if there are inputs that need to be resolved before we can analyze the task
|
|
113
|
+
# (e.g. an abstract var)
|
|
114
|
+
# - source: if the task has outputs that are abstract and need to flow to other nodes
|
|
115
|
+
#
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
# The core idea of the typer is to build a propagation network where nodes
|
|
119
|
+
# are vars, fields, or overloaded lookups/updates/aggregates. The intuition
|
|
120
|
+
# is that _all_ types in the IR ultimately flow from relation fields, so if
|
|
121
|
+
# we figure those out we just need to propagate their types to unknown vars, which
|
|
122
|
+
# may then flow into other fields and so on.
|
|
123
|
+
|
|
124
|
+
# This means the network only needs to contain nodes that either directly flow into
|
|
125
|
+
# an abstract node or are themselves abstract. We need to track overloads because
|
|
126
|
+
# their arguments effectively act like abstract vars until we've resolved the final types.
|
|
127
|
+
|
|
128
|
+
Node = Union[mm.Var, mm.Field, mm.Literal, mm.Lookup, mm.Update, mm.Aggregate]
|
|
129
|
+
|
|
130
|
+
class PropagationNetwork():
|
|
131
|
+
def __init__(self, model: mm.Model):
|
|
132
|
+
# the model that is either being analyzed or that is the context for a query
|
|
133
|
+
self.model = model
|
|
134
|
+
|
|
135
|
+
# the resolved types of nodes in the network
|
|
136
|
+
self.resolved_types: dict[Node, mm.Type] = {}
|
|
137
|
+
|
|
138
|
+
# map from unresolved placeholder relations to their potential target replacements
|
|
139
|
+
self.potential_targets: dict[mm.Relation, list[mm.Relation]] = {}
|
|
140
|
+
|
|
141
|
+
# track the set of nodes that represent entry points into the network
|
|
142
|
+
self.roots = OrderedSet()
|
|
143
|
+
# we separately want to track nodes that were loaded from a previous run
|
|
144
|
+
# so that even if we have edges to them, we _still_ consider them roots
|
|
145
|
+
# and properly propagate types from them at the beginning
|
|
146
|
+
self.loaded_roots = set()
|
|
147
|
+
|
|
148
|
+
# edges in the propagation network, from one node to potentially many
|
|
149
|
+
self.edges:dict[Node, OrderedSet[Node]] = defaultdict(lambda: OrderedSet())
|
|
150
|
+
self.back_edges:dict[Node, OrderedSet[Node]] = defaultdict(lambda: OrderedSet())
|
|
151
|
+
# all nodes that are the target of an edge (to find roots)
|
|
152
|
+
self.has_incoming = set()
|
|
153
|
+
|
|
154
|
+
# type requirements: for a var with abstract declared type, the set of fields that
|
|
155
|
+
# it must match the type of because it flows into them
|
|
156
|
+
self.type_requirements:dict[mm.Var, OrderedSet[mm.Field]] = defaultdict(lambda: OrderedSet())
|
|
157
|
+
|
|
158
|
+
# all errors collected during inference
|
|
159
|
+
self.errors:list[TyperError] = []
|
|
160
|
+
|
|
161
|
+
# overloads resolved for a lookup/update/aggregate, by node id. This is only for
|
|
162
|
+
# relations that declare overloads
|
|
163
|
+
self.resolved_overload:dict[int, mm.Overload] = {}
|
|
164
|
+
# placeholders resolved for a lookup, by node id. This is only for relations that
|
|
165
|
+
# are placeholders (i.e. only Any fields) and will be replaced by references to
|
|
166
|
+
# these concrete relations. E.g. a query for "name(Any, Any)" may be replaced by
|
|
167
|
+
# the union of "name(Dog, String)" and name(Cat, String)".
|
|
168
|
+
self.resolved_placeholder:dict[int, list[mm.Relation]] = {}
|
|
169
|
+
# for a given lookup/update/aggregate that involves numbers, the specific number
|
|
170
|
+
# type resolved for it.
|
|
171
|
+
self.resolved_number:dict[int, mm.NumberType] = {}
|
|
172
|
+
# keep track of references resolved to avoid re-resolving
|
|
173
|
+
self.resolved_references:set[int] = set()
|
|
174
|
+
|
|
175
|
+
#--------------------------------------------------
|
|
176
|
+
# Error reporting
|
|
177
|
+
#--------------------------------------------------
|
|
178
|
+
|
|
179
|
+
def type_mismatch(self, node: Node, expected: mm.Type, actual: mm.Type):
|
|
180
|
+
self.errors.append(TypeMismatch(node, expected, actual))
|
|
181
|
+
|
|
182
|
+
def invalid_type(self, node: Node, type: mm.Type):
|
|
183
|
+
self.errors.append(InvalidType(node, type))
|
|
184
|
+
|
|
185
|
+
def unresolved_overload(self, node: mm.Lookup|mm.Update|mm.Aggregate):
|
|
186
|
+
# TODO - consider renaming this to UnresolvedReference
|
|
187
|
+
self.errors.append(UnresolvedOverload(node, [self.resolve(a) for a in node.args]))
|
|
188
|
+
|
|
189
|
+
def unresolved_type(self, node: Node):
|
|
190
|
+
self.errors.append(UnresolvedType(node))
|
|
191
|
+
|
|
192
|
+
def has_errors(self, node: Node) -> bool:
|
|
193
|
+
for mismatch in self.errors:
|
|
194
|
+
if mismatch.node == node:
|
|
195
|
+
return True
|
|
196
|
+
return False
|
|
197
|
+
|
|
198
|
+
#--------------------------------------------------
|
|
199
|
+
# Types and Edges
|
|
200
|
+
#--------------------------------------------------
|
|
201
|
+
|
|
202
|
+
def add_edge(self, source: Node, target: Node):
|
|
203
|
+
# manage roots
|
|
204
|
+
if target in self.roots and target not in self.loaded_roots:
|
|
205
|
+
self.roots.remove(target)
|
|
206
|
+
if source not in self.has_incoming:
|
|
207
|
+
self.roots.add(source)
|
|
208
|
+
# register edge
|
|
209
|
+
self.edges[source].add(target)
|
|
210
|
+
self.back_edges[target].add(source)
|
|
211
|
+
self.has_incoming.add(target)
|
|
212
|
+
|
|
213
|
+
def add_resolved_type(self, node: Node, type: mm.Type):
|
|
214
|
+
""" Register that this node was resolved to have this type. """
|
|
215
|
+
if node in self.resolved_types:
|
|
216
|
+
self.resolved_types[node] = merge_types(self.resolved_types[node], type)
|
|
217
|
+
else:
|
|
218
|
+
self.resolved_types[node] = type
|
|
219
|
+
|
|
220
|
+
def add_type_requirement(self, source: mm.Var, field: mm.Field):
|
|
221
|
+
""" Register that this var, which has an abstract declared type, must match the type
|
|
222
|
+
of this field as it flows into it.. """
|
|
223
|
+
self.type_requirements[source].add(field)
|
|
224
|
+
|
|
225
|
+
#--------------------------------------------------
|
|
226
|
+
# Load previous types
|
|
227
|
+
#--------------------------------------------------
|
|
228
|
+
|
|
229
|
+
def load_types(self, type_dict: dict[Node, mm.Type]):
|
|
230
|
+
for node, type in type_dict.items():
|
|
231
|
+
if isinstance(node, (mm.Field)):
|
|
232
|
+
self.add_resolved_type(node, type)
|
|
233
|
+
self.loaded_roots.add(node)
|
|
234
|
+
self.roots.add(node)
|
|
235
|
+
|
|
236
|
+
#--------------------------------------------------
|
|
237
|
+
# Resolve
|
|
238
|
+
#--------------------------------------------------
|
|
239
|
+
|
|
240
|
+
def resolve(self, value: Node|mm.Value) -> mm.Type:
|
|
241
|
+
if isinstance(value, (mm.Var, mm.Field, mm.Literal)):
|
|
242
|
+
return self.resolved_types.get(value) or to_type(value)
|
|
243
|
+
assert not isinstance(value, (mm.Lookup, mm.Update, mm.Aggregate)), "Should never try to resolve a task"
|
|
244
|
+
return to_type(value)
|
|
245
|
+
|
|
246
|
+
#--------------------------------------------------
|
|
247
|
+
# Overloads
|
|
248
|
+
#--------------------------------------------------
|
|
249
|
+
|
|
250
|
+
def get_fields(self, relation: mm.Relation, name: str) -> Iterable[mm.Field]:
|
|
251
|
+
""" Get the fields of this relation, potentially reordered to match the reading with the given name."""
|
|
252
|
+
if name == relation.name:
|
|
253
|
+
return relation.fields
|
|
254
|
+
for reading in relation.readings:
|
|
255
|
+
if reading.name == name:
|
|
256
|
+
# reorder the fields to match the correct ordering
|
|
257
|
+
fields = []
|
|
258
|
+
for idx in reading.field_order:
|
|
259
|
+
fields.append(relation.fields[idx])
|
|
260
|
+
return fields
|
|
261
|
+
return []
|
|
262
|
+
|
|
263
|
+
def get_args(self, node: mm.Lookup, target: mm.Relation):
|
|
264
|
+
""" Get the args of this lookup, potentially reordered to match the reading with the given name."""
|
|
265
|
+
for reading in target.readings:
|
|
266
|
+
if reading.name == node.relation.name:
|
|
267
|
+
# reorder the args to match the correct ordering
|
|
268
|
+
args = []
|
|
269
|
+
for idx in reading.field_order:
|
|
270
|
+
args.append(node.args[idx])
|
|
271
|
+
return args
|
|
272
|
+
return node.args
|
|
273
|
+
|
|
274
|
+
|
|
275
|
+
def resolve_reference(self, op: mm.Lookup|mm.Update|mm.Aggregate) -> Optional[mm.Overload|list[mm.Relation]]:
|
|
276
|
+
# check if we have any unresolved args that are required, if all of our args
|
|
277
|
+
# are unresolved (len(incoming) == len(relation.fields)) then we have no information
|
|
278
|
+
# to try and resolve the overloads with
|
|
279
|
+
# incoming, required = self.resolve_reference_deps(op)
|
|
280
|
+
#
|
|
281
|
+
if not self.dependencies_resolved(op):
|
|
282
|
+
return None
|
|
283
|
+
|
|
284
|
+
relation = get_relation(op)
|
|
285
|
+
if bt.is_placeholder(relation):
|
|
286
|
+
# when replacing a placeholder, we may have multiple matches
|
|
287
|
+
matches = []
|
|
288
|
+
resolved_args = [self.resolve(arg) for arg in op.args]
|
|
289
|
+
for target in self.potential_targets.get(relation, []):
|
|
290
|
+
fields = self.get_fields(target, relation.name)
|
|
291
|
+
if all(type_matches(arg, self.resolve(field))
|
|
292
|
+
for arg, field in zip(resolved_args, fields)):
|
|
293
|
+
matches.append(target)
|
|
294
|
+
|
|
295
|
+
return matches
|
|
296
|
+
|
|
297
|
+
elif relation.overloads:
|
|
298
|
+
# when resolving an overload for a concrete relation, we can only have one
|
|
299
|
+
resolved_args = [self.resolve(arg) for arg in op.args]
|
|
300
|
+
is_function = bt.is_function(relation)
|
|
301
|
+
for overload in relation.overloads:
|
|
302
|
+
# note that for functions we only consider input fields when matching
|
|
303
|
+
if all(type_matches(arg, field_type) or conversion_allowed(arg, field_type)
|
|
304
|
+
for arg, field, field_type in zip(resolved_args, relation.fields, overload.types)
|
|
305
|
+
if field.input or not is_function):
|
|
306
|
+
self.resolved_overload[op.id] = overload
|
|
307
|
+
return overload
|
|
308
|
+
return [] # no matches found
|
|
309
|
+
else:
|
|
310
|
+
# this is a relation with type vars or numbers that need to be specialized
|
|
311
|
+
return [relation]
|
|
312
|
+
|
|
313
|
+
|
|
314
|
+
def dependencies_resolved(self, op:mm.Lookup|mm.Update|mm.Aggregate):
|
|
315
|
+
# if this is a placeholder, we need assume all possible args were resolved
|
|
316
|
+
if bt.is_placeholder(get_relation(op)):
|
|
317
|
+
return True
|
|
318
|
+
# else, find whether all back-edges were resolved
|
|
319
|
+
for node in self.back_edges[op]:
|
|
320
|
+
if isinstance(node, (mm.Var, mm.Field, mm.Literal)):
|
|
321
|
+
node_type = self.resolve(node)
|
|
322
|
+
if bt.is_abstract(node_type):
|
|
323
|
+
return False
|
|
324
|
+
return True
|
|
325
|
+
|
|
326
|
+
|
|
327
|
+
|
|
328
|
+
|
|
329
|
+
#--------------------------------------------------
|
|
330
|
+
# Propagation
|
|
331
|
+
#--------------------------------------------------
|
|
332
|
+
|
|
333
|
+
def propagate(self):
|
|
334
|
+
edges = self.edges
|
|
335
|
+
work_list = []
|
|
336
|
+
|
|
337
|
+
# go through all the roots and find any that are not abstract, they'll
|
|
338
|
+
# be the first nodes to push types through the network
|
|
339
|
+
unhandled = OrderedSet()
|
|
340
|
+
for node in self.roots:
|
|
341
|
+
if not isinstance(node, (mm.Var, mm.Field, mm.Literal)):
|
|
342
|
+
continue
|
|
343
|
+
node_type = self.resolve(node)
|
|
344
|
+
if not bt.is_abstract(node_type):
|
|
345
|
+
work_list.append(node)
|
|
346
|
+
else:
|
|
347
|
+
unhandled.add(node)
|
|
348
|
+
|
|
349
|
+
# push known type nodes through the edges
|
|
350
|
+
while work_list:
|
|
351
|
+
source = work_list.pop(0)
|
|
352
|
+
self.resolved_references.add(source.id)
|
|
353
|
+
if source in unhandled:
|
|
354
|
+
unhandled.remove(source)
|
|
355
|
+
source_type = self.resolve(source)
|
|
356
|
+
# check to see if the source has ended up with a set of types that
|
|
357
|
+
# aren't valid, e.g. a union of primitives
|
|
358
|
+
if invalid_type(source_type):
|
|
359
|
+
self.invalid_type(source, source_type)
|
|
360
|
+
|
|
361
|
+
# propagate our type to each outgoing edge
|
|
362
|
+
for out in edges.get(source, []):
|
|
363
|
+
# if this is an overload then we need to try and resolve it
|
|
364
|
+
if isinstance(out, (mm.Update, mm.Lookup, mm.Aggregate)):
|
|
365
|
+
if not out.id in self.resolved_references:
|
|
366
|
+
found = self.resolve_reference(out)
|
|
367
|
+
if found is not None:
|
|
368
|
+
self.resolved_references.add(out.id)
|
|
369
|
+
start_arg_types = [self.resolve(arg) for arg in out.args]
|
|
370
|
+
self.propagate_reference(out, start_arg_types, found)
|
|
371
|
+
# final_arg_types = [self.resolve(arg) for arg in out.args]
|
|
372
|
+
for arg, start in zip(out.args, start_arg_types):
|
|
373
|
+
# if start != final and
|
|
374
|
+
# this won't cause loops because we resolve refs only once;
|
|
375
|
+
# we can be safer and check?
|
|
376
|
+
if arg not in work_list:
|
|
377
|
+
work_list.append(arg)
|
|
378
|
+
# otherwise, we just add to the outgoing node's type and if it
|
|
379
|
+
# changes we add it to the work list
|
|
380
|
+
elif start := self.resolve(out):
|
|
381
|
+
self.add_resolved_type(out, source_type)
|
|
382
|
+
# if out not in work_list and (start != self.resolve(out) or out in unhandled):
|
|
383
|
+
# if out not in work_list:
|
|
384
|
+
if out not in work_list and (start != self.resolve(out) or not out.id in self.resolved_references):
|
|
385
|
+
work_list.append(out)
|
|
386
|
+
|
|
387
|
+
for source in unhandled:
|
|
388
|
+
self.unresolved_type(source)
|
|
389
|
+
|
|
390
|
+
# now that we've pushed all the types through the network, we need to validate
|
|
391
|
+
# that all type requirements of those nodes are met
|
|
392
|
+
for node, fields in self.type_requirements.items():
|
|
393
|
+
node_type = self.resolve(node)
|
|
394
|
+
for field in fields:
|
|
395
|
+
field_type = self.resolve(field)
|
|
396
|
+
if not type_matches(node_type, field_type) and not conversion_allowed(node_type, field_type):
|
|
397
|
+
# node_base = bt.get_primitive_supertype(node_type)
|
|
398
|
+
# field_base = bt.get_primitive_supertype(field_type)
|
|
399
|
+
# if node_base and field_base and conversion_allowed(node_base, field_base):
|
|
400
|
+
# pass
|
|
401
|
+
# else:
|
|
402
|
+
self.type_mismatch(node, field_type, node_type)
|
|
403
|
+
|
|
404
|
+
|
|
405
|
+
def propagate_reference(self, node:mm.Lookup|mm.Update|mm.Aggregate, resolved_args:list[mm.Type], references:mm.Overload|list[mm.Relation]):
|
|
406
|
+
# TODO: distinguish between overloads and placeholders better
|
|
407
|
+
if not references:
|
|
408
|
+
return self.unresolved_overload(node)
|
|
409
|
+
|
|
410
|
+
# we need to determine the final types of our args by taking all the references
|
|
411
|
+
# and adding the type of their fields back to the args.
|
|
412
|
+
relation = get_relation(node)
|
|
413
|
+
|
|
414
|
+
if bt.is_placeholder(relation):
|
|
415
|
+
assert(references and isinstance(references, list))
|
|
416
|
+
# we've resolved the placeholder, so store that
|
|
417
|
+
self.resolved_placeholder[node.id] = references
|
|
418
|
+
|
|
419
|
+
# we need to determine the final types of our args by taking all the references
|
|
420
|
+
# and adding the type of their fields back to the args.
|
|
421
|
+
for reference in references:
|
|
422
|
+
resolved_fields = [self.resolve(f) for f in self.get_fields(reference, relation.name)]
|
|
423
|
+
for field_type, arg_type, arg in zip(resolved_fields, resolved_args, node.args):
|
|
424
|
+
if bt.is_abstract(arg_type) and isinstance(arg, mm.Var):
|
|
425
|
+
self.add_resolved_type(arg, field_type)
|
|
426
|
+
else:
|
|
427
|
+
if isinstance(references, mm.Overload):
|
|
428
|
+
# we resolved an overload, use its types
|
|
429
|
+
types = list(references.types)
|
|
430
|
+
else:
|
|
431
|
+
# we resolved to a single concrete relation that contains type vars or needs
|
|
432
|
+
# number specialization, so use that relation's field types
|
|
433
|
+
types = list([self.resolve(f) for f in relation.fields])
|
|
434
|
+
|
|
435
|
+
# if our overload preserves types, we check to see if there's a preserved
|
|
436
|
+
# output type given the inputs and if so, shadow the field's type with the
|
|
437
|
+
# preserved type
|
|
438
|
+
resolved_fields = types
|
|
439
|
+
if bt.is_function(relation) and len(set(resolved_fields)) == 1:
|
|
440
|
+
input_types = set([arg_type for field, arg_type
|
|
441
|
+
in zip(relation.fields, resolved_args) if field.input])
|
|
442
|
+
if out_type := self.try_preserve_type(input_types):
|
|
443
|
+
resolved_fields = [field_type if field.input else out_type
|
|
444
|
+
for field, field_type in zip(relation.fields, types)]
|
|
445
|
+
|
|
446
|
+
# TODO - we also need to make sure the type vars are constently resolved here
|
|
447
|
+
# i.e. if types contain typevars, check that the args that are bound to those
|
|
448
|
+
# typevars are consistent
|
|
449
|
+
if b.core.Number in types or (b.core.TypeVar in types and any(bt.is_number(t) for t in resolved_args)):
|
|
450
|
+
# this overload contains generic numbers or typevars bound to numbers, so
|
|
451
|
+
# find which specific type of number to use given the arguments being passed
|
|
452
|
+
number, resolved_fields = self.specialize_number(relation, resolved_fields, resolved_args)
|
|
453
|
+
self.resolved_number[node.id] = number
|
|
454
|
+
# push the specialized number to the outputs
|
|
455
|
+
for field, field_type, arg in zip(relation.fields, resolved_fields, node.args):
|
|
456
|
+
if not field.input and isinstance(arg, mm.Var):
|
|
457
|
+
self.add_resolved_type(arg, field_type)
|
|
458
|
+
else:
|
|
459
|
+
for field_type, arg, arg_type in zip(resolved_fields, node.args, resolved_args):
|
|
460
|
+
if bt.is_abstract(arg_type) and isinstance(arg, mm.Var):
|
|
461
|
+
self.add_resolved_type(arg, field_type)
|
|
462
|
+
|
|
463
|
+
|
|
464
|
+
def try_preserve_type(self, types:set[mm.Type]) -> Optional[mm.Type]:
|
|
465
|
+
# we keep the input type as the output type if either all inputs
|
|
466
|
+
# are the exact same type or there's one nominal and its base primitive
|
|
467
|
+
# type, e.g. USD + Decimal
|
|
468
|
+
if len(types) == 1:
|
|
469
|
+
return next(iter(types))
|
|
470
|
+
if len(types) == 2:
|
|
471
|
+
t1, t2 = types
|
|
472
|
+
t1_base = bt.get_primitive_supertype(t1)
|
|
473
|
+
t2_base = bt.get_primitive_supertype(t2)
|
|
474
|
+
if t1_base is None or t2_base is None:
|
|
475
|
+
base_equivalent = type_matches(t1, t2, is_type_relation=True)
|
|
476
|
+
else:
|
|
477
|
+
base_equivalent = type_matches(t1_base, t2_base)
|
|
478
|
+
if base_equivalent:
|
|
479
|
+
# as long as one of the types is a base primitive, we can use the
|
|
480
|
+
# other type as final preserved type
|
|
481
|
+
if bt.is_primitive(t1):
|
|
482
|
+
return t2
|
|
483
|
+
elif bt.is_primitive(t2):
|
|
484
|
+
return t1
|
|
485
|
+
return None
|
|
486
|
+
|
|
487
|
+
def get_number(self, t: mm.Type) -> mm.NumberType:
|
|
488
|
+
# Get a number type from the given type, if it is Number return the default number
|
|
489
|
+
x = bt.get_number_supertype(t)
|
|
490
|
+
if isinstance(x, mm.NumberType):
|
|
491
|
+
return x
|
|
492
|
+
return b.core.DefaultNumber
|
|
493
|
+
|
|
494
|
+
def specialize_number(self, op, field_types:list[mm.Type], arg_types:list[mm.Type]) -> Tuple[mm.NumberType, list[mm.Type]]:
|
|
495
|
+
"""
|
|
496
|
+
Find the number type to use for an overload that has Number in its field_types,
|
|
497
|
+
and which is being referred to with these arg_types.
|
|
498
|
+
|
|
499
|
+
Return a tuple where the first element is the specialized number type, and the second
|
|
500
|
+
element is a new list that contains the same types as field_types but with
|
|
501
|
+
Number replaced by this specialized number.
|
|
502
|
+
"""
|
|
503
|
+
if op == b.core.div:
|
|
504
|
+
# see https://docs.snowflake.com/en/sql-reference/operators-arithmetic#division
|
|
505
|
+
numerator, denominator = self.get_number(arg_types[0]), self.get_number(arg_types[1])
|
|
506
|
+
s = max(numerator.scale, min(numerator.scale + 6, 12))
|
|
507
|
+
number = mm.NumberType(name=f"Number(38,{s})", precision=38, scale=s, source=op.source, super_types=(b.core.Numeric,))
|
|
508
|
+
return number, [numerator, denominator, number]
|
|
509
|
+
elif op == b.core.mul:
|
|
510
|
+
# see https://docs.snowflake.com/en/sql-reference/operators-arithmetic#multiplication
|
|
511
|
+
t1, t2 = self.get_number(arg_types[0]), self.get_number(arg_types[1])
|
|
512
|
+
S1 = t1.scale
|
|
513
|
+
S2 = t2.scale
|
|
514
|
+
s = min(S1 + S2, max(S1, S2, 12))
|
|
515
|
+
number = mm.NumberType(name=f"Number(38,{s})", precision=38, scale=s, source=op.source, super_types=(b.core.Numeric,))
|
|
516
|
+
return number, [t1, t2, number]
|
|
517
|
+
elif op == b.aggregates.avg:
|
|
518
|
+
# TODO!! - implement proper avg specialization
|
|
519
|
+
pass
|
|
520
|
+
|
|
521
|
+
|
|
522
|
+
number = None
|
|
523
|
+
for arg_type in arg_types:
|
|
524
|
+
# x = types.decimal_supertype(arg_type)
|
|
525
|
+
x = bt.get_number_supertype(arg_type)
|
|
526
|
+
if isinstance(x, mm.NumberType):
|
|
527
|
+
# the current specialization policy is to select the number with largest
|
|
528
|
+
# scale and, if there multiple with the largest scale, the one with the
|
|
529
|
+
# largest precision. This is safe because when converting a number to the
|
|
530
|
+
# specialized number, we never truncate fractional digits (because we
|
|
531
|
+
# selected the largest scale) and, if the non-fractional digits are too
|
|
532
|
+
# large to fit the specialized number, we will have a runtime overflow,
|
|
533
|
+
# which should alert the user of the problem.
|
|
534
|
+
#
|
|
535
|
+
# In the future we can implement more complex policies. For example,
|
|
536
|
+
# snowflake has well documented behavior for how the output of operations
|
|
537
|
+
# behave in face of different number types, and we may use that:
|
|
538
|
+
# https://docs.snowflake.com/en/sql-reference/operators-arithmetic#scale-and-precision-in-arithmetic-operations
|
|
539
|
+
if number is None or x.scale > number.scale or (x.scale == number.scale and x.precision > number.precision):
|
|
540
|
+
number = x
|
|
541
|
+
assert(isinstance(number, mm.NumberType))
|
|
542
|
+
return number, [number if bt.is_number(field_type) else field_type
|
|
543
|
+
for field_type in field_types]
|
|
544
|
+
|
|
545
|
+
|
|
546
|
+
#--------------------------------------------------
|
|
547
|
+
# Display
|
|
548
|
+
#--------------------------------------------------
|
|
549
|
+
|
|
550
|
+
# draw the network as a mermaid graph for the debugger
|
|
551
|
+
def to_mermaid(self, max_edges=500) -> str:
|
|
552
|
+
|
|
553
|
+
# add links for edges while collecting nodes
|
|
554
|
+
nodes = OrderedSet()
|
|
555
|
+
link_strs = []
|
|
556
|
+
# edges
|
|
557
|
+
for src, dsts in self.edges.items():
|
|
558
|
+
nodes.add(src)
|
|
559
|
+
for dst in dsts:
|
|
560
|
+
if len(link_strs) > max_edges:
|
|
561
|
+
break
|
|
562
|
+
nodes.add(dst)
|
|
563
|
+
link_strs.append(f"n{src.id} --> n{dst.id}")
|
|
564
|
+
if len(link_strs) > max_edges:
|
|
565
|
+
break
|
|
566
|
+
# type requirements
|
|
567
|
+
for src, dsts in self.type_requirements.items():
|
|
568
|
+
nodes.add(src)
|
|
569
|
+
for dst in dsts:
|
|
570
|
+
if len(link_strs) > max_edges:
|
|
571
|
+
break
|
|
572
|
+
nodes.add(dst)
|
|
573
|
+
link_strs.append(f"n{src.id} -.-> n{dst.id}")
|
|
574
|
+
if len(link_strs) > max_edges:
|
|
575
|
+
break
|
|
576
|
+
|
|
577
|
+
def type_span(t:mm.Type) -> str:
|
|
578
|
+
type_str = t.name if isinstance(t, mm.ScalarType) else str(t)
|
|
579
|
+
return f"<span style='color:cyan;'>{type_str.strip()}</span>"
|
|
580
|
+
|
|
581
|
+
def reference_span(rel:mm.Relation, arg_types:list[mm.Type], root:str) -> str:
|
|
582
|
+
args = []
|
|
583
|
+
for field, arg_type in zip(rel.fields, arg_types):
|
|
584
|
+
field_type = self.resolve(field)
|
|
585
|
+
if not type_matches(arg_type, field_type) and not conversion_allowed(arg_type, field_type) and not bt.is_abstract(field_type):
|
|
586
|
+
args.append(f"<span style='color:yellow;'>{str(arg_type).strip()} -> {str(field_type).strip()}</span>")
|
|
587
|
+
elif isinstance(arg_type, mm.UnionType):
|
|
588
|
+
args.append(type_span(field_type))
|
|
589
|
+
else:
|
|
590
|
+
args.append(type_span(arg_type))
|
|
591
|
+
return f'{rel.name}{root}({", ".join(args)})'
|
|
592
|
+
|
|
593
|
+
resolved = self.resolved_types
|
|
594
|
+
node_strs = []
|
|
595
|
+
for node in nodes:
|
|
596
|
+
klass = ""
|
|
597
|
+
root = "(*)" if node in self.roots else ""
|
|
598
|
+
if isinstance(node, mm.Var):
|
|
599
|
+
ir_type = resolved.get(node) or self.resolve(node)
|
|
600
|
+
type_str = type_span(ir_type)
|
|
601
|
+
label = f'(["{node.name}{root}:{type_str}"])'
|
|
602
|
+
elif isinstance(node, mm.Literal):
|
|
603
|
+
ir_type = resolved.get(node) or self.resolve(node)
|
|
604
|
+
type_str = type_span(ir_type)
|
|
605
|
+
klass = ":::literal"
|
|
606
|
+
label = f'[/"{node.value}{root}: {type_str}"\\]'
|
|
607
|
+
elif isinstance(node, mm.Field):
|
|
608
|
+
ir_type = resolved.get(node) or self.resolve(node)
|
|
609
|
+
type_str = type_span(ir_type)
|
|
610
|
+
klass = ":::field"
|
|
611
|
+
rel = node._relation
|
|
612
|
+
if rel is not None:
|
|
613
|
+
rel = str(node._relation)
|
|
614
|
+
label = f'{{{{"{node.name}{root}:{type_str}\nfrom {rel}"}}}}'
|
|
615
|
+
else:
|
|
616
|
+
label = f'{{{{"{node.name}{root}:\n{type_str}"}}}}'
|
|
617
|
+
elif isinstance(node, (mm.Lookup, mm.Update, mm.Aggregate)):
|
|
618
|
+
arg_types = [self.resolve(arg) for arg in node.args]
|
|
619
|
+
if node.id in self.resolved_placeholder:
|
|
620
|
+
overloads = self.resolved_placeholder[node.id]
|
|
621
|
+
content = "<br/>".join([reference_span(o, arg_types, root) for o in overloads])
|
|
622
|
+
else:
|
|
623
|
+
content = reference_span(get_relation(node), arg_types, root)
|
|
624
|
+
label = f'[/"{content}"/]'
|
|
625
|
+
# elif isinstance(node, mm.Relation):
|
|
626
|
+
# label = f'[("{node}")]'
|
|
627
|
+
else:
|
|
628
|
+
raise NotImplementedError(f"Unknown node type: {type(node)}")
|
|
629
|
+
if self.has_errors(node):
|
|
630
|
+
klass = ":::error"
|
|
631
|
+
node_strs.append(f'n{node.id}{label}{klass}')
|
|
632
|
+
|
|
633
|
+
node_str = "\n ".join(node_strs)
|
|
634
|
+
link_str = "\n ".join(link_strs)
|
|
635
|
+
template = f"""
|
|
636
|
+
%%{{init: {{'theme':'dark', 'flowchart':{{'useMaxWidth':false, 'htmlLabels': true}}}}}}%%
|
|
637
|
+
flowchart TD
|
|
638
|
+
linkStyle default stroke:#666
|
|
639
|
+
classDef field fill:#245,stroke:#478
|
|
640
|
+
classDef literal fill:#452,stroke:#784
|
|
641
|
+
classDef error fill:#624,stroke:#945,color:#f9a
|
|
642
|
+
classDef default stroke:#444,stroke-width:2px, font-size:12px
|
|
643
|
+
|
|
644
|
+
%% nodes
|
|
645
|
+
{node_str}
|
|
646
|
+
|
|
647
|
+
%% edges
|
|
648
|
+
{link_str}
|
|
649
|
+
"""
|
|
650
|
+
return template
|
|
651
|
+
|
|
652
|
+
# simplified, less verbose (compared to mermaid) output for snapshot testing
|
|
653
|
+
def to_fish(self) -> str:
|
|
654
|
+
resolved = self.resolved_types
|
|
655
|
+
nodes = OrderedSet()
|
|
656
|
+
for src, dsts in self.edges.items():
|
|
657
|
+
nodes.add(src)
|
|
658
|
+
nodes.update(dsts)
|
|
659
|
+
for src, dsts in self.type_requirements.items():
|
|
660
|
+
nodes.add(src)
|
|
661
|
+
nodes.update(dsts)
|
|
662
|
+
|
|
663
|
+
def type_to_str(t:mm.Type) -> str:
|
|
664
|
+
type_str = t.name if isinstance(t, mm.ScalarType) else str(t)
|
|
665
|
+
return type_str.strip()
|
|
666
|
+
|
|
667
|
+
def overload_str(rel:mm.Relation, arg_types:list[mm.Type]) -> str:
|
|
668
|
+
args = []
|
|
669
|
+
for field, arg_type in zip(rel.fields, arg_types):
|
|
670
|
+
field_type = self.resolve(field)
|
|
671
|
+
if not type_matches(arg_type, field_type):
|
|
672
|
+
args.append(f"{str(arg_type).strip()} -?> {str(field_type).strip()}")
|
|
673
|
+
elif isinstance(arg_type, mm.UnionType):
|
|
674
|
+
args.append(type_to_str(field_type))
|
|
675
|
+
else:
|
|
676
|
+
args.append(type_to_str(arg_type))
|
|
677
|
+
return f'{rel.name}({", ".join(args)})'
|
|
678
|
+
|
|
679
|
+
def node_kind(node:mm.Node) -> str:
|
|
680
|
+
if isinstance(node, (mm.Lookup, mm.Update, mm.Aggregate)):
|
|
681
|
+
return "overload"
|
|
682
|
+
return type(node).__name__.lower()
|
|
683
|
+
|
|
684
|
+
def error_info(err:TyperError) -> str:
|
|
685
|
+
if isinstance(err, TypeMismatch):
|
|
686
|
+
return f'Type Mismatch|expected {get_name(err.expected)}, got {get_name(err.actual)}'
|
|
687
|
+
return f'{type(err).__name__}'
|
|
688
|
+
|
|
689
|
+
nl = "\n"
|
|
690
|
+
node_strs = []
|
|
691
|
+
for node in nodes:
|
|
692
|
+
info = ""
|
|
693
|
+
if isinstance(node, mm.Var):
|
|
694
|
+
ir_type = resolved.get(node) or self.resolve(node)
|
|
695
|
+
if not(isinstance(ir_type, mm.ScalarType) and node.name == ir_type.name):
|
|
696
|
+
info = f'{node.name}|{type_to_str(ir_type)}'
|
|
697
|
+
elif isinstance(node, mm.Field):
|
|
698
|
+
ir_type = resolved.get(node) or self.resolve(node)
|
|
699
|
+
info = f'{node.name}|{type_to_str(ir_type)}'
|
|
700
|
+
elif isinstance(node, (mm.Lookup, mm.Update, mm.Aggregate)):
|
|
701
|
+
arg_types = [self.resolve(arg) for arg in node.args]
|
|
702
|
+
if node.id in self.resolved_placeholder:
|
|
703
|
+
overloads = self.resolved_placeholder[node.id]
|
|
704
|
+
content = nl.join([overload_str(o, arg_types) for o in overloads])
|
|
705
|
+
else:
|
|
706
|
+
content = overload_str(get_relation(node), arg_types)
|
|
707
|
+
info = f'{content}'
|
|
708
|
+
else:
|
|
709
|
+
raise NotImplementedError(f"Unknown node type: {type(node)}")
|
|
710
|
+
|
|
711
|
+
if info:
|
|
712
|
+
error_suffix = " !" if self.has_errors(node) else ""
|
|
713
|
+
node_strs.append(f'{node_kind(node)}|{info}{error_suffix}')
|
|
714
|
+
|
|
715
|
+
node_strs.sort()
|
|
716
|
+
if self.errors:
|
|
717
|
+
node_strs.append("---ERRORS---")
|
|
718
|
+
for err in self.errors:
|
|
719
|
+
node_strs.append(f'{error_info(err)} ({str(err.node).strip()})')
|
|
720
|
+
|
|
721
|
+
return f"""{nl.join(node_strs)}"""
|
|
722
|
+
|
|
723
|
+
|
|
724
|
+
|
|
725
|
+
|
|
726
|
+
#--------------------------------------------------
|
|
727
|
+
# Analyzer
|
|
728
|
+
#--------------------------------------------------
|
|
729
|
+
|
|
730
|
+
class Analyzer(Walker):
|
|
731
|
+
""" Walks the metamodel and builds the propagation network. """
|
|
732
|
+
|
|
733
|
+
def __init__(self, net:PropagationNetwork):
|
|
734
|
+
super().__init__()
|
|
735
|
+
self.net = net
|
|
736
|
+
|
|
737
|
+
def analyze(self, node: mm.Node):
|
|
738
|
+
self(node)
|
|
739
|
+
|
|
740
|
+
# TODO - ignoring requires for now because the typing of constraints seems incorrect
|
|
741
|
+
def enter_require(self, require: mm.Require):
|
|
742
|
+
return NO_WALK
|
|
743
|
+
|
|
744
|
+
def compute_potential_targets(self, relation: mm.Relation):
|
|
745
|
+
# register potential targets for placeholders
|
|
746
|
+
if bt.is_placeholder(relation):
|
|
747
|
+
self.net.potential_targets[relation] = get_potential_targets(self.net.model, relation)
|
|
748
|
+
|
|
749
|
+
#--------------------------------------------------
|
|
750
|
+
# Walk Update
|
|
751
|
+
#--------------------------------------------------
|
|
752
|
+
|
|
753
|
+
def update(self, node: mm.Update):
|
|
754
|
+
rel = node.relation
|
|
755
|
+
self.compute_potential_targets(rel)
|
|
756
|
+
|
|
757
|
+
# if this is a type relation, the update is asserting that the argument is of that
|
|
758
|
+
# type; so, it's fine to pass a super-type in to the population e.g. Employee(Person)
|
|
759
|
+
# should be a valid way to populate that a particular Person is also an Employee.
|
|
760
|
+
is_type_relation = isinstance(rel, mm.TypeNode)
|
|
761
|
+
for arg, field in zip(node.args, rel.fields):
|
|
762
|
+
field_type = field.type
|
|
763
|
+
arg_type = self.net.resolve(arg)
|
|
764
|
+
|
|
765
|
+
# if the arg is abstract, but the field isn't, then we need to make sure that
|
|
766
|
+
# once the arg is resolved we check that it matches the field type
|
|
767
|
+
if isinstance(arg, mm.Var) and bt.is_abstract(arg_type) and bt.is_concrete(field_type):
|
|
768
|
+
self.net.add_type_requirement(arg, field)
|
|
769
|
+
|
|
770
|
+
if bt.is_abstract(field_type) and isinstance(arg, (mm.Var, mm.Literal)):
|
|
771
|
+
# if the field is abstract, then eventually this arg will help determine
|
|
772
|
+
# the field's type, so add an edge from the arg to the field
|
|
773
|
+
self.net.add_edge(arg, field)
|
|
774
|
+
elif not type_matches(arg_type, field_type, is_type_relation=is_type_relation):
|
|
775
|
+
if not conversion_allowed(arg_type, field_type):
|
|
776
|
+
self.net.type_mismatch(node, field_type, arg_type)
|
|
777
|
+
|
|
778
|
+
#--------------------------------------------------
|
|
779
|
+
# Walk Lookups + Aggregates
|
|
780
|
+
#--------------------------------------------------
|
|
781
|
+
|
|
782
|
+
def lookup(self, node: mm.Lookup):
|
|
783
|
+
self.compute_potential_targets(node.relation)
|
|
784
|
+
self.visit_rel_op(node)
|
|
785
|
+
|
|
786
|
+
def aggregate(self, node: mm.Aggregate):
|
|
787
|
+
self.visit_rel_op(node)
|
|
788
|
+
|
|
789
|
+
def visit_rel_op(self, node: mm.Lookup|mm.Aggregate):
|
|
790
|
+
rel = get_relation(node)
|
|
791
|
+
|
|
792
|
+
# special case eq lookups
|
|
793
|
+
if isinstance(node, mm.Lookup) and rel == b.core.eq:
|
|
794
|
+
if self.visit_eq2(node):
|
|
795
|
+
return
|
|
796
|
+
# return self.visit_eq(node)
|
|
797
|
+
|
|
798
|
+
# special case when the relation needs to be resolved as there are overloads, placeholders,
|
|
799
|
+
# type vars or it needs number specialization
|
|
800
|
+
if self.requires_resolution(rel):
|
|
801
|
+
return self.visit_unresolved_reference(node)
|
|
802
|
+
|
|
803
|
+
# if this is a population check, then it's fine to pass a subtype in to do the check
|
|
804
|
+
# e.g. Employee(Person) is a valid way to check if a person is an employee
|
|
805
|
+
is_population_lookup = isinstance(rel, mm.TypeNode)
|
|
806
|
+
for arg, field in zip(node.args, rel.fields):
|
|
807
|
+
field_type = self.net.resolve(field)
|
|
808
|
+
arg_type = self.net.resolve(arg)
|
|
809
|
+
if not type_matches(arg_type, field_type, is_population_lookup):
|
|
810
|
+
# Do not complain if we can convert the arg to the field type.
|
|
811
|
+
if not conversion_allowed(arg_type, field_type):
|
|
812
|
+
# if the arg is a var and it matches when allowing for super types of
|
|
813
|
+
# the expected we can expect to refine it later; but we add a type
|
|
814
|
+
# requirement to check at the end
|
|
815
|
+
if isinstance(arg, mm.Var) and type_matches(arg_type, field_type, True):
|
|
816
|
+
self.net.add_type_requirement(arg, field)
|
|
817
|
+
else:
|
|
818
|
+
self.net.type_mismatch(node, field_type, arg_type)
|
|
819
|
+
# if we have an abstract var then this field will ultimately propagate to that
|
|
820
|
+
# var's type; also, if this is a population lookup, the type of the population
|
|
821
|
+
# being looked up will flow back to the var
|
|
822
|
+
if isinstance(arg, mm.Var):
|
|
823
|
+
if not field.input:
|
|
824
|
+
self.net.add_edge(field, arg)
|
|
825
|
+
else:
|
|
826
|
+
self.net.add_type_requirement(arg, field)
|
|
827
|
+
# if isinstance(arg, mm.Var) and (bt.is_abstract(arg_type) or is_population_lookup) and field_type != arg_type:
|
|
828
|
+
# self.net.add_edge(field, arg)
|
|
829
|
+
# elif isinstance(arg, mm.Var) and bt.is_abstract(arg_type) and bt.is_abstract(field_type):
|
|
830
|
+
# self.net.add_type_requirement(arg, field)
|
|
831
|
+
|
|
832
|
+
|
|
833
|
+
def requires_resolution(self, rel: mm.Relation) -> bool:
|
|
834
|
+
# has overloads or is a placeholder relation that needs replacement
|
|
835
|
+
if rel.overloads or bt.is_placeholder(rel):
|
|
836
|
+
return True
|
|
837
|
+
# there are type vars or numbers in the fields that need specialization
|
|
838
|
+
for field in rel.fields:
|
|
839
|
+
t = self.net.resolve(field)
|
|
840
|
+
if bt.is_type_var(t) or t == b.core.Number:
|
|
841
|
+
return True
|
|
842
|
+
return False
|
|
843
|
+
|
|
844
|
+
#--------------------------------------------------
|
|
845
|
+
# Reference resolution
|
|
846
|
+
#--------------------------------------------------
|
|
847
|
+
|
|
848
|
+
def visit_unresolved_reference(self, node: mm.Lookup|mm.Update|mm.Aggregate):
|
|
849
|
+
relation = get_relation(node)
|
|
850
|
+
# functions have their outputs determined by their inputs
|
|
851
|
+
is_function = bt.is_function(relation)
|
|
852
|
+
is_placeholder = bt.is_placeholder(relation)
|
|
853
|
+
# add edges between args and the relation based on input/output
|
|
854
|
+
for field, arg in zip(relation.fields, node.args):
|
|
855
|
+
if isinstance(arg, (mm.Var, mm.Literal)):
|
|
856
|
+
if field.input:
|
|
857
|
+
# the arg type will flow into the input
|
|
858
|
+
self.net.add_edge(arg, node)
|
|
859
|
+
# if relation == b.core.eq and bt.is_abstract(self.net.resolve(arg)):
|
|
860
|
+
# # for eq, the input also flows back to the arg
|
|
861
|
+
# self.net.add_edge(field, arg)
|
|
862
|
+
else:
|
|
863
|
+
if is_function:
|
|
864
|
+
# this is an output of a function, so the field type will flow to the arg
|
|
865
|
+
self.net.add_edge(node, arg)
|
|
866
|
+
else:
|
|
867
|
+
if is_placeholder:
|
|
868
|
+
self.net.add_edge(arg, node)
|
|
869
|
+
# the output of a non-function, arg and field flow to each other
|
|
870
|
+
# self.net.add_edge(arg, node)
|
|
871
|
+
# if not placeholder and
|
|
872
|
+
# if placeholder:
|
|
873
|
+
# self.net.add_edge(arg, node)
|
|
874
|
+
# el
|
|
875
|
+
if bt.is_abstract(field.type) and not is_placeholder:
|
|
876
|
+
self.net.add_edge(field, node)
|
|
877
|
+
|
|
878
|
+
if bt.is_abstract(self.net.resolve(arg)):
|
|
879
|
+
self.net.add_edge(node, arg)
|
|
880
|
+
# elif placeholder:
|
|
881
|
+
# # force the resolution of the placeholder by adding an edge
|
|
882
|
+
# self.net.add_edge(arg, node)
|
|
883
|
+
|
|
884
|
+
|
|
885
|
+
# for placeholders, we need to add edges from the target fields back to the node
|
|
886
|
+
# if relation in self.net.potential_targets:
|
|
887
|
+
# for target in self.net.potential_targets[relation]:
|
|
888
|
+
# for arg, field in zip(node.args, target.fields):
|
|
889
|
+
# if isinstance(arg, mm.Var) and bt.is_abstract(self.net.resolve(arg)):
|
|
890
|
+
# self.net.add_edge(field, node)
|
|
891
|
+
# for field in target.fields:
|
|
892
|
+
# if bt.is_abstract(self.net.resolve(field)):
|
|
893
|
+
# self.net.add_edge(field, node)
|
|
894
|
+
|
|
895
|
+
|
|
896
|
+
|
|
897
|
+
#--------------------------------------------------
|
|
898
|
+
# Eq
|
|
899
|
+
#--------------------------------------------------
|
|
900
|
+
|
|
901
|
+
def visit_eq2(self, node: mm.Lookup):
|
|
902
|
+
(left, right) = node.args
|
|
903
|
+
left_type = self.net.resolve(left)
|
|
904
|
+
right_type = self.net.resolve(right)
|
|
905
|
+
if bt.is_abstract(left_type) and bt.is_abstract(right_type):
|
|
906
|
+
assert isinstance(left, mm.Var) and isinstance(right, mm.Var)
|
|
907
|
+
# if both sides are abstract, then whatever we find out about
|
|
908
|
+
# either should propagate to the other
|
|
909
|
+
self.net.add_edge(left, right)
|
|
910
|
+
self.net.add_edge(right, left)
|
|
911
|
+
return True
|
|
912
|
+
else:
|
|
913
|
+
return False
|
|
914
|
+
def visit_eq(self, node: mm.Lookup):
|
|
915
|
+
(left, right) = node.args
|
|
916
|
+
left_type = self.net.resolve(left)
|
|
917
|
+
right_type = self.net.resolve(right)
|
|
918
|
+
if bt.is_abstract(left_type) and bt.is_abstract(right_type):
|
|
919
|
+
assert isinstance(left, mm.Var) and isinstance(right, mm.Var)
|
|
920
|
+
# if both sides are abstract, then whatever we find out about
|
|
921
|
+
# either should propagate to the other
|
|
922
|
+
self.net.add_edge(left, right)
|
|
923
|
+
self.net.add_edge(right, left)
|
|
924
|
+
elif bt.is_abstract(left_type):
|
|
925
|
+
assert isinstance(left, mm.Var)
|
|
926
|
+
if isinstance(right, (mm.Var, mm.Literal)):
|
|
927
|
+
self.net.add_edge(right, left)
|
|
928
|
+
elif bt.is_abstract(right_type):
|
|
929
|
+
assert isinstance(right, mm.Var)
|
|
930
|
+
if isinstance(left, (mm.Var, mm.Literal)):
|
|
931
|
+
self.net.add_edge(left, right)
|
|
932
|
+
elif not type_matches(left_type, right_type):
|
|
933
|
+
if not conversion_allowed(left_type, right_type) and not conversion_allowed(right_type, left_type):
|
|
934
|
+
self.net.type_mismatch(node, left_type, right_type)
|
|
935
|
+
|
|
936
|
+
|
|
937
|
+
|
|
938
|
+
|
|
939
|
+
#--------------------------------------------------
|
|
940
|
+
# Replacer
|
|
941
|
+
#--------------------------------------------------
|
|
942
|
+
|
|
943
|
+
# Once we've pushed all the types through the network, we need to replace the types of
|
|
944
|
+
# fields and vars that we may have discovered. We also need to replace placeholder lookups
|
|
945
|
+
# with the chosen relations and do any conversions that are needed.
|
|
946
|
+
class Replacer(Rewriter):
|
|
947
|
+
def __init__(self, net:PropagationNetwork):
|
|
948
|
+
super().__init__()
|
|
949
|
+
self.net = net
|
|
950
|
+
|
|
951
|
+
def rewrite(self, model: T) -> T:
|
|
952
|
+
return self(model) # type: ignore
|
|
953
|
+
|
|
954
|
+
def logical(self, logical: mm.Logical):
|
|
955
|
+
if len(logical.body) == 0:
|
|
956
|
+
return logical
|
|
957
|
+
# inline logicals that are just there to group other nodes during rewrite
|
|
958
|
+
body = []
|
|
959
|
+
for child in logical.body:
|
|
960
|
+
if isinstance(child, mm.Logical) and not child.optional and not child.scope:
|
|
961
|
+
body.extend(child.body)
|
|
962
|
+
else:
|
|
963
|
+
body.append(child)
|
|
964
|
+
return logical.mut(body = tuple(body))
|
|
965
|
+
|
|
966
|
+
#--------------------------------------------------
|
|
967
|
+
# Rewriter handlers
|
|
968
|
+
#--------------------------------------------------
|
|
969
|
+
|
|
970
|
+
def field(self, node: mm.Field):
|
|
971
|
+
# TODO - this is only modifying the relation in the model, but then we have a new
|
|
972
|
+
# relation there, which is different than the object referenced by tasks.
|
|
973
|
+
if node in self.net.resolved_types:
|
|
974
|
+
return mm.Field(node.name, self.net.resolved_types[node], node.input)
|
|
975
|
+
return node
|
|
976
|
+
|
|
977
|
+
def var(self, node: mm.Var):
|
|
978
|
+
if node in self.net.resolved_types:
|
|
979
|
+
return mm.Var(self.net.resolved_types[node], node.name)
|
|
980
|
+
return node
|
|
981
|
+
|
|
982
|
+
def literal(self, node: mm.Literal):
|
|
983
|
+
if node in self.net.resolved_types:
|
|
984
|
+
return mm.Literal(self.net.resolved_types[node], node.value)
|
|
985
|
+
return node
|
|
986
|
+
|
|
987
|
+
def update(self, node: mm.Update):
|
|
988
|
+
return self.convert_arguments(node, node.relation)
|
|
989
|
+
|
|
990
|
+
def lookup(self, node: mm.Lookup):
|
|
991
|
+
# We need to handle eq specially because its arguments can be converted symmetrically
|
|
992
|
+
if node.relation == b.core.eq:
|
|
993
|
+
return self.visit_eq_lookup(node)
|
|
994
|
+
|
|
995
|
+
args = types = None
|
|
996
|
+
if node.id in self.net.resolved_placeholder:
|
|
997
|
+
resolved_relations = self.net.resolved_placeholder[node.id]
|
|
998
|
+
args = self.net.get_args(node, resolved_relations[0])
|
|
999
|
+
types = [f.type for f in resolved_relations[0].fields]
|
|
1000
|
+
elif node.id in self.net.resolved_overload:
|
|
1001
|
+
resolved_relations = [node.relation]
|
|
1002
|
+
types = self.net.resolved_overload[node.id].types
|
|
1003
|
+
else:
|
|
1004
|
+
resolved_relations = [node.relation]
|
|
1005
|
+
|
|
1006
|
+
if len(resolved_relations) == 1:
|
|
1007
|
+
x = self.convert_arguments(node, resolved_relations[0], args, types)
|
|
1008
|
+
if isinstance(x, mm.Logical) and len(x.body) == 1:
|
|
1009
|
+
return x.body[0]
|
|
1010
|
+
else:
|
|
1011
|
+
return x
|
|
1012
|
+
|
|
1013
|
+
branches:list = []
|
|
1014
|
+
for target in resolved_relations:
|
|
1015
|
+
args = self.net.get_args(node, target)
|
|
1016
|
+
types = [f.type for f in self.net.get_fields(resolved_relations[0], node.relation.name)]
|
|
1017
|
+
# adding this logical to avoid issues in the old backend
|
|
1018
|
+
branches.append(mm.Logical((self.convert_arguments(node, target, args, types=types),)))
|
|
1019
|
+
return mm.Union(tuple(branches))
|
|
1020
|
+
|
|
1021
|
+
def convert_arguments(self, node: mm.Lookup|mm.Update, relation: mm.Relation, args: Iterable[mm.Value]|None=None, types: Iterable[mm.Type]|None=None) -> mm.Logical|mm.Lookup|mm.Update:
|
|
1022
|
+
args = args or node.args
|
|
1023
|
+
types = types or [self.net.resolve(f) for f in relation.fields]
|
|
1024
|
+
number_type = self.net.resolved_number.get(node.id)
|
|
1025
|
+
is_function = bt.is_function(relation)
|
|
1026
|
+
tasks = []
|
|
1027
|
+
final_args = []
|
|
1028
|
+
for arg, field, field_type in zip(args, relation.fields, types):
|
|
1029
|
+
if isinstance(arg, (mm.Var, mm.Literal)) and (not is_function or field.input):
|
|
1030
|
+
arg_type = to_type(arg)
|
|
1031
|
+
# field_type = to_type(field)
|
|
1032
|
+
if number_type and bt.is_number(arg_type) and not arg_type == b.core.ScaledNumber:
|
|
1033
|
+
field_type = number_type
|
|
1034
|
+
# the typer previously made sure that this should be valid so a type mismatch
|
|
1035
|
+
# means we need to convert
|
|
1036
|
+
if not type_matches(arg_type, field_type): # and conversion_allowed(arg_type, field_type):
|
|
1037
|
+
final_args.append(convert(arg, field_type, tasks))
|
|
1038
|
+
else:
|
|
1039
|
+
final_args.append(arg)
|
|
1040
|
+
else:
|
|
1041
|
+
final_args.append(arg)
|
|
1042
|
+
if isinstance(node, mm.Lookup):
|
|
1043
|
+
tasks.append(node.mut(relation = relation, args = tuple(final_args)))
|
|
1044
|
+
else:
|
|
1045
|
+
tasks.append(node.mut(relation = relation, args = tuple(final_args)))
|
|
1046
|
+
if len(tasks) == 1:
|
|
1047
|
+
return tasks[0]
|
|
1048
|
+
return mm.Logical(tuple(tasks))
|
|
1049
|
+
|
|
1050
|
+
|
|
1051
|
+
def visit_eq_lookup(self, node: mm.Lookup):
|
|
1052
|
+
(left, right) = node.args
|
|
1053
|
+
left_type = to_type(left)
|
|
1054
|
+
right_type = to_type(right)
|
|
1055
|
+
|
|
1056
|
+
if type_matches(left_type, right_type):
|
|
1057
|
+
return node
|
|
1058
|
+
|
|
1059
|
+
assert isinstance(left, (mm.Var, mm.Literal)) and isinstance(right, (mm.Var, mm.Literal))
|
|
1060
|
+
final_args = []
|
|
1061
|
+
tasks = []
|
|
1062
|
+
if conversion_allowed(left_type, right_type):
|
|
1063
|
+
final_args = [convert(left, right_type, tasks), right]
|
|
1064
|
+
elif conversion_allowed(right_type, left_type):
|
|
1065
|
+
final_args = [left, convert(right, left_type, tasks)]
|
|
1066
|
+
else:
|
|
1067
|
+
self.net.type_mismatch(node, left_type, right_type)
|
|
1068
|
+
return node
|
|
1069
|
+
|
|
1070
|
+
tasks.append(mm.Lookup(b.core.eq, tuple(final_args)))
|
|
1071
|
+
return mm.Logical(tuple(tasks))
|
|
1072
|
+
|
|
1073
|
+
#--------------------------------------------------
|
|
1074
|
+
# Helpers
|
|
1075
|
+
#--------------------------------------------------
|
|
1076
|
+
|
|
1077
|
+
def get_relation(node: mm.Lookup|mm.Update|mm.Aggregate) -> mm.Relation:
|
|
1078
|
+
if isinstance(node, mm.Aggregate):
|
|
1079
|
+
return node.aggregation
|
|
1080
|
+
return node.relation
|
|
1081
|
+
|
|
1082
|
+
def get_name(type: mm.Type) -> str:
|
|
1083
|
+
if isinstance(type, mm.ScalarType):
|
|
1084
|
+
return type.name
|
|
1085
|
+
elif isinstance(type, mm.UnionType):
|
|
1086
|
+
return '|'.join([get_name(t) for t in type.types])
|
|
1087
|
+
elif isinstance(type, mm.ListType):
|
|
1088
|
+
return f'List[{get_name(type.element_type)}]'
|
|
1089
|
+
elif isinstance(type, mm.TupleType):
|
|
1090
|
+
return f'Tuple[{", ".join([get_name(t) for t in type.element_types])}]'
|
|
1091
|
+
else:
|
|
1092
|
+
raise TypeError(f"Unknown type: {type}")
|
|
1093
|
+
|
|
1094
|
+
#--------------------------------------------------
|
|
1095
|
+
# Type and Relation helpers
|
|
1096
|
+
#--------------------------------------------------
|
|
1097
|
+
|
|
1098
|
+
|
|
1099
|
+
def is_potential_target(placeholder: mm.Relation, target: mm.Relation) -> bool:
|
|
1100
|
+
""" Whether this target is matches the placeholder signature and, thus, can be a potential target. """
|
|
1101
|
+
if placeholder != target and len(placeholder.fields) == len(target.fields) and not bt.is_placeholder(target):
|
|
1102
|
+
return placeholder.name == target.name or any(placeholder.name == reading.name for reading in target.readings)
|
|
1103
|
+
return False
|
|
1104
|
+
|
|
1105
|
+
def get_potential_targets(model: mm.Model, placeholder: mm.Relation) -> list[mm.Relation]:
|
|
1106
|
+
""" Get all potential target relations in the model that match the placeholder signature. """
|
|
1107
|
+
return list(filter(lambda r: is_potential_target(placeholder, r), model.relations))
|
|
1108
|
+
|
|
1109
|
+
def to_type(value: mm.Value|mm.Field|mm.Literal) -> mm.Type:
|
|
1110
|
+
if isinstance(value, (mm.Var, mm.Field, mm.Literal)):
|
|
1111
|
+
return value.type
|
|
1112
|
+
|
|
1113
|
+
if isinstance(value, mm.Type):
|
|
1114
|
+
return b.core.Type
|
|
1115
|
+
|
|
1116
|
+
if isinstance(value, tuple):
|
|
1117
|
+
return mm.TupleType(element_types=tuple(to_type(v) for v in value))
|
|
1118
|
+
|
|
1119
|
+
raise TypeError(f"Cannot determine IR type for value: {value} of type {type(value).__name__}")
|
|
1120
|
+
|
|
1121
|
+
|
|
1122
|
+
def convert(value: mm.Var|mm.Literal, to_type: mm.Type, tasks: list[mm.Task]) -> mm.Value:
|
|
1123
|
+
# if the arg is a literal, we can just change its type
|
|
1124
|
+
# TODO - we may want to check that the value is actually convertible
|
|
1125
|
+
if isinstance(value, mm.Literal):
|
|
1126
|
+
return mm.Literal(to_type, value.value)
|
|
1127
|
+
|
|
1128
|
+
# otherise we need to add a cast
|
|
1129
|
+
name = sanitize(value.name + "_" + get_name(to_type))
|
|
1130
|
+
to_type_base = bt.get_primitive_supertype(to_type) or to_type
|
|
1131
|
+
new_value = mm.Var(to_type_base, name)
|
|
1132
|
+
tasks.append(mm.Lookup(b.core.cast, (to_type_base, value, new_value)))
|
|
1133
|
+
return new_value
|
|
1134
|
+
|
|
1135
|
+
|
|
1136
|
+
def conversion_allowed(from_type: mm.Type, to_type: mm.Type) -> bool:
|
|
1137
|
+
# value type conversion is allowed
|
|
1138
|
+
x = bt.get_primitive_supertype(from_type)
|
|
1139
|
+
y = bt.get_primitive_supertype(to_type)
|
|
1140
|
+
if x and y and (x != from_type or y != to_type) and conversion_allowed(x, y):
|
|
1141
|
+
return True
|
|
1142
|
+
|
|
1143
|
+
# numbers can be converted to floats
|
|
1144
|
+
if bt.is_numeric(from_type) and to_type == b.core.Float:
|
|
1145
|
+
return True
|
|
1146
|
+
|
|
1147
|
+
# a number can be converted to another number of larger scale
|
|
1148
|
+
if isinstance(from_type, mm.NumberType) and isinstance(to_type, mm.NumberType):
|
|
1149
|
+
if to_type.scale > from_type.scale:
|
|
1150
|
+
return True
|
|
1151
|
+
|
|
1152
|
+
if from_type == b.core.Number and isinstance(to_type, mm.NumberType):
|
|
1153
|
+
return True
|
|
1154
|
+
|
|
1155
|
+
return False
|
|
1156
|
+
|
|
1157
|
+
def type_matches(actual: mm.Type, expected: mm.Type, is_type_relation=False) -> bool:
|
|
1158
|
+
"""
|
|
1159
|
+
True iff we can use a value of the actual type when expecting the expected type, without
|
|
1160
|
+
conversions.
|
|
1161
|
+
|
|
1162
|
+
Any super-type of `actual` can match `expected`. For example if we expect a `Person`, we
|
|
1163
|
+
can use an `Employee` if `Employee < Person`.
|
|
1164
|
+
|
|
1165
|
+
In general, the other way around is not true: if we expect an `Employee` we cannot use a
|
|
1166
|
+
`Person` instead.
|
|
1167
|
+
|
|
1168
|
+
However, when the relation is a Type (previsously known as "population relations"), it
|
|
1169
|
+
is valid to provide sub-types of the expected type. For example, `Employee(Person)` is
|
|
1170
|
+
a valid way to check that `Person` is an `Employee` on a `Lookup`, or to assert that a
|
|
1171
|
+
particular `Person` is an `Employee` on an `Update`.
|
|
1172
|
+
"""
|
|
1173
|
+
# exact match
|
|
1174
|
+
if actual == expected:
|
|
1175
|
+
return True
|
|
1176
|
+
|
|
1177
|
+
# any matches anything
|
|
1178
|
+
if actual == b.core.Any or expected == b.core.Any:
|
|
1179
|
+
return True
|
|
1180
|
+
|
|
1181
|
+
# type vars match anything
|
|
1182
|
+
if expected == b.core.TypeVar:
|
|
1183
|
+
return True
|
|
1184
|
+
|
|
1185
|
+
# TODO - remove this once we make them singletons per precision/scale
|
|
1186
|
+
if isinstance(actual, mm.NumberType) and isinstance(expected, mm.NumberType):
|
|
1187
|
+
if actual.precision == expected.precision and actual.scale == expected.scale:
|
|
1188
|
+
return True
|
|
1189
|
+
|
|
1190
|
+
# if an entity type var or any entity is expected, it matches any actual entity type
|
|
1191
|
+
if (expected == b.core.EntityTypeVar or bt.extends(expected, b.core.AnyEntity)) and not bt.is_primitive(actual):
|
|
1192
|
+
return True
|
|
1193
|
+
|
|
1194
|
+
# the abstract Number type and the number type variable match any number type
|
|
1195
|
+
if (expected == b.core.Number) and bt.is_number(actual):
|
|
1196
|
+
return True
|
|
1197
|
+
|
|
1198
|
+
if (expected == b.core.Numeric) and bt.is_numeric(actual):
|
|
1199
|
+
return True
|
|
1200
|
+
|
|
1201
|
+
# if actual is scalar, any of its parents may match the expected type
|
|
1202
|
+
if isinstance(actual, mm.ScalarType) and any([type_matches(parent, expected) for parent in actual.super_types]):
|
|
1203
|
+
return True
|
|
1204
|
+
|
|
1205
|
+
# if expected is a value type or this is a check for a type relation, any of the expected type's parents may match the actual type
|
|
1206
|
+
if (is_type_relation or bt.is_value_type(expected)) and isinstance(expected, mm.ScalarType) and any([type_matches(actual, parent, is_type_relation) for parent in expected.super_types]):
|
|
1207
|
+
return True
|
|
1208
|
+
|
|
1209
|
+
# if we expect a union, the actual can match any of its types
|
|
1210
|
+
if isinstance(expected, mm.UnionType):
|
|
1211
|
+
for t in expected.types:
|
|
1212
|
+
if type_matches(t, actual, is_type_relation):
|
|
1213
|
+
return True
|
|
1214
|
+
|
|
1215
|
+
# if actual is a union, every one of its types must match the expected type
|
|
1216
|
+
# if isinstance(actual, mm.UnionType):
|
|
1217
|
+
# for t in actual.types:
|
|
1218
|
+
# if not type_matches(t, expected, is_type_relation):
|
|
1219
|
+
# return False
|
|
1220
|
+
# return True
|
|
1221
|
+
# TODO - we have to distinguish between when we are checking that a specific arg matches
|
|
1222
|
+
# a relation vs when we are selecting relations for the placeholders; then we have to
|
|
1223
|
+
# decide between the above and this.
|
|
1224
|
+
if isinstance(actual, mm.UnionType):
|
|
1225
|
+
for t in actual.types:
|
|
1226
|
+
if type_matches(t, expected, is_type_relation):
|
|
1227
|
+
return True
|
|
1228
|
+
|
|
1229
|
+
# a list type matches if their element types match
|
|
1230
|
+
if isinstance(actual, (mm.ListType, mm.TupleType)) and isinstance(expected, mm.ListType):
|
|
1231
|
+
if isinstance(actual, mm.TupleType):
|
|
1232
|
+
for et in actual.element_types:
|
|
1233
|
+
if not type_matches(et, expected.element_type):
|
|
1234
|
+
return False
|
|
1235
|
+
return True
|
|
1236
|
+
return type_matches(actual.element_type, expected.element_type)
|
|
1237
|
+
|
|
1238
|
+
# a tuple types match if any of all their types match
|
|
1239
|
+
if isinstance(actual, mm.TupleType) and isinstance(expected, mm.TupleType):
|
|
1240
|
+
return all([type_matches(ae, ee) for ae, ee in zip(actual.element_types, expected.element_types)])
|
|
1241
|
+
|
|
1242
|
+
# accept tuples with a single element type to match a list with that type
|
|
1243
|
+
if isinstance(actual, mm.TupleType) and isinstance(expected, mm.ListType):
|
|
1244
|
+
if len(set(actual.element_types)) == 1:
|
|
1245
|
+
return type_matches(actual.element_types[0], expected.element_type)
|
|
1246
|
+
|
|
1247
|
+
# otherwise no match
|
|
1248
|
+
return False
|
|
1249
|
+
|
|
1250
|
+
|
|
1251
|
+
def merge_types(type1: mm.Type, type2: mm.Type) -> mm.Type:
|
|
1252
|
+
if type1 == type2:
|
|
1253
|
+
return type1
|
|
1254
|
+
types_to_process = [type1, type2]
|
|
1255
|
+
|
|
1256
|
+
# if one of them is the abstract Number type, pick the other
|
|
1257
|
+
if type1 == b.core.Number and isinstance(type2, mm.NumberType):
|
|
1258
|
+
return type2
|
|
1259
|
+
if type2 == b.core.Number and isinstance(type1, mm.NumberType):
|
|
1260
|
+
return type1
|
|
1261
|
+
|
|
1262
|
+
# if both are number types, pick the one with larger scale/precision
|
|
1263
|
+
if isinstance(type1, mm.NumberType) and isinstance(type2, mm.NumberType):
|
|
1264
|
+
if type1.scale > type2.scale or (type1.scale == type2.scale and type1.precision > type2.precision):
|
|
1265
|
+
return type1
|
|
1266
|
+
else:
|
|
1267
|
+
return type2
|
|
1268
|
+
|
|
1269
|
+
# if we are overriding a number with a float, pick float
|
|
1270
|
+
if isinstance(type1, mm.NumberType) and type2 == b.core.Float:
|
|
1271
|
+
return type2
|
|
1272
|
+
|
|
1273
|
+
# if one extends the other, pick the most specific one
|
|
1274
|
+
if bt.extends(type1, type2):
|
|
1275
|
+
return type1
|
|
1276
|
+
if bt.extends(type2, type1):
|
|
1277
|
+
return type2
|
|
1278
|
+
|
|
1279
|
+
# give precedence to nominal types (e.g. merging USD(decimal) with decimal gives USD(decimal))
|
|
1280
|
+
base_primitive_type1 = bt.get_primitive_supertype(type1)
|
|
1281
|
+
base_primitive_type2 = bt.get_primitive_supertype(type2)
|
|
1282
|
+
if base_primitive_type1 == base_primitive_type2:
|
|
1283
|
+
if bt.is_primitive(type1):
|
|
1284
|
+
return type2
|
|
1285
|
+
elif bt.is_primitive(type2):
|
|
1286
|
+
return type1
|
|
1287
|
+
|
|
1288
|
+
combined = OrderedSet()
|
|
1289
|
+
# Iterative flattening of union types
|
|
1290
|
+
while types_to_process:
|
|
1291
|
+
t = types_to_process.pop()
|
|
1292
|
+
if isinstance(t, mm.UnionType):
|
|
1293
|
+
types_to_process.extend(t.types)
|
|
1294
|
+
else:
|
|
1295
|
+
combined.add(t)
|
|
1296
|
+
|
|
1297
|
+
# If we have multiple types and Any or AnyEntity is one of them, remove Any
|
|
1298
|
+
if len(combined) > 1:
|
|
1299
|
+
if b.core.Any in combined:
|
|
1300
|
+
combined.remove(b.core.Any)
|
|
1301
|
+
if b.core.AnyEntity in combined:
|
|
1302
|
+
combined.remove(b.core.AnyEntity)
|
|
1303
|
+
|
|
1304
|
+
# If we still have multiple types, make sure supertypes are removed to keep only the
|
|
1305
|
+
# most specific types
|
|
1306
|
+
if len(combined) > 1:
|
|
1307
|
+
to_remove = set()
|
|
1308
|
+
for t1 in combined:
|
|
1309
|
+
for t2 in combined:
|
|
1310
|
+
if t1 != t2 and bt.extends(t1, t2):
|
|
1311
|
+
to_remove.add(t2)
|
|
1312
|
+
for r in to_remove:
|
|
1313
|
+
combined.remove(r)
|
|
1314
|
+
|
|
1315
|
+
# Return single type or create a union
|
|
1316
|
+
return next(iter(combined)) if len(combined) == 1 else mm.UnionType(types=tuple(combined))
|
|
1317
|
+
|
|
1318
|
+
def invalid_type(type:mm.Type) -> bool:
|
|
1319
|
+
if isinstance(type, mm.UnionType):
|
|
1320
|
+
# if there are multiple primitives, or a primitive and a non-primitive
|
|
1321
|
+
# then we have an invalid type
|
|
1322
|
+
if len(type.types) > 1:
|
|
1323
|
+
return any([bt.is_primitive(t) for t in type.types])
|
|
1324
|
+
return False
|
|
1325
|
+
|
|
1326
|
+
|
|
1327
|
+
#--------------------------------------------------
|
|
1328
|
+
# Type Errors
|
|
1329
|
+
#--------------------------------------------------
|
|
1330
|
+
|
|
1331
|
+
@dataclass
|
|
1332
|
+
class TyperError():
|
|
1333
|
+
node: mm.Node
|
|
1334
|
+
|
|
1335
|
+
def report(self):
|
|
1336
|
+
err(self.name(), self.message(), self.parts())
|
|
1337
|
+
|
|
1338
|
+
def name(self) -> str:
|
|
1339
|
+
return type(self).__name__
|
|
1340
|
+
|
|
1341
|
+
def message(self) -> str:
|
|
1342
|
+
raise NotImplementedError()
|
|
1343
|
+
|
|
1344
|
+
def parts(self) -> list[Part]:
|
|
1345
|
+
if self.node.source is None:
|
|
1346
|
+
return [str(self.node)]
|
|
1347
|
+
return [str(self.node), Source(self.node.source)]
|
|
1348
|
+
|
|
1349
|
+
@dataclass
|
|
1350
|
+
class TypeMismatch(TyperError):
|
|
1351
|
+
expected: mm.Type
|
|
1352
|
+
actual: mm.Type
|
|
1353
|
+
|
|
1354
|
+
def message(self) -> str:
|
|
1355
|
+
return f"Expected {get_name(self.expected)}, got {get_name(self.actual)}"
|
|
1356
|
+
|
|
1357
|
+
@dataclass
|
|
1358
|
+
class InvalidType(TyperError):
|
|
1359
|
+
type: mm.Type
|
|
1360
|
+
|
|
1361
|
+
def message(self) -> str:
|
|
1362
|
+
return f"Incompatible types infered: {get_name(self.type)}"
|
|
1363
|
+
|
|
1364
|
+
@dataclass
|
|
1365
|
+
class UnresolvedOverload(TyperError):
|
|
1366
|
+
arg_types: list[mm.Type]
|
|
1367
|
+
|
|
1368
|
+
def message(self) -> str:
|
|
1369
|
+
assert isinstance(self.node, (mm.Lookup, mm.Update, mm.Aggregate))
|
|
1370
|
+
rel = get_relation(self.node)
|
|
1371
|
+
types = ', '.join([get_name(t) for t in self.arg_types])
|
|
1372
|
+
return f"Unresolved overload: {rel.name}({types})"
|
|
1373
|
+
|
|
1374
|
+
@dataclass
|
|
1375
|
+
class UnresolvedType(TyperError):
|
|
1376
|
+
|
|
1377
|
+
def message(self) -> str:
|
|
1378
|
+
return "Unable to determine concrete type."
|