relationalai 0.13.5__py3-none-any.whl → 1.0.0a2__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 -256
- 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/config/shims.py +1 -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 +1719 -0
- relationalai/semantics/frontend/core.py +179 -0
- relationalai/semantics/frontend/front_compiler.py +1316 -0
- relationalai/semantics/frontend/pprint.py +408 -0
- relationalai/semantics/metamodel/__init__.py +6 -40
- relationalai/semantics/metamodel/builtins.py +206 -772
- relationalai/semantics/metamodel/metamodel.py +465 -0
- relationalai/semantics/metamodel/metamodel_analyzer.py +519 -0
- relationalai/semantics/metamodel/pprint.py +414 -0
- relationalai/semantics/metamodel/rewriter.py +266 -0
- relationalai/semantics/metamodel/typer.py +1213 -0
- relationalai/semantics/std/__init__.py +60 -40
- relationalai/semantics/std/aggregates.py +148 -0
- relationalai/semantics/std/common.py +44 -0
- relationalai/semantics/std/constraints.py +37 -43
- relationalai/semantics/std/datetime.py +249 -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 +101 -46
- relationalai/shims/executor.py +179 -0
- relationalai/shims/helpers.py +126 -0
- relationalai/shims/hoister.py +221 -0
- relationalai/shims/mm2v0.py +1394 -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 -109
- 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-1.0.0a2.dist-info/METADATA +44 -0
- relationalai-1.0.0a2.dist-info/RECORD +489 -0
- relationalai-1.0.0a2.dist-info/WHEEL +5 -0
- relationalai-1.0.0a2.dist-info/entry_points.txt +3 -0
- relationalai-1.0.0a2.dist-info/top_level.txt +2 -0
- v0/relationalai/__init__.py +216 -0
- v0/relationalai/clients/__init__.py +5 -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 +2478 -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/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 +325 -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 +474 -0
- v0/relationalai/semantics/lqp/intrinsics.py +24 -0
- v0/relationalai/semantics/lqp/ir.py +124 -0
- v0/relationalai/semantics/lqp/model2lqp.py +877 -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/__init__.py +18 -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 +490 -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 +776 -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/ir.py +923 -0
- v0/relationalai/semantics/metamodel/rewrite/__init__.py +7 -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 +554 -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/metamodel/util.py +505 -0
- v0/relationalai/semantics/metamodel/visitor.py +944 -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 +9019 -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/__init__.py +0 -18
- relationalai/clients/client.py +0 -946
- relationalai/clients/config.py +0 -673
- relationalai/clients/direct_access_client.py +0 -118
- relationalai/clients/exec_txn_poller.py +0 -153
- relationalai/clients/hash_util.py +0 -31
- relationalai/clients/local.py +0 -594
- relationalai/clients/profile_polling.py +0 -73
- relationalai/clients/resources/__init__.py +0 -8
- relationalai/clients/resources/azure/azure.py +0 -502
- relationalai/clients/resources/snowflake/__init__.py +0 -20
- relationalai/clients/resources/snowflake/cli_resources.py +0 -98
- relationalai/clients/resources/snowflake/direct_access_resources.py +0 -739
- relationalai/clients/resources/snowflake/engine_service.py +0 -381
- relationalai/clients/resources/snowflake/engine_state_handlers.py +0 -315
- relationalai/clients/resources/snowflake/error_handlers.py +0 -240
- relationalai/clients/resources/snowflake/export_procedure.py.jinja +0 -249
- relationalai/clients/resources/snowflake/resources_factory.py +0 -99
- relationalai/clients/resources/snowflake/snowflake.py +0 -3193
- relationalai/clients/resources/snowflake/use_index_poller.py +0 -1019
- relationalai/clients/resources/snowflake/use_index_resources.py +0 -188
- relationalai/clients/resources/snowflake/util.py +0 -387
- relationalai/clients/result_helpers.py +0 -420
- relationalai/clients/types.py +0 -118
- 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 -105
- 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 -2496
- 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 -1095
- 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 -535
- 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 -329
- relationalai/semantics/lqp/README.md +0 -34
- relationalai/semantics/lqp/algorithms.py +0 -173
- relationalai/semantics/lqp/builtins.py +0 -213
- relationalai/semantics/lqp/compiler.py +0 -22
- relationalai/semantics/lqp/constructors.py +0 -68
- relationalai/semantics/lqp/executor.py +0 -518
- relationalai/semantics/lqp/export_rewriter.py +0 -40
- relationalai/semantics/lqp/intrinsics.py +0 -24
- relationalai/semantics/lqp/ir.py +0 -150
- relationalai/semantics/lqp/model2lqp.py +0 -1056
- relationalai/semantics/lqp/passes.py +0 -38
- relationalai/semantics/lqp/primitives.py +0 -252
- relationalai/semantics/lqp/result_helpers.py +0 -266
- relationalai/semantics/lqp/rewrite/__init__.py +0 -32
- relationalai/semantics/lqp/rewrite/algorithm.py +0 -385
- relationalai/semantics/lqp/rewrite/annotate_constraints.py +0 -69
- relationalai/semantics/lqp/rewrite/cdc.py +0 -216
- relationalai/semantics/lqp/rewrite/constants_to_vars.py +0 -70
- relationalai/semantics/lqp/rewrite/deduplicate_vars.py +0 -104
- relationalai/semantics/lqp/rewrite/eliminate_data.py +0 -108
- relationalai/semantics/lqp/rewrite/extract_common.py +0 -340
- relationalai/semantics/lqp/rewrite/extract_keys.py +0 -577
- relationalai/semantics/lqp/rewrite/flatten_script.py +0 -301
- relationalai/semantics/lqp/rewrite/function_annotations.py +0 -114
- relationalai/semantics/lqp/rewrite/functional_dependencies.py +0 -348
- relationalai/semantics/lqp/rewrite/period_math.py +0 -77
- relationalai/semantics/lqp/rewrite/quantify_vars.py +0 -339
- relationalai/semantics/lqp/rewrite/splinter.py +0 -76
- relationalai/semantics/lqp/rewrite/unify_definitions.py +0 -323
- relationalai/semantics/lqp/types.py +0 -101
- relationalai/semantics/lqp/utils.py +0 -170
- relationalai/semantics/lqp/validators.py +0 -70
- relationalai/semantics/metamodel/compiler.py +0 -134
- relationalai/semantics/metamodel/dependency.py +0 -880
- relationalai/semantics/metamodel/executor.py +0 -78
- relationalai/semantics/metamodel/factory.py +0 -287
- relationalai/semantics/metamodel/helpers.py +0 -368
- relationalai/semantics/metamodel/ir.py +0 -924
- relationalai/semantics/metamodel/rewrite/__init__.py +0 -8
- relationalai/semantics/metamodel/rewrite/discharge_constraints.py +0 -39
- relationalai/semantics/metamodel/rewrite/dnf_union_splitter.py +0 -220
- relationalai/semantics/metamodel/rewrite/extract_nested_logicals.py +0 -78
- relationalai/semantics/metamodel/rewrite/flatten.py +0 -590
- relationalai/semantics/metamodel/rewrite/format_outputs.py +0 -256
- relationalai/semantics/metamodel/rewrite/handle_aggregations_and_ranks.py +0 -237
- relationalai/semantics/metamodel/typer/checker.py +0 -355
- relationalai/semantics/metamodel/typer/typer.py +0 -1396
- relationalai/semantics/metamodel/util.py +0 -506
- relationalai/semantics/metamodel/visitor.py +0 -945
- 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 -9019
- 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 -1407
- relationalai/semantics/rel/builtins.py +0 -40
- relationalai/semantics/rel/compiler.py +0 -994
- relationalai/semantics/rel/executor.py +0 -363
- 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 -149
- 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/lqp/algorithms.py +0 -345
- relationalai/semantics/tests/test_snapshot_abstract.py +0 -144
- 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 -2089
- relationalai/tools/cli_controls.py +0 -1975
- relationalai/tools/cli_helpers.py +0 -802
- 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/tools/txn_progress.py +0 -188
- relationalai/util/clean_up_databases.py +0 -95
- relationalai/util/list_databases.py +0 -9
- relationalai/util/otel_configuration.py +0 -26
- 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.13.5.dist-info/METADATA +0 -74
- relationalai-0.13.5.dist-info/RECORD +0 -473
- relationalai-0.13.5.dist-info/WHEEL +0 -4
- relationalai-0.13.5.dist-info/entry_points.txt +0 -3
- relationalai-0.13.5.dist-info/licenses/LICENSE +0 -202
- relationalai_test_util/__init__.py +0 -4
- relationalai_test_util/fixtures.py +0 -233
- 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/clients/resources/snowflake → 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/semantics/tests/lqp → 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/tools → v0/relationalai/loaders}/__init__.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/pragmas.py +0 -0
- {relationalai → v0/relationalai}/semantics/metamodel/dataflow.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/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,1213 @@
|
|
|
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
|
+
from ...util.structures import OrderedSet
|
|
11
|
+
|
|
12
|
+
from . import metamodel as mm, builtins as bt
|
|
13
|
+
from .rewriter import Walker, Rewriter, NO_WALK
|
|
14
|
+
from .builtins import builtins as b
|
|
15
|
+
|
|
16
|
+
#--------------------------------------------------
|
|
17
|
+
# Typer
|
|
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
|
+
|
|
66
|
+
# replace the fields in the model with the new types
|
|
67
|
+
with tracing.span("typer.replace"):
|
|
68
|
+
replacer = Replacer(net)
|
|
69
|
+
final = replacer.rewrite(node)
|
|
70
|
+
|
|
71
|
+
# report any errors found during typing
|
|
72
|
+
if net.errors:
|
|
73
|
+
for error in net.errors:
|
|
74
|
+
error.report()
|
|
75
|
+
if self.enforce:
|
|
76
|
+
exc("TyperError", "Type errors detected during type inference.")
|
|
77
|
+
return final
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
#--------------------------------------------------
|
|
81
|
+
# Propagation Network
|
|
82
|
+
#--------------------------------------------------
|
|
83
|
+
|
|
84
|
+
# The core idea of the typer is to build a propagation network that represents data
|
|
85
|
+
# dependencies between nodes. Nodes can be variables, literals, fields and tasks that refer
|
|
86
|
+
# to relations (lookups, aggregates, updates). An edge from node A to node B means that
|
|
87
|
+
# in order to resolve references and types for node B we need to know the type of node A.
|
|
88
|
+
#
|
|
89
|
+
# After building the network, we start with roots that have known types (literals, fields
|
|
90
|
+
# loaded from previous analysis and vars with concrete types) and propagate their types via
|
|
91
|
+
# the edges to other nodes. It is possible that the network contains cycles, so we iterate
|
|
92
|
+
# on the work list until fixpoint is reached, i.e. when we cannot propagate any new type.
|
|
93
|
+
#
|
|
94
|
+
# During the propagation, we are only gathering information about the types and references
|
|
95
|
+
# of nodes. Once we reach a fixpoint, we do a final pass to rewrite the model with the new
|
|
96
|
+
# type information, which may include adding casts to convert between types where needed.
|
|
97
|
+
#
|
|
98
|
+
Node = Union[mm.Var, mm.Field, mm.Literal, mm.Lookup, mm.Aggregate, mm.Update]
|
|
99
|
+
|
|
100
|
+
class PropagationNetwork():
|
|
101
|
+
def __init__(self, model: mm.Model):
|
|
102
|
+
# the model that is either being analyzed or that is the context for a query
|
|
103
|
+
self.model = model
|
|
104
|
+
|
|
105
|
+
# the resolved types of nodes in the network
|
|
106
|
+
self.resolved_types: dict[Node, mm.Type] = {}
|
|
107
|
+
|
|
108
|
+
# map from unresolved placeholder relations to their potential target replacements
|
|
109
|
+
self.potential_targets: dict[mm.Relation, list[mm.Relation]] = {}
|
|
110
|
+
|
|
111
|
+
# nodes loaded from previous analysis, which we use as roots to start propagation
|
|
112
|
+
self.loaded_roots = set()
|
|
113
|
+
|
|
114
|
+
# edges in the propagation network, from one node to potentially many
|
|
115
|
+
self.edges:dict[Node, OrderedSet[Node]] = defaultdict(lambda: OrderedSet())
|
|
116
|
+
self.back_edges:dict[Node, OrderedSet[Node]] = defaultdict(lambda: OrderedSet())
|
|
117
|
+
|
|
118
|
+
# all errors collected during inference
|
|
119
|
+
self.errors:list[TyperError] = []
|
|
120
|
+
|
|
121
|
+
# overloads resolved for a lookup/update/aggregate, by node id. This is only for
|
|
122
|
+
# relations that declare overloads
|
|
123
|
+
self.resolved_overload:dict[int, mm.Overload] = {}
|
|
124
|
+
|
|
125
|
+
# placeholders resolved for a lookup, by node id. This is only for relations that
|
|
126
|
+
# are placeholders (i.e. only Any fields) and will be replaced by references to
|
|
127
|
+
# these concrete relations. E.g. a query for "name(Any, Any)" may be replaced by
|
|
128
|
+
# the union of "name(Dog, String)" and name(Cat, String)".
|
|
129
|
+
self.resolved_placeholder:dict[int, list[mm.Relation]] = {}
|
|
130
|
+
|
|
131
|
+
# for a given lookup/update/aggregate that involves numbers, the specific number
|
|
132
|
+
# type resolved for it.
|
|
133
|
+
self.resolved_number:dict[int, mm.NumberType] = {}
|
|
134
|
+
|
|
135
|
+
#--------------------------------------------------
|
|
136
|
+
# Error reporting
|
|
137
|
+
#--------------------------------------------------
|
|
138
|
+
|
|
139
|
+
def type_mismatch(self, node: Node|mm.Update, expected: mm.Type, actual: mm.Type):
|
|
140
|
+
self.errors.append(TypeMismatch(node, expected, actual))
|
|
141
|
+
|
|
142
|
+
def invalid_type(self, node: Node, type: mm.Type):
|
|
143
|
+
self.errors.append(InvalidType(node, type))
|
|
144
|
+
|
|
145
|
+
def unresolved_overload(self, node: mm.Lookup|mm.Aggregate):
|
|
146
|
+
# TODO - consider renaming this to UnresolvedReference
|
|
147
|
+
self.errors.append(UnresolvedOverload(node, [self.resolve(a) for a in node.args]))
|
|
148
|
+
|
|
149
|
+
def unresolved_type(self, node: Node):
|
|
150
|
+
# TODO - this is not being used yet, we need a pass at the end to check for any node
|
|
151
|
+
# that we could not resolve
|
|
152
|
+
self.errors.append(UnresolvedType(node))
|
|
153
|
+
|
|
154
|
+
def has_errors(self, node: Node) -> bool:
|
|
155
|
+
for mismatch in self.errors:
|
|
156
|
+
if mismatch.node == node:
|
|
157
|
+
return True
|
|
158
|
+
return False
|
|
159
|
+
|
|
160
|
+
#--------------------------------------------------
|
|
161
|
+
# Types and Edges
|
|
162
|
+
#--------------------------------------------------
|
|
163
|
+
|
|
164
|
+
def add_edge(self, source: Node, target: Node):
|
|
165
|
+
self.edges[source].add(target)
|
|
166
|
+
self.back_edges[target].add(source)
|
|
167
|
+
|
|
168
|
+
def add_resolved_type(self, node: Node, type: mm.Type):
|
|
169
|
+
""" Register that this node was resolved to have this type. """
|
|
170
|
+
if node in self.resolved_types:
|
|
171
|
+
self.resolved_types[node] = merge_types(self.resolved_types[node], type)
|
|
172
|
+
else:
|
|
173
|
+
self.resolved_types[node] = type
|
|
174
|
+
|
|
175
|
+
#--------------------------------------------------
|
|
176
|
+
# Load types from a previous analysis
|
|
177
|
+
#--------------------------------------------------
|
|
178
|
+
|
|
179
|
+
def load_types(self, type_dict: dict[Node, mm.Type]):
|
|
180
|
+
for node, type in type_dict.items():
|
|
181
|
+
if isinstance(node, (mm.Field)):
|
|
182
|
+
self.add_resolved_type(node, type)
|
|
183
|
+
self.loaded_roots.add(node)
|
|
184
|
+
|
|
185
|
+
#--------------------------------------------------
|
|
186
|
+
# Resolve Values
|
|
187
|
+
#--------------------------------------------------
|
|
188
|
+
|
|
189
|
+
def resolve(self, value: mm.Value) -> mm.Type:
|
|
190
|
+
if isinstance(value, (mm.Var, mm.Literal)):
|
|
191
|
+
return self.resolved_types.get(value) or to_type(value)
|
|
192
|
+
if isinstance(value, mm.Field):
|
|
193
|
+
return self.resolved_types.get(value) or value.type
|
|
194
|
+
assert not isinstance(value, (mm.Task)), "Should never try to resolve a task"
|
|
195
|
+
return to_type(value)
|
|
196
|
+
|
|
197
|
+
#--------------------------------------------------
|
|
198
|
+
# Resolve References
|
|
199
|
+
#--------------------------------------------------
|
|
200
|
+
|
|
201
|
+
def all_dependencies_resolved(self, op:mm.Lookup|mm.Aggregate):
|
|
202
|
+
""" True iff all dependencies required to resolve this reference are met. """
|
|
203
|
+
rel = get_relation(op)
|
|
204
|
+
# if this is a placeholder, eq or cast, we assume all possible args were resolved
|
|
205
|
+
if bt.is_placeholder(rel) or rel == b.core.eq or rel == b.core.cast:
|
|
206
|
+
return True
|
|
207
|
+
|
|
208
|
+
# else, find whether all back-edges were resolved
|
|
209
|
+
for node in self.back_edges[op]:
|
|
210
|
+
# cannot resolve if a required var, literal or input field is still abstract
|
|
211
|
+
if isinstance(node, (mm.Var, mm.Literal)) or (isinstance(node, mm.Field) and node.input):
|
|
212
|
+
node_type = self.resolve(node)
|
|
213
|
+
if bt.is_abstract(node_type):
|
|
214
|
+
return False
|
|
215
|
+
return True
|
|
216
|
+
|
|
217
|
+
def resolve_reference(self, op: mm.Lookup|mm.Aggregate) -> Optional[mm.Overload|list[mm.Relation]]:
|
|
218
|
+
# check if all dependencies required to resolve this reference are met
|
|
219
|
+
if not self.all_dependencies_resolved(op):
|
|
220
|
+
return None
|
|
221
|
+
|
|
222
|
+
relation = get_relation(op)
|
|
223
|
+
if bt.is_placeholder(relation):
|
|
224
|
+
# when replacing a placeholder, we may have multiple matches
|
|
225
|
+
matches = []
|
|
226
|
+
resolved_args = [self.resolve(arg) for arg in op.args]
|
|
227
|
+
for target in self.potential_targets.get(relation, []):
|
|
228
|
+
fields = get_relation_fields(target, relation.name)
|
|
229
|
+
if all(type_matches(arg, self.resolve(field))
|
|
230
|
+
for arg, field in zip(resolved_args, fields)):
|
|
231
|
+
matches.append(target)
|
|
232
|
+
return matches
|
|
233
|
+
|
|
234
|
+
elif relation.overloads:
|
|
235
|
+
# when resolving an overload for a concrete relation, we can only have one
|
|
236
|
+
resolved_args = [self.resolve(arg) for arg in op.args]
|
|
237
|
+
is_function = bt.is_function(relation)
|
|
238
|
+
for overload in relation.overloads:
|
|
239
|
+
# note that for functions we only consider input fields when matching
|
|
240
|
+
if all(type_matches(arg, field_type) or conversion_allowed(arg, field_type)
|
|
241
|
+
for arg, field, field_type in zip(resolved_args, relation.fields, overload.types)
|
|
242
|
+
if field.input or not is_function):
|
|
243
|
+
self.resolved_overload[op.id] = overload
|
|
244
|
+
return overload
|
|
245
|
+
return [] # no matches found
|
|
246
|
+
|
|
247
|
+
else:
|
|
248
|
+
# this is a relation with type vars or numbers that needs to be specialized
|
|
249
|
+
return [relation]
|
|
250
|
+
|
|
251
|
+
#--------------------------------------------------
|
|
252
|
+
# Propagation
|
|
253
|
+
#--------------------------------------------------
|
|
254
|
+
|
|
255
|
+
def propagate(self):
|
|
256
|
+
edges = self.edges
|
|
257
|
+
work_list = []
|
|
258
|
+
|
|
259
|
+
# start with the loaded roots + all literals + sources of edges without back edges
|
|
260
|
+
work_list.extend(self.loaded_roots)
|
|
261
|
+
for source in self.edges.keys():
|
|
262
|
+
if isinstance(source, (mm.Literal)) or not source in self.back_edges:
|
|
263
|
+
work_list.append(source)
|
|
264
|
+
|
|
265
|
+
# limit the number of iterations to avoid infinite loops
|
|
266
|
+
i = 0
|
|
267
|
+
max_iterations = 100 * len(self.edges)
|
|
268
|
+
|
|
269
|
+
# propagate types until we reach a fixed point
|
|
270
|
+
while work_list:
|
|
271
|
+
i += 1
|
|
272
|
+
if i > max_iterations:
|
|
273
|
+
err("Infinite Loop", "Infinite loop detected in the typer. Please, report this as a bug.")
|
|
274
|
+
break
|
|
275
|
+
|
|
276
|
+
node = work_list.pop(0)
|
|
277
|
+
next = None
|
|
278
|
+
if isinstance(node, mm.Field):
|
|
279
|
+
# this is a field loaded from a previous analysis, so all we need to do is
|
|
280
|
+
# propagate its type to its output edges
|
|
281
|
+
next = edges.get(node, [])
|
|
282
|
+
|
|
283
|
+
elif isinstance(node, (mm.Lookup, mm.Aggregate)):
|
|
284
|
+
# this is a lookup/aggregate that may be overloaded or a placeholder; try
|
|
285
|
+
# to resolve its reference, i.e. determine which specific relation or
|
|
286
|
+
# overload it refers to
|
|
287
|
+
found = self.resolve_reference(node)
|
|
288
|
+
if found is not None:
|
|
289
|
+
# if found is None it means that we need more info to resolve the
|
|
290
|
+
# reference; otherwise it was possible to resolve it (even if to no matches)
|
|
291
|
+
|
|
292
|
+
# keep the resolved args before propagation to see if they will change
|
|
293
|
+
resolved_args = [self.resolve(arg) for arg in node.args]
|
|
294
|
+
# propagate the reference resolution
|
|
295
|
+
self.propagate_reference(node, resolved_args, found)
|
|
296
|
+
if found:
|
|
297
|
+
# the next nodes to process are all the outgoing edges plus any arg that
|
|
298
|
+
# changed during propagation
|
|
299
|
+
next = OrderedSet()
|
|
300
|
+
next.update(edges.get(node, []))
|
|
301
|
+
next.update([arg for idx, arg in enumerate(node.args) if isinstance(arg, mm.Var) and not (self.resolve(arg) == resolved_args[idx])])
|
|
302
|
+
else:
|
|
303
|
+
# the reference is unresolved, so remove from the worklist the args
|
|
304
|
+
# because they depend on this being resolved
|
|
305
|
+
for arg in node.args:
|
|
306
|
+
if arg in work_list:
|
|
307
|
+
work_list.remove(arg)
|
|
308
|
+
else:
|
|
309
|
+
assert isinstance(node, (mm.Var, mm.Literal))
|
|
310
|
+
resolved = self.resolve(node)
|
|
311
|
+
# if we ended up with an invalid type, report it on a next edge (which is
|
|
312
|
+
# where the var or literal is going to be used)
|
|
313
|
+
if invalid_type(resolved):
|
|
314
|
+
self.invalid_type(edges.get(node, [node])[0], resolved)
|
|
315
|
+
|
|
316
|
+
# if the var is still abstract, add it to the work list to try again later
|
|
317
|
+
if isinstance(node, mm.Var) and bt.is_abstract(resolved):
|
|
318
|
+
# but if everything that is left are vars, we cannot make progress
|
|
319
|
+
if not all(isinstance(x, mm.Var) for x in work_list):
|
|
320
|
+
next = [node]
|
|
321
|
+
else:
|
|
322
|
+
# otherwise, check all outgoing edges
|
|
323
|
+
next = []
|
|
324
|
+
for out in edges.get(node, []):
|
|
325
|
+
if isinstance(out, mm.Update):
|
|
326
|
+
# this is an update to a field. We have to check that the type is
|
|
327
|
+
# valid for the field and if the field is abstract we can refine the
|
|
328
|
+
# type. But if the field is concrete we have to check that the type
|
|
329
|
+
# matche or can be converted.
|
|
330
|
+
is_population_lookup = isinstance(out.relation, mm.TypeNode)
|
|
331
|
+
for field in get_update_fields(node, out):
|
|
332
|
+
if not type_matches(resolved, field.type, accept_expected_super_types=is_population_lookup) and not conversion_allowed(resolved, field.type):
|
|
333
|
+
self.type_mismatch(out, field.type, resolved)
|
|
334
|
+
elif bt.is_abstract(field.type):
|
|
335
|
+
# if the field type changed, propagate further
|
|
336
|
+
if resolved != self.resolve(field):
|
|
337
|
+
next.append(field)
|
|
338
|
+
self.add_resolved_type(field, resolved)
|
|
339
|
+
else:
|
|
340
|
+
# this is an arg flowing into a task, so resolve the task next
|
|
341
|
+
next.append(out)
|
|
342
|
+
|
|
343
|
+
# add the new nodes to the work list
|
|
344
|
+
if next is not None:
|
|
345
|
+
for n in next:
|
|
346
|
+
if n not in work_list:
|
|
347
|
+
work_list.append(n)
|
|
348
|
+
|
|
349
|
+
|
|
350
|
+
def propagate_reference(self, task:mm.Lookup|mm.Aggregate, resolved_args:list[mm.Type], references:mm.Overload|list[mm.Relation]):
|
|
351
|
+
# TODO: distinguish between overloads and placeholders better when raising errors
|
|
352
|
+
if not references:
|
|
353
|
+
return self.unresolved_overload(task)
|
|
354
|
+
|
|
355
|
+
relation = get_relation(task)
|
|
356
|
+
|
|
357
|
+
if relation == b.core.cast:
|
|
358
|
+
# cast is special cased: we just propagate the type to the target
|
|
359
|
+
cast_type, target = task.args[0], task.args[2]
|
|
360
|
+
assert isinstance(cast_type, mm.Type)
|
|
361
|
+
assert isinstance(target, mm.Var)
|
|
362
|
+
self.add_resolved_type(target, cast_type)
|
|
363
|
+
return
|
|
364
|
+
|
|
365
|
+
if bt.is_placeholder(relation):
|
|
366
|
+
assert(references and isinstance(references, list))
|
|
367
|
+
# we've resolved the placeholder, so store that
|
|
368
|
+
self.resolved_placeholder[task.id] = references
|
|
369
|
+
|
|
370
|
+
# we need to determine the final types of our args by taking all the references
|
|
371
|
+
# and adding the type of their fields back to the args.
|
|
372
|
+
for reference in references:
|
|
373
|
+
resolved_fields = [self.resolve(f) for f in get_relation_fields(reference, relation.name)]
|
|
374
|
+
for field_type, arg_type, arg in zip(resolved_fields, resolved_args, task.args):
|
|
375
|
+
if bt.is_abstract(arg_type) and isinstance(arg, mm.Var):
|
|
376
|
+
self.add_resolved_type(arg, field_type)
|
|
377
|
+
else:
|
|
378
|
+
if isinstance(references, mm.Overload):
|
|
379
|
+
# we resolved an overload, use its types
|
|
380
|
+
types = list(references.types)
|
|
381
|
+
else:
|
|
382
|
+
# we resolved to a single concrete relation that contains type vars or needs
|
|
383
|
+
# number specialization, so use that relation's field types
|
|
384
|
+
types = list([self.resolve(f) for f in relation.fields])
|
|
385
|
+
|
|
386
|
+
# if our overload preserves types, we check to see if there's a preserved output
|
|
387
|
+
# type given the inputs and if so, shadow the field type with the preserved type.
|
|
388
|
+
# this is only attempted if all input types match the field types, i.e. no
|
|
389
|
+
# conversions are needed
|
|
390
|
+
resolved_fields = types
|
|
391
|
+
if bt.is_function(relation) and len(set(resolved_fields)) == 1 and not relation in self.NON_TYPE_PRESERVERS and\
|
|
392
|
+
all(type_matches(arg_type, field_type) for arg_type, field_type, field in zip(resolved_args, types, relation.fields) if field.input):
|
|
393
|
+
|
|
394
|
+
input_types = set([arg_type for field, arg_type
|
|
395
|
+
in zip(relation.fields, resolved_args) if field.input])
|
|
396
|
+
if out_type := self.try_preserve_type(input_types):
|
|
397
|
+
resolved_fields = [field_type if field.input else out_type
|
|
398
|
+
for field, field_type in zip(relation.fields, types)]
|
|
399
|
+
|
|
400
|
+
|
|
401
|
+
# eq is special cased because we don't want to specialize a number for it, as it
|
|
402
|
+
# can be just comparing numbers of different types.
|
|
403
|
+
if relation != b.core.eq and (b.core.Number in types or (b.core.TypeVar in types and any(bt.is_number(t) for t in resolved_args))):
|
|
404
|
+
# this relation contains generic numbers or typevars bound to numbers, so
|
|
405
|
+
# find which specific type of number to use given the arguments being passed
|
|
406
|
+
number, resolved_fields = self.specialize_number(relation, resolved_fields, resolved_args)
|
|
407
|
+
self.resolved_number[task.id] = number
|
|
408
|
+
# push the specialized number to the outputs
|
|
409
|
+
for field, field_type, arg in zip(relation.fields, resolved_fields, task.args):
|
|
410
|
+
if not field.input and isinstance(arg, mm.Var):
|
|
411
|
+
self.add_resolved_type(arg, field_type)
|
|
412
|
+
|
|
413
|
+
elif b.core.TypeVar in types:
|
|
414
|
+
# this relation contains type vars, so we have to make sure that all args
|
|
415
|
+
# bound to the same type var are consistent
|
|
416
|
+
|
|
417
|
+
# find which arg is bound to the type var and check that they are all consistent
|
|
418
|
+
typevar_type = None
|
|
419
|
+
for arg_type, field_type in zip(resolved_args, types):
|
|
420
|
+
if field_type == b.core.TypeVar:
|
|
421
|
+
if typevar_type is None:
|
|
422
|
+
typevar_type = arg_type
|
|
423
|
+
else:
|
|
424
|
+
typevar_type = merge_types(typevar_type, arg_type)
|
|
425
|
+
assert typevar_type is not None
|
|
426
|
+
|
|
427
|
+
# compute the final arg types by replacing type vars with the typevar_type
|
|
428
|
+
computed_arg_types = []
|
|
429
|
+
for arg_type, field_type in zip(resolved_args, types):
|
|
430
|
+
# check that the arg type matches the type var type
|
|
431
|
+
if not type_matches(typevar_type, arg_type) and not conversion_allowed(arg_type, typevar_type):
|
|
432
|
+
self.type_mismatch(task, typevar_type, arg_type)
|
|
433
|
+
if field_type == b.core.TypeVar:
|
|
434
|
+
computed_arg_types.append(typevar_type)
|
|
435
|
+
else:
|
|
436
|
+
computed_arg_types.append(arg_type)
|
|
437
|
+
|
|
438
|
+
# if no mismatches were found, propagate the computed arg types back to the args
|
|
439
|
+
if len(computed_arg_types) == len(task.args):
|
|
440
|
+
for computed_type, arg, arg_type in zip(computed_arg_types, task.args, resolved_args):
|
|
441
|
+
# TODO: we could allow for non-numeric/string literals because this means
|
|
442
|
+
# the literal is being used as a value type, but the backend emitters
|
|
443
|
+
# would have to deal with that.
|
|
444
|
+
if isinstance(arg, mm.Var) or (isinstance(arg, mm.Literal) and (bt.is_numeric(computed_type) or computed_type == b.core.String)):
|
|
445
|
+
self.add_resolved_type(arg, computed_type)
|
|
446
|
+
|
|
447
|
+
else:
|
|
448
|
+
# no typevar or number specialization shenanigans, just propagate field types to args
|
|
449
|
+
for field, field_type, arg, arg_type in zip(relation.fields, resolved_fields, task.args, resolved_args):
|
|
450
|
+
# add a resolved type if we learned more about the arg's type
|
|
451
|
+
if isinstance(arg, mm.Var) and (bt.is_abstract(arg_type) or bt.extends(field_type, arg_type)):
|
|
452
|
+
self.add_resolved_type(arg, field_type)
|
|
453
|
+
elif not type_matches(arg_type, field_type) and not conversion_allowed(arg_type, field_type):
|
|
454
|
+
self.type_mismatch(task, field_type, arg_type)
|
|
455
|
+
|
|
456
|
+
|
|
457
|
+
# we try to preserve types for relations that are functions (i.e. potentially multiple
|
|
458
|
+
# input but a single output) and where all types are the same. However, there are some
|
|
459
|
+
# exceptions to this rule, e.g. range() always returns int regardless of whether the
|
|
460
|
+
# input type is an int or a value type that extends int.
|
|
461
|
+
NON_TYPE_PRESERVERS = [
|
|
462
|
+
b.common.range
|
|
463
|
+
]
|
|
464
|
+
|
|
465
|
+
def try_preserve_type(self, types:set[mm.Type]) -> Optional[mm.Type]:
|
|
466
|
+
# we keep the input type as the output type if either all inputs are the exact same
|
|
467
|
+
# type or there's one nominal and its base primitive 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, accept_expected_super_types=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 specialize_number(self, op, field_types:list[mm.Type], arg_types:list[mm.Type]) -> Tuple[mm.NumberType, list[mm.Type]]:
|
|
488
|
+
"""
|
|
489
|
+
Find the number type to use for an overload that has Number in its field_types, and
|
|
490
|
+
which is being referred to with these arg_types.
|
|
491
|
+
|
|
492
|
+
Return a tuple where the first element is the specialized number type, and the second
|
|
493
|
+
element is a new list that contains the same types as field_types but with
|
|
494
|
+
Number replaced by this specialized number.
|
|
495
|
+
"""
|
|
496
|
+
# special case a few operators according to Snowflake's rules in
|
|
497
|
+
# https://docs.snowflake.com/en/sql-reference/operators-arithmetic#scale-and-precision-in-arithmetic-operations
|
|
498
|
+
if op == b.core.div:
|
|
499
|
+
# see https://docs.snowflake.com/en/sql-reference/operators-arithmetic#division
|
|
500
|
+
numerator, denominator = get_number_type(arg_types[0]), get_number_type(arg_types[1])
|
|
501
|
+
s = max(numerator.scale, min(numerator.scale + 6, 12))
|
|
502
|
+
number = mm.NumberType(name=f"Number(38,{s})", precision=38, scale=s, source=op.source, super_types=(b.core.Numeric,))
|
|
503
|
+
return number, [numerator, denominator, number]
|
|
504
|
+
elif op == b.core.mul:
|
|
505
|
+
# see https://docs.snowflake.com/en/sql-reference/operators-arithmetic#multiplication
|
|
506
|
+
t1, t2 = get_number_type(arg_types[0]), get_number_type(arg_types[1])
|
|
507
|
+
S1 = t1.scale
|
|
508
|
+
S2 = t2.scale
|
|
509
|
+
s = min(S1 + S2, max(S1, S2, 12))
|
|
510
|
+
number = mm.NumberType(name=f"Number(38,{s})", precision=38, scale=s, source=op.source, super_types=(b.core.Numeric,))
|
|
511
|
+
return number, [t1, t2, number]
|
|
512
|
+
elif op == b.aggregates.avg:
|
|
513
|
+
# TODO!! - implement proper avg specialization
|
|
514
|
+
pass
|
|
515
|
+
|
|
516
|
+
# fall back to the the current specialization policy, which is to select the number
|
|
517
|
+
# with largest scale and, if there multiple with the largest scale, the one with the
|
|
518
|
+
# largest precision. This is safe because when converting a number to the
|
|
519
|
+
# specialized number, we never truncate fractional digits (because we selected the
|
|
520
|
+
# largest scale) and, if the non-fractional digits are too large to fit the
|
|
521
|
+
# specialized number, we will have a runtime overflow, which should alert the user
|
|
522
|
+
# of the problem.
|
|
523
|
+
number = None
|
|
524
|
+
for arg_type in arg_types:
|
|
525
|
+
x = bt.get_number_supertype(arg_type)
|
|
526
|
+
if isinstance(x, mm.NumberType):
|
|
527
|
+
if number is None or x.scale > number.scale or (x.scale == number.scale and x.precision > number.precision):
|
|
528
|
+
number = x
|
|
529
|
+
assert(number is not None)
|
|
530
|
+
return number, [number if bt.is_number(t) else t for t in field_types]
|
|
531
|
+
|
|
532
|
+
|
|
533
|
+
#--------------------------------------------------
|
|
534
|
+
# Display
|
|
535
|
+
#--------------------------------------------------
|
|
536
|
+
|
|
537
|
+
# draw the network as a mermaid graph for the debugger
|
|
538
|
+
def to_mermaid(self, max_edges=500) -> str:
|
|
539
|
+
# add links for edges while collecting nodes
|
|
540
|
+
nodes = OrderedSet()
|
|
541
|
+
link_strs = []
|
|
542
|
+
# edges
|
|
543
|
+
for src, dsts in self.edges.items():
|
|
544
|
+
nodes.add(src)
|
|
545
|
+
for dst in dsts:
|
|
546
|
+
if len(link_strs) > max_edges:
|
|
547
|
+
break
|
|
548
|
+
nodes.add(dst)
|
|
549
|
+
link_strs.append(f"n{src.id} --> n{dst.id}")
|
|
550
|
+
if len(link_strs) > max_edges:
|
|
551
|
+
break
|
|
552
|
+
|
|
553
|
+
def type_span(t:mm.Type) -> str:
|
|
554
|
+
type_str = t.name if isinstance(t, mm.ScalarType) else str(t)
|
|
555
|
+
return f"<span style='color:cyan;'>{type_str.strip()}</span>"
|
|
556
|
+
|
|
557
|
+
def reference_span(rel:mm.Relation, arg_types:list[mm.Type]) -> str:
|
|
558
|
+
args = []
|
|
559
|
+
for field, arg_type in zip(rel.fields, arg_types):
|
|
560
|
+
field_type = self.resolve(field)
|
|
561
|
+
if not type_matches(arg_type, field_type) and not conversion_allowed(arg_type, field_type) and not bt.is_abstract(field_type):
|
|
562
|
+
args.append(f"<span style='color:yellow;'>{str(arg_type).strip()} -> {str(field_type).strip()}</span>")
|
|
563
|
+
elif isinstance(arg_type, mm.UnionType):
|
|
564
|
+
args.append(type_span(field_type))
|
|
565
|
+
else:
|
|
566
|
+
args.append(type_span(arg_type))
|
|
567
|
+
return f'{rel.name}({", ".join(args)})'
|
|
568
|
+
|
|
569
|
+
resolved = self.resolved_types
|
|
570
|
+
node_strs = []
|
|
571
|
+
for node in nodes:
|
|
572
|
+
klass = ""
|
|
573
|
+
if isinstance(node, mm.Var):
|
|
574
|
+
ir_type = resolved.get(node) or self.resolve(node)
|
|
575
|
+
type_str = type_span(ir_type)
|
|
576
|
+
label = f'(["{node.name}:{type_str}"])'
|
|
577
|
+
elif isinstance(node, mm.Literal):
|
|
578
|
+
ir_type = resolved.get(node) or self.resolve(node)
|
|
579
|
+
type_str = type_span(ir_type)
|
|
580
|
+
klass = ":::literal"
|
|
581
|
+
label = f'(["{node.value}: {type_str}"])'
|
|
582
|
+
elif isinstance(node, mm.Field):
|
|
583
|
+
ir_type = resolved.get(node) or self.resolve(node)
|
|
584
|
+
type_str = type_span(ir_type)
|
|
585
|
+
klass = ":::field"
|
|
586
|
+
rel = node._relation
|
|
587
|
+
if rel is not None:
|
|
588
|
+
rel = str(node._relation)
|
|
589
|
+
label = f'[/"{node.name}:{type_str}\nfrom {rel}"\\]'
|
|
590
|
+
else:
|
|
591
|
+
label = f'[/"{node.name}:\n{type_str}"\\]'
|
|
592
|
+
elif isinstance(node, (mm.Lookup, mm.Aggregate, mm.Update)):
|
|
593
|
+
arg_types = [self.resolve(arg) for arg in node.args]
|
|
594
|
+
if node.id in self.resolved_placeholder:
|
|
595
|
+
overloads = self.resolved_placeholder[node.id]
|
|
596
|
+
content = "<br/>".join([reference_span(o, arg_types) for o in overloads])
|
|
597
|
+
else:
|
|
598
|
+
content = reference_span(get_relation(node), arg_types)
|
|
599
|
+
if isinstance(node, mm.Update):
|
|
600
|
+
klass = ":::update"
|
|
601
|
+
label = f'{{{{"{content}"}}}}'
|
|
602
|
+
else:
|
|
603
|
+
klass = ":::reference"
|
|
604
|
+
label = f'[/"{content}"/]'
|
|
605
|
+
else:
|
|
606
|
+
raise NotImplementedError(f"Unknown node type: {type(node)}")
|
|
607
|
+
if self.has_errors(node):
|
|
608
|
+
klass = ":::error"
|
|
609
|
+
node_strs.append(f'n{node.id}{label}{klass}')
|
|
610
|
+
|
|
611
|
+
node_str = "\n ".join(node_strs)
|
|
612
|
+
link_str = "\n ".join(link_strs)
|
|
613
|
+
template = f"""
|
|
614
|
+
%%{{init: {{'theme':'dark', 'flowchart':{{'useMaxWidth':false, 'htmlLabels': true}}}}}}%%
|
|
615
|
+
flowchart TD
|
|
616
|
+
linkStyle default stroke:#666
|
|
617
|
+
classDef field fill:#245,stroke:#478
|
|
618
|
+
classDef update fill:#245,stroke:#478
|
|
619
|
+
classDef literal fill:#452,stroke:#784
|
|
620
|
+
classDef error fill:#624,stroke:#945,color:#f9a
|
|
621
|
+
classDef default stroke:#444,stroke-width:2px, font-size:12px
|
|
622
|
+
|
|
623
|
+
%% nodes
|
|
624
|
+
{node_str}
|
|
625
|
+
|
|
626
|
+
%% edges
|
|
627
|
+
{link_str}
|
|
628
|
+
"""
|
|
629
|
+
return template
|
|
630
|
+
|
|
631
|
+
#--------------------------------------------------
|
|
632
|
+
# Analyzer
|
|
633
|
+
#--------------------------------------------------
|
|
634
|
+
|
|
635
|
+
class Analyzer(Walker):
|
|
636
|
+
""" Walks the metamodel and builds the propagation network. """
|
|
637
|
+
|
|
638
|
+
def __init__(self, net:PropagationNetwork):
|
|
639
|
+
super().__init__()
|
|
640
|
+
self.net = net
|
|
641
|
+
|
|
642
|
+
def analyze(self, node: mm.Node):
|
|
643
|
+
self(node)
|
|
644
|
+
|
|
645
|
+
# TODO - ignoring requires for now because the typing of constraints seems incorrect
|
|
646
|
+
def enter_require(self, require: mm.Require):
|
|
647
|
+
return NO_WALK
|
|
648
|
+
|
|
649
|
+
def compute_potential_targets(self, relation: mm.Relation):
|
|
650
|
+
# register potential targets for placeholders
|
|
651
|
+
if bt.is_placeholder(relation):
|
|
652
|
+
self.net.potential_targets[relation] = get_potential_targets(self.net.model, relation)
|
|
653
|
+
|
|
654
|
+
#--------------------------------------------------
|
|
655
|
+
# Walk Update
|
|
656
|
+
#--------------------------------------------------
|
|
657
|
+
|
|
658
|
+
def update(self, node: mm.Update):
|
|
659
|
+
rel = node.relation
|
|
660
|
+
self.compute_potential_targets(rel)
|
|
661
|
+
|
|
662
|
+
# arg is flowing into a field
|
|
663
|
+
for arg, field in zip(node.args, rel.fields):
|
|
664
|
+
if isinstance(arg, (mm.Var, mm.Literal)):
|
|
665
|
+
self.net.add_edge(arg, node)
|
|
666
|
+
|
|
667
|
+
#--------------------------------------------------
|
|
668
|
+
# Walk Lookups + Aggregates
|
|
669
|
+
#--------------------------------------------------
|
|
670
|
+
|
|
671
|
+
def lookup(self, task: mm.Lookup):
|
|
672
|
+
self.compute_potential_targets(task.relation)
|
|
673
|
+
self.visit_rel_op(task)
|
|
674
|
+
|
|
675
|
+
def aggregate(self, task: mm.Aggregate):
|
|
676
|
+
self.visit_rel_op(task)
|
|
677
|
+
|
|
678
|
+
def visit_rel_op(self, task: mm.Lookup|mm.Aggregate):
|
|
679
|
+
relation = get_relation(task)
|
|
680
|
+
|
|
681
|
+
is_placeholder = bt.is_placeholder(relation)
|
|
682
|
+
for field, arg in zip(relation.fields, task.args):
|
|
683
|
+
if isinstance(arg, (mm.Var, mm.Literal)):
|
|
684
|
+
if field.input:
|
|
685
|
+
# we need to resolve all inputs before resolving the relation
|
|
686
|
+
self.net.add_edge(arg, task)
|
|
687
|
+
else:
|
|
688
|
+
# placeholders also need the output to be resolved
|
|
689
|
+
if is_placeholder:
|
|
690
|
+
self.net.add_edge(arg, task)
|
|
691
|
+
else:
|
|
692
|
+
# args bound to outputs can be resolved after
|
|
693
|
+
self.net.add_edge(task, arg)
|
|
694
|
+
# if the field is abstract, it needs to be resolved before we can
|
|
695
|
+
# resolve the task.
|
|
696
|
+
if bt.is_abstract(self.net.resolve(field)):
|
|
697
|
+
self.net.add_edge(field, task)
|
|
698
|
+
|
|
699
|
+
|
|
700
|
+
#--------------------------------------------------
|
|
701
|
+
# Replacer
|
|
702
|
+
#--------------------------------------------------
|
|
703
|
+
|
|
704
|
+
# Once we've pushed all the types through the network, we need to replace the types of
|
|
705
|
+
# fields and vars that we may have discovered. We also need to replace placeholder lookups
|
|
706
|
+
# with the chosen relations and do any conversions that are needed.
|
|
707
|
+
class Replacer(Rewriter):
|
|
708
|
+
def __init__(self, net:PropagationNetwork):
|
|
709
|
+
super().__init__()
|
|
710
|
+
self.net = net
|
|
711
|
+
|
|
712
|
+
def rewrite(self, model: T) -> T:
|
|
713
|
+
return self(model) # type: ignore
|
|
714
|
+
|
|
715
|
+
def logical(self, logical: mm.Logical):
|
|
716
|
+
if len(logical.body) == 0:
|
|
717
|
+
return logical
|
|
718
|
+
# inline logicals that are just there to group other nodes during rewrite
|
|
719
|
+
body = []
|
|
720
|
+
for child in logical.body:
|
|
721
|
+
if isinstance(child, mm.Logical) and not child.optional and not child.scope:
|
|
722
|
+
body.extend(child.body)
|
|
723
|
+
else:
|
|
724
|
+
body.append(child)
|
|
725
|
+
return logical.mut(body = tuple(body))
|
|
726
|
+
|
|
727
|
+
#--------------------------------------------------
|
|
728
|
+
# Rewriter handlers
|
|
729
|
+
#--------------------------------------------------
|
|
730
|
+
|
|
731
|
+
def field(self, node: mm.Field):
|
|
732
|
+
# TODO - this is only modifying the relation in the model, but then we have a new
|
|
733
|
+
# relation there, which is different than the object referenced by tasks.
|
|
734
|
+
if node in self.net.resolved_types:
|
|
735
|
+
return mm.Field(node.name, self.net.resolved_types[node], node.input, _relation = node._relation)
|
|
736
|
+
return node
|
|
737
|
+
|
|
738
|
+
def var(self, node: mm.Var):
|
|
739
|
+
if node in self.net.resolved_types:
|
|
740
|
+
return mm.Var(self.net.resolved_types[node], node.name)
|
|
741
|
+
return node
|
|
742
|
+
|
|
743
|
+
def literal(self, node: mm.Literal):
|
|
744
|
+
if node in self.net.resolved_types:
|
|
745
|
+
return mm.Literal(self.net.resolved_types[node], node.value)
|
|
746
|
+
return node
|
|
747
|
+
|
|
748
|
+
def update(self, node: mm.Update):
|
|
749
|
+
return self.convert_arguments(node, node.relation)
|
|
750
|
+
|
|
751
|
+
def lookup(self, node: mm.Lookup):
|
|
752
|
+
# We need to handle eq specially because its arguments can be converted symmetrically
|
|
753
|
+
if node.relation == b.core.eq:
|
|
754
|
+
return self.visit_eq_lookup(node)
|
|
755
|
+
|
|
756
|
+
args = types = None
|
|
757
|
+
if node.id in self.net.resolved_placeholder:
|
|
758
|
+
# placeholder resolved to multiple relations
|
|
759
|
+
resolved_relations = self.net.resolved_placeholder[node.id]
|
|
760
|
+
args = get_lookup_args(node, resolved_relations[0])
|
|
761
|
+
types = [f.type for f in resolved_relations[0].fields]
|
|
762
|
+
elif node.id in self.net.resolved_overload:
|
|
763
|
+
# overload resolved to a specific relation
|
|
764
|
+
resolved_relations = [node.relation]
|
|
765
|
+
types = self.net.resolved_overload[node.id].types
|
|
766
|
+
else:
|
|
767
|
+
# single relation
|
|
768
|
+
resolved_relations = [node.relation]
|
|
769
|
+
|
|
770
|
+
if len(resolved_relations) == 1:
|
|
771
|
+
# single relation, just convert arguments
|
|
772
|
+
x = self.convert_arguments(node, resolved_relations[0], args, types)
|
|
773
|
+
if isinstance(x, mm.Logical) and len(x.body) == 1:
|
|
774
|
+
return x.body[0]
|
|
775
|
+
else:
|
|
776
|
+
return x
|
|
777
|
+
|
|
778
|
+
# multiple relations, create a union
|
|
779
|
+
branches:list = []
|
|
780
|
+
for target in resolved_relations:
|
|
781
|
+
args = get_lookup_args(node, target)
|
|
782
|
+
types = [f.type for f in get_relation_fields(resolved_relations[0], node.relation.name)]
|
|
783
|
+
# adding this logical to avoid issues in the old backend
|
|
784
|
+
branches.append(mm.Logical((self.convert_arguments(node, target, args, types=types, force_copy=True),)))
|
|
785
|
+
return mm.Union(tuple(branches))
|
|
786
|
+
|
|
787
|
+
def convert_arguments(self, node: mm.Lookup|mm.Update, relation: mm.Relation, args: Iterable[mm.Value]|None=None, types: Iterable[mm.Type]|None=None, force_copy=False) -> mm.Logical|mm.Lookup|mm.Update:
|
|
788
|
+
""" This node was resolved to target this relation using these args, which should
|
|
789
|
+
have these types. Convert any arguments as needed and return a new node with the
|
|
790
|
+
proper relation and converted args. If multiple conversions are needed, return a
|
|
791
|
+
logical that contains all the conversion tasks plus the final node. """
|
|
792
|
+
args = args or node.args
|
|
793
|
+
types = types or [self.net.resolve(f) for f in relation.fields]
|
|
794
|
+
number_type = self.net.resolved_number.get(node.id)
|
|
795
|
+
is_function = bt.is_function(relation)
|
|
796
|
+
tasks = []
|
|
797
|
+
final_args = []
|
|
798
|
+
for arg, field, field_type in zip(args, relation.fields, types):
|
|
799
|
+
if isinstance(arg, (mm.Var, mm.Literal)) and (not is_function or field.input):
|
|
800
|
+
arg_type = to_type(arg)
|
|
801
|
+
if number_type and bt.is_number(arg_type) and not arg_type == b.core.ScaledNumber:
|
|
802
|
+
field_type = number_type
|
|
803
|
+
# the typer previously made sure that this should be valid so a type mismatch
|
|
804
|
+
# means we need to convert
|
|
805
|
+
if not type_matches(arg_type, field_type):
|
|
806
|
+
final_args.append(convert(arg, field_type, tasks))
|
|
807
|
+
else:
|
|
808
|
+
final_args.append(arg)
|
|
809
|
+
else:
|
|
810
|
+
final_args.append(arg)
|
|
811
|
+
# add the original node with the proper target relation and converted args;
|
|
812
|
+
# here we want to use mut because we keep information about nodes based on their ids
|
|
813
|
+
# (e.g. we store resolved types based on ids), so we want to keep the same id. But
|
|
814
|
+
# when a lookup is being converted as part of a union (i.e. multiple targets for a
|
|
815
|
+
# placeholder), we need to create nodes with new ids to avoid conflicts.
|
|
816
|
+
if force_copy:
|
|
817
|
+
tasks.append(node.replace(relation = relation, args = tuple(final_args)))
|
|
818
|
+
else:
|
|
819
|
+
tasks.append(node.mut(relation = relation, args = tuple(final_args)))
|
|
820
|
+
# if we need conversion tasks, wrap in a logical
|
|
821
|
+
if len(tasks) == 1:
|
|
822
|
+
return tasks[0]
|
|
823
|
+
return mm.Logical(tuple(tasks))
|
|
824
|
+
|
|
825
|
+
def visit_eq_lookup(self, node: mm.Lookup):
|
|
826
|
+
(left, right) = node.args
|
|
827
|
+
left_type = to_type(left)
|
|
828
|
+
right_type = to_type(right)
|
|
829
|
+
|
|
830
|
+
if type_matches(left_type, right_type):
|
|
831
|
+
return node
|
|
832
|
+
|
|
833
|
+
assert isinstance(left, (mm.Var, mm.Literal)) and isinstance(right, (mm.Var, mm.Literal))
|
|
834
|
+
final_args = []
|
|
835
|
+
tasks = []
|
|
836
|
+
if conversion_allowed(left_type, right_type):
|
|
837
|
+
final_args = [convert(left, right_type, tasks), right]
|
|
838
|
+
elif conversion_allowed(right_type, left_type):
|
|
839
|
+
final_args = [left, convert(right, left_type, tasks)]
|
|
840
|
+
else:
|
|
841
|
+
# this type mismatch was reported during propagation, so just return the node
|
|
842
|
+
return node
|
|
843
|
+
|
|
844
|
+
tasks.append(mm.Lookup(b.core.eq, tuple(final_args)))
|
|
845
|
+
return mm.Logical(tuple(tasks))
|
|
846
|
+
|
|
847
|
+
#--------------------------------------------------
|
|
848
|
+
# Helpers
|
|
849
|
+
#--------------------------------------------------
|
|
850
|
+
|
|
851
|
+
def get_relation(node: mm.Lookup|mm.Update|mm.Aggregate) -> mm.Relation:
|
|
852
|
+
if isinstance(node, mm.Aggregate):
|
|
853
|
+
return node.aggregation
|
|
854
|
+
return node.relation
|
|
855
|
+
|
|
856
|
+
def get_name(type: mm.Type) -> str:
|
|
857
|
+
if isinstance(type, mm.ScalarType):
|
|
858
|
+
return type.name
|
|
859
|
+
elif isinstance(type, mm.UnionType):
|
|
860
|
+
return '|'.join([get_name(t) for t in type.types])
|
|
861
|
+
elif isinstance(type, mm.ListType):
|
|
862
|
+
return f'List[{get_name(type.element_type)}]'
|
|
863
|
+
elif isinstance(type, mm.TupleType):
|
|
864
|
+
return f'Tuple[{", ".join([get_name(t) for t in type.element_types])}]'
|
|
865
|
+
else:
|
|
866
|
+
raise TypeError(f"Unknown type: {type}")
|
|
867
|
+
|
|
868
|
+
#--------------------------------------------------
|
|
869
|
+
# Type and Relation helpers
|
|
870
|
+
#--------------------------------------------------
|
|
871
|
+
|
|
872
|
+
def get_update_fields(arg, update: mm.Update) -> Iterable[mm.Field]:
|
|
873
|
+
""" Get the fields of the relation being updated by this arg. Note that an arg can be
|
|
874
|
+
bound to multiple fields at the same time. """
|
|
875
|
+
if arg in update.args:
|
|
876
|
+
for x, field in zip(update.args, update.relation.fields):
|
|
877
|
+
if arg == x:
|
|
878
|
+
yield field
|
|
879
|
+
return []
|
|
880
|
+
|
|
881
|
+
def get_relation_fields(relation: mm.Relation, name: str) -> Iterable[mm.Field]:
|
|
882
|
+
""" Get the fields of this relation, potentially reordered to match the reading with the given name."""
|
|
883
|
+
if name == relation.name:
|
|
884
|
+
return relation.fields
|
|
885
|
+
for reading in relation.readings:
|
|
886
|
+
if reading.name == name:
|
|
887
|
+
# reorder the fields to match the correct ordering
|
|
888
|
+
fields = []
|
|
889
|
+
for idx in reading.field_order:
|
|
890
|
+
fields.append(relation.fields[idx])
|
|
891
|
+
return fields
|
|
892
|
+
return []
|
|
893
|
+
|
|
894
|
+
def get_lookup_args(node: mm.Lookup, target: mm.Relation):
|
|
895
|
+
""" Get the args of this lookup, potentially reordered to match the reading with the given name."""
|
|
896
|
+
for reading in target.readings:
|
|
897
|
+
if reading.name == node.relation.name:
|
|
898
|
+
# reorder the args to match the correct ordering
|
|
899
|
+
args = []
|
|
900
|
+
for idx in reading.field_order:
|
|
901
|
+
args.append(node.args[idx])
|
|
902
|
+
return args
|
|
903
|
+
return node.args
|
|
904
|
+
|
|
905
|
+
def get_number_type(t: mm.Type) -> mm.NumberType:
|
|
906
|
+
# Get a number type from the given type, if it is Number return the default number
|
|
907
|
+
x = bt.get_number_supertype(t)
|
|
908
|
+
if isinstance(x, mm.NumberType):
|
|
909
|
+
return x
|
|
910
|
+
return b.core.DefaultNumber
|
|
911
|
+
|
|
912
|
+
def is_potential_target(placeholder: mm.Relation, target: mm.Relation) -> bool:
|
|
913
|
+
""" Whether this target is matches the placeholder signature and, thus, can be a potential target. """
|
|
914
|
+
if placeholder != target and len(placeholder.fields) == len(target.fields) and not bt.is_placeholder(target):
|
|
915
|
+
return placeholder.name == target.name or any(placeholder.name == reading.name for reading in target.readings)
|
|
916
|
+
return False
|
|
917
|
+
|
|
918
|
+
def get_potential_targets(model: mm.Model, placeholder: mm.Relation) -> list[mm.Relation]:
|
|
919
|
+
""" Get all potential target relations in the model that match the placeholder signature. """
|
|
920
|
+
return list(filter(lambda r: is_potential_target(placeholder, r), model.relations))
|
|
921
|
+
|
|
922
|
+
def to_type(value: mm.Value|mm.Field|mm.Literal) -> mm.Type:
|
|
923
|
+
if isinstance(value, (mm.Var, mm.Literal)):
|
|
924
|
+
return value.type
|
|
925
|
+
|
|
926
|
+
if isinstance(value, mm.Type):
|
|
927
|
+
return b.core.Type
|
|
928
|
+
|
|
929
|
+
if isinstance(value, mm.Field):
|
|
930
|
+
return b.core.Field
|
|
931
|
+
|
|
932
|
+
if isinstance(value, tuple):
|
|
933
|
+
return mm.TupleType(element_types=tuple(to_type(v) for v in value))
|
|
934
|
+
|
|
935
|
+
raise TypeError(f"Cannot determine IR type for value: {value} of type {type(value).__name__}")
|
|
936
|
+
|
|
937
|
+
def convert(value: mm.Var|mm.Literal, to_type: mm.Type, tasks: list[mm.Task]) -> mm.Value:
|
|
938
|
+
# if the arg is a literal, we can just change its type
|
|
939
|
+
# TODO - we may want to check that the value is actually convertible
|
|
940
|
+
if isinstance(value, mm.Literal):
|
|
941
|
+
return mm.Literal(to_type, value.value)
|
|
942
|
+
|
|
943
|
+
# otherise we need to add a cast
|
|
944
|
+
name = sanitize(value.name + "_" + get_name(to_type))
|
|
945
|
+
to_type_base = bt.get_primitive_supertype(to_type) or to_type
|
|
946
|
+
new_value = mm.Var(to_type_base, name)
|
|
947
|
+
tasks.append(mm.Lookup(b.core.cast, (to_type_base, value, new_value)))
|
|
948
|
+
return new_value
|
|
949
|
+
|
|
950
|
+
def conversion_allowed(from_type: mm.Type, to_type: mm.Type) -> bool:
|
|
951
|
+
# value type conversion is allowed only if the value types are related by inheritance
|
|
952
|
+
if bt.is_value_type(from_type) and bt.is_value_type(to_type) and not bt.extends(from_type, to_type):
|
|
953
|
+
return False
|
|
954
|
+
# value type conversion is allowed
|
|
955
|
+
x = bt.get_primitive_supertype(from_type)
|
|
956
|
+
y = bt.get_primitive_supertype(to_type)
|
|
957
|
+
if x and y and (x != from_type or y != to_type) and conversion_allowed(x, y):
|
|
958
|
+
return True
|
|
959
|
+
|
|
960
|
+
# numbers can be converted to floats
|
|
961
|
+
if bt.is_numeric(from_type) and to_type == b.core.Float:
|
|
962
|
+
return True
|
|
963
|
+
|
|
964
|
+
# a number can be converted to another number of larger scale
|
|
965
|
+
if isinstance(from_type, mm.NumberType) and isinstance(to_type, mm.NumberType):
|
|
966
|
+
if to_type.scale >= from_type.scale:
|
|
967
|
+
return True
|
|
968
|
+
|
|
969
|
+
if from_type == b.core.Number and isinstance(to_type, mm.NumberType):
|
|
970
|
+
return True
|
|
971
|
+
|
|
972
|
+
return False
|
|
973
|
+
|
|
974
|
+
def type_matches(actual: mm.Type, expected: mm.Type, accept_expected_super_types=False) -> bool:
|
|
975
|
+
"""
|
|
976
|
+
True iff we can use a value of the actual type when expecting the expected type, without
|
|
977
|
+
conversions.
|
|
978
|
+
|
|
979
|
+
Any super-type of `actual` can match `expected`. For example if we expect a `Person`, we
|
|
980
|
+
can use an `Employee` if `Employee < Person`.
|
|
981
|
+
|
|
982
|
+
In general, the other way around is not true: if we expect an `Employee` we cannot use a
|
|
983
|
+
`Person` instead.
|
|
984
|
+
|
|
985
|
+
However, when the relation is a Type (previsously known as "population relations"), it
|
|
986
|
+
is valid to provide sub-types of the expected type. For example, `Employee(Person)` is
|
|
987
|
+
a valid way to check that `Person` is an `Employee` on a `Lookup`, or to assert that a
|
|
988
|
+
particular `Person` is an `Employee` on an `Update`.
|
|
989
|
+
"""
|
|
990
|
+
# exact match
|
|
991
|
+
if actual == expected:
|
|
992
|
+
return True
|
|
993
|
+
|
|
994
|
+
# any matches anything
|
|
995
|
+
if actual == b.core.Any or expected == b.core.Any:
|
|
996
|
+
return True
|
|
997
|
+
|
|
998
|
+
# type vars match anything
|
|
999
|
+
if expected == b.core.TypeVar:
|
|
1000
|
+
return True
|
|
1001
|
+
|
|
1002
|
+
# if an entity type var or any entity is expected, it matches any actual entity type
|
|
1003
|
+
if (expected == b.core.EntityTypeVar or bt.extends(expected, b.core.AnyEntity)) and not bt.is_primitive(actual):
|
|
1004
|
+
return True
|
|
1005
|
+
|
|
1006
|
+
# the abstract Number type and the number type variable match any number type
|
|
1007
|
+
if (expected == b.core.Number) and bt.is_number(actual):
|
|
1008
|
+
return True
|
|
1009
|
+
|
|
1010
|
+
if (expected == b.core.Numeric) and bt.is_numeric(actual):
|
|
1011
|
+
return True
|
|
1012
|
+
|
|
1013
|
+
# different value types never match
|
|
1014
|
+
if bt.is_value_type(actual) and bt.is_value_type(expected) and not bt.extends(actual, expected):
|
|
1015
|
+
return False
|
|
1016
|
+
|
|
1017
|
+
# if actual is scalar, any of its parents may match the expected type
|
|
1018
|
+
if isinstance(actual, mm.ScalarType) and any([type_matches(parent, expected) for parent in actual.super_types]):
|
|
1019
|
+
return True
|
|
1020
|
+
|
|
1021
|
+
# 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
|
|
1022
|
+
if (accept_expected_super_types or bt.is_value_type(expected)) and isinstance(expected, mm.ScalarType) and any([type_matches(actual, parent, accept_expected_super_types) for parent in expected.super_types]):
|
|
1023
|
+
return True
|
|
1024
|
+
|
|
1025
|
+
# if we expect a union, the actual can match any of its types
|
|
1026
|
+
if isinstance(expected, mm.UnionType):
|
|
1027
|
+
for t in expected.types:
|
|
1028
|
+
if type_matches(t, actual, accept_expected_super_types):
|
|
1029
|
+
return True
|
|
1030
|
+
|
|
1031
|
+
# if actual is a union, every one of its types must match the expected type
|
|
1032
|
+
# if isinstance(actual, mm.UnionType):
|
|
1033
|
+
# for t in actual.types:
|
|
1034
|
+
# if not type_matches(t, expected, accept_expected_super_types):
|
|
1035
|
+
# return False
|
|
1036
|
+
# return True
|
|
1037
|
+
# TODO - we have to distinguish between when we are checking that a specific arg matches
|
|
1038
|
+
# a relation vs when we are selecting relations for the placeholders; then we have to
|
|
1039
|
+
# decide between the above and this.
|
|
1040
|
+
if isinstance(actual, mm.UnionType):
|
|
1041
|
+
for t in actual.types:
|
|
1042
|
+
if type_matches(t, expected, accept_expected_super_types):
|
|
1043
|
+
return True
|
|
1044
|
+
|
|
1045
|
+
# a list type matches if their element types match
|
|
1046
|
+
if isinstance(actual, (mm.ListType, mm.TupleType)) and isinstance(expected, mm.ListType):
|
|
1047
|
+
if isinstance(actual, mm.TupleType):
|
|
1048
|
+
for et in actual.element_types:
|
|
1049
|
+
if not type_matches(et, expected.element_type):
|
|
1050
|
+
return False
|
|
1051
|
+
return True
|
|
1052
|
+
return type_matches(actual.element_type, expected.element_type)
|
|
1053
|
+
|
|
1054
|
+
# a tuple types match if any of all their types match
|
|
1055
|
+
if isinstance(actual, mm.TupleType) and isinstance(expected, mm.TupleType):
|
|
1056
|
+
return all([type_matches(ae, ee) for ae, ee in zip(actual.element_types, expected.element_types)])
|
|
1057
|
+
|
|
1058
|
+
# accept tuples with a single element type to match a list with that type
|
|
1059
|
+
if isinstance(actual, mm.TupleType) and isinstance(expected, mm.ListType):
|
|
1060
|
+
return type_matches(actual.element_types[0], expected.element_type)
|
|
1061
|
+
|
|
1062
|
+
# otherwise no match
|
|
1063
|
+
return False
|
|
1064
|
+
|
|
1065
|
+
def merge_numeric_types(type1: mm.Type, type2: mm.Type) -> Optional[mm.Type]:
|
|
1066
|
+
# if one of them is the abstract Number type, pick the other
|
|
1067
|
+
if type1 == b.core.Number and isinstance(type2, mm.NumberType):
|
|
1068
|
+
return type2
|
|
1069
|
+
if type2 == b.core.Number and isinstance(type1, mm.NumberType):
|
|
1070
|
+
return type1
|
|
1071
|
+
|
|
1072
|
+
# if both are number types, pick the one with larger scale/precision
|
|
1073
|
+
if isinstance(type1, mm.NumberType) and isinstance(type2, mm.NumberType):
|
|
1074
|
+
if type1.scale > type2.scale or (type1.scale == type2.scale and type1.precision > type2.precision):
|
|
1075
|
+
return type1
|
|
1076
|
+
else:
|
|
1077
|
+
return type2
|
|
1078
|
+
|
|
1079
|
+
# if we are overriding a number with a float, pick float
|
|
1080
|
+
if isinstance(type1, mm.NumberType) and type2 == b.core.Float:
|
|
1081
|
+
return type2
|
|
1082
|
+
if isinstance(type2, mm.NumberType) and type1 == b.core.Float:
|
|
1083
|
+
return type1
|
|
1084
|
+
|
|
1085
|
+
return None
|
|
1086
|
+
|
|
1087
|
+
def merge_types(type1: mm.Type, type2: mm.Type) -> mm.Type:
|
|
1088
|
+
if type1 == type2:
|
|
1089
|
+
return type1
|
|
1090
|
+
if bt.is_type_var(type1):
|
|
1091
|
+
return type2
|
|
1092
|
+
if bt.is_type_var(type2):
|
|
1093
|
+
return type1
|
|
1094
|
+
|
|
1095
|
+
types_to_process = [type1, type2]
|
|
1096
|
+
|
|
1097
|
+
numeric_merge = merge_numeric_types(type1, type2)
|
|
1098
|
+
if numeric_merge is not None:
|
|
1099
|
+
return numeric_merge
|
|
1100
|
+
|
|
1101
|
+
# if one extends the other, pick the most specific one
|
|
1102
|
+
if bt.extends(type1, type2):
|
|
1103
|
+
return type1
|
|
1104
|
+
if bt.extends(type2, type1):
|
|
1105
|
+
return type2
|
|
1106
|
+
|
|
1107
|
+
# give precedence to nominal types (e.g. merging USD(decimal) with decimal gives USD(decimal))
|
|
1108
|
+
base_primitive_type1 = bt.get_primitive_supertype(type1)
|
|
1109
|
+
base_primitive_type2 = bt.get_primitive_supertype(type2)
|
|
1110
|
+
if base_primitive_type1 == base_primitive_type2:
|
|
1111
|
+
if bt.is_primitive(type1):
|
|
1112
|
+
return type2
|
|
1113
|
+
elif bt.is_primitive(type2):
|
|
1114
|
+
return type1
|
|
1115
|
+
|
|
1116
|
+
if base_primitive_type1 and base_primitive_type2:
|
|
1117
|
+
numeric_merge = merge_numeric_types(base_primitive_type1, base_primitive_type2)
|
|
1118
|
+
if numeric_merge == base_primitive_type1:
|
|
1119
|
+
return type1
|
|
1120
|
+
elif numeric_merge == base_primitive_type2:
|
|
1121
|
+
return type2
|
|
1122
|
+
|
|
1123
|
+
combined = OrderedSet()
|
|
1124
|
+
# Iterative flattening of union types
|
|
1125
|
+
while types_to_process:
|
|
1126
|
+
t = types_to_process.pop()
|
|
1127
|
+
if isinstance(t, mm.UnionType):
|
|
1128
|
+
types_to_process.extend(t.types)
|
|
1129
|
+
else:
|
|
1130
|
+
combined.add(t)
|
|
1131
|
+
|
|
1132
|
+
# If we have multiple types and Any or AnyEntity is one of them, remove Any
|
|
1133
|
+
if len(combined) > 1:
|
|
1134
|
+
if b.core.Any in combined:
|
|
1135
|
+
combined.remove(b.core.Any)
|
|
1136
|
+
if b.core.AnyEntity in combined:
|
|
1137
|
+
combined.remove(b.core.AnyEntity)
|
|
1138
|
+
|
|
1139
|
+
# If we still have multiple types, make sure supertypes are removed to keep only the
|
|
1140
|
+
# most specific types
|
|
1141
|
+
if len(combined) > 1:
|
|
1142
|
+
to_remove = set()
|
|
1143
|
+
for t1 in combined:
|
|
1144
|
+
for t2 in combined:
|
|
1145
|
+
if t1 != t2 and bt.extends(t1, t2):
|
|
1146
|
+
to_remove.add(t2)
|
|
1147
|
+
for r in to_remove:
|
|
1148
|
+
combined.remove(r)
|
|
1149
|
+
|
|
1150
|
+
# Return single type or create a union
|
|
1151
|
+
return next(iter(combined)) if len(combined) == 1 else mm.UnionType(types=tuple(combined))
|
|
1152
|
+
|
|
1153
|
+
def invalid_type(type:mm.Type) -> bool:
|
|
1154
|
+
if isinstance(type, mm.UnionType):
|
|
1155
|
+
# if there are multiple primitives, or a primitive and a non-primitive
|
|
1156
|
+
# then we have an invalid type
|
|
1157
|
+
if len(type.types) > 1:
|
|
1158
|
+
return any([bt.is_primitive(t) for t in type.types])
|
|
1159
|
+
return False
|
|
1160
|
+
|
|
1161
|
+
|
|
1162
|
+
#--------------------------------------------------
|
|
1163
|
+
# Type Errors
|
|
1164
|
+
#--------------------------------------------------
|
|
1165
|
+
|
|
1166
|
+
@dataclass
|
|
1167
|
+
class TyperError():
|
|
1168
|
+
node: mm.Node
|
|
1169
|
+
|
|
1170
|
+
def report(self):
|
|
1171
|
+
err(self.name(), self.message(), self.parts())
|
|
1172
|
+
|
|
1173
|
+
def name(self) -> str:
|
|
1174
|
+
return type(self).__name__
|
|
1175
|
+
|
|
1176
|
+
def message(self) -> str:
|
|
1177
|
+
raise NotImplementedError()
|
|
1178
|
+
|
|
1179
|
+
def parts(self) -> list[Part]:
|
|
1180
|
+
if self.node.source is None:
|
|
1181
|
+
return [str(self.node)]
|
|
1182
|
+
return [str(self.node), Source(self.node.source)]
|
|
1183
|
+
|
|
1184
|
+
@dataclass
|
|
1185
|
+
class TypeMismatch(TyperError):
|
|
1186
|
+
expected: mm.Type
|
|
1187
|
+
actual: mm.Type
|
|
1188
|
+
|
|
1189
|
+
def message(self) -> str:
|
|
1190
|
+
return f"Expected {get_name(self.expected)}, got {get_name(self.actual)}"
|
|
1191
|
+
|
|
1192
|
+
@dataclass
|
|
1193
|
+
class InvalidType(TyperError):
|
|
1194
|
+
type: mm.Type
|
|
1195
|
+
|
|
1196
|
+
def message(self) -> str:
|
|
1197
|
+
return f"Incompatible types infered: {get_name(self.type)}"
|
|
1198
|
+
|
|
1199
|
+
@dataclass
|
|
1200
|
+
class UnresolvedOverload(TyperError):
|
|
1201
|
+
arg_types: list[mm.Type]
|
|
1202
|
+
|
|
1203
|
+
def message(self) -> str:
|
|
1204
|
+
assert isinstance(self.node, (mm.Lookup, mm.Update, mm.Aggregate))
|
|
1205
|
+
rel = get_relation(self.node)
|
|
1206
|
+
types = ', '.join([get_name(t) for t in self.arg_types])
|
|
1207
|
+
return f"Unresolved overload: {rel.name}({types})"
|
|
1208
|
+
|
|
1209
|
+
@dataclass
|
|
1210
|
+
class UnresolvedType(TyperError):
|
|
1211
|
+
|
|
1212
|
+
def message(self) -> str:
|
|
1213
|
+
return "Unable to determine concrete type."
|