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,1395 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
from collections import defaultdict
|
|
3
|
+
from dataclasses import dataclass
|
|
4
|
+
import dataclasses
|
|
5
|
+
import datetime
|
|
6
|
+
from decimal import Decimal as PyDecimal
|
|
7
|
+
from typing import Optional, Union, Tuple
|
|
8
|
+
from v0.relationalai import debugging
|
|
9
|
+
from v0.relationalai.semantics.metamodel import builtins, helpers, ir, types, visitor, compiler, factory as f
|
|
10
|
+
from v0.relationalai.semantics.metamodel.util import OrderedSet, ordered_set
|
|
11
|
+
import rich
|
|
12
|
+
import sys
|
|
13
|
+
|
|
14
|
+
#--------------------------------------------------
|
|
15
|
+
# Helpers
|
|
16
|
+
#--------------------------------------------------
|
|
17
|
+
|
|
18
|
+
def to_relation(node:ir.Lookup|ir.Update|ir.Aggregate) -> ir.Relation:
|
|
19
|
+
""" Get the relation being referred to by this node. """
|
|
20
|
+
if isinstance(node, ir.Aggregate):
|
|
21
|
+
return node.aggregation
|
|
22
|
+
return node.relation
|
|
23
|
+
|
|
24
|
+
def to_name(type:ir.Type) -> str:
|
|
25
|
+
""" Get a human-readable name representing a type. """
|
|
26
|
+
if isinstance(type, ir.DecimalType):
|
|
27
|
+
return f"Decimal({type.precision},{type.scale})"
|
|
28
|
+
elif isinstance(type, ir.ScalarType):
|
|
29
|
+
return type.name
|
|
30
|
+
elif isinstance(type, ir.UnionType):
|
|
31
|
+
return '|'.join([to_name(t) for t in type.types])
|
|
32
|
+
elif isinstance(type, ir.ListType):
|
|
33
|
+
return f"[{to_name(type.element_type)}]"
|
|
34
|
+
else:
|
|
35
|
+
raise TypeError(f"Unknown type: {type}")
|
|
36
|
+
|
|
37
|
+
def check_int64(value: int):
|
|
38
|
+
INT64_MIN = -(2 ** 63)
|
|
39
|
+
INT64_MAX = 2 ** 63 - 1
|
|
40
|
+
|
|
41
|
+
if not (INT64_MIN <= value <= INT64_MAX):
|
|
42
|
+
raise OverflowError(f"Value '{value}' does not fit in Int64.")
|
|
43
|
+
|
|
44
|
+
#--------------------------------------------------
|
|
45
|
+
# Constants
|
|
46
|
+
#--------------------------------------------------
|
|
47
|
+
|
|
48
|
+
# map potential type conversions to their cost
|
|
49
|
+
# The differences in the costs of converting from one type to another should be
|
|
50
|
+
# smaller than the cost of a literal conversion.
|
|
51
|
+
# Otherwise, we'll introduce spurious conversions of say Int->Decimal64 plus a cheap literal conversion
|
|
52
|
+
# Float->Decimal64, rather than a single literal conversion Int->Float.
|
|
53
|
+
# Use a large enough number here so that if all arguments are literals, it's cheaper to convert
|
|
54
|
+
# the literals (cost 10) rather than not convert some literals and convert a non-literal instead.
|
|
55
|
+
# For instance, for `y=1+x` is should be cheaper to convert 1 to float and not convert x and y, than it would be
|
|
56
|
+
# convert both x and y to integer.
|
|
57
|
+
# We do however, want to prefer converting to number types lower in the lattice.
|
|
58
|
+
# So we make the cost depend only on the result type.
|
|
59
|
+
# Making all the costs the same would introduce spurious unions of conversions.
|
|
60
|
+
|
|
61
|
+
CONVERSION_LATTICE:dict[tuple[ir.Type, ir.Type], int] = {
|
|
62
|
+
# Make the cost depend only on the result type.
|
|
63
|
+
(types.Int64, types.Int128): 1000,
|
|
64
|
+
(types.Int64, types.GenericDecimal): 1002,
|
|
65
|
+
(types.Int64, types.Float): 1004,
|
|
66
|
+
(types.Int128, types.GenericDecimal): 1002,
|
|
67
|
+
(types.GenericDecimal, types.Float): 1003,
|
|
68
|
+
(types.Int128, types.Float): 1004,
|
|
69
|
+
# Int -> Hash (UInt128)
|
|
70
|
+
(types.Int64, types.Hash): 1001,
|
|
71
|
+
(types.Int128, types.Hash): 1001,
|
|
72
|
+
(types.Int128, types.UInt128): 1001,
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
def conversion_lattice_cost(from_type, to_type):
|
|
76
|
+
"""
|
|
77
|
+
Lookup the conversion cost in the conversion lattice to convert from_type to to_type.
|
|
78
|
+
Return inf is the conversion is not allowed.
|
|
79
|
+
"""
|
|
80
|
+
# the conversion lattice only has generic decimals as placeholders
|
|
81
|
+
if isinstance(from_type, ir.DecimalType):
|
|
82
|
+
from_type = types.GenericDecimal
|
|
83
|
+
if isinstance(to_type, ir.DecimalType):
|
|
84
|
+
to_type = types.GenericDecimal
|
|
85
|
+
|
|
86
|
+
if (from_type, to_type) in CONVERSION_LATTICE:
|
|
87
|
+
return CONVERSION_LATTICE[(from_type, to_type)]
|
|
88
|
+
return float("inf")
|
|
89
|
+
|
|
90
|
+
# list of non-parametric types that are primitives. Avoid using this constant, use
|
|
91
|
+
# is_base_primitive instead because it accounts for parametric types like decimals.
|
|
92
|
+
_NON_PARAMETRIC_PRIMITIVES = [
|
|
93
|
+
types.Int64,
|
|
94
|
+
types.Int128,
|
|
95
|
+
types.UInt128,
|
|
96
|
+
types.Float,
|
|
97
|
+
types.GenericDecimal,
|
|
98
|
+
types.Bool,
|
|
99
|
+
types.String,
|
|
100
|
+
types.Date,
|
|
101
|
+
types.DateTime,
|
|
102
|
+
types.Hash
|
|
103
|
+
]
|
|
104
|
+
|
|
105
|
+
ENTITY_LIKES = [types.RowId]
|
|
106
|
+
|
|
107
|
+
# these are relations that try to preserve their input types
|
|
108
|
+
# e.g. if you add a USD and a Decimal, the result is still a USD
|
|
109
|
+
TYPE_PRESERVERS = [
|
|
110
|
+
*builtins.plus.overloads,
|
|
111
|
+
*builtins.minus.overloads,
|
|
112
|
+
*builtins.mul.overloads,
|
|
113
|
+
*builtins.mod.overloads,
|
|
114
|
+
builtins.trunc_div,
|
|
115
|
+
builtins.concat,
|
|
116
|
+
*builtins.abs.overloads,
|
|
117
|
+
*builtins.sum.overloads,
|
|
118
|
+
*builtins.max.overloads,
|
|
119
|
+
*builtins.min.overloads,
|
|
120
|
+
# Exclude div overloads (like Int/Int -> Float) where the result type is different from the input types
|
|
121
|
+
*(filter(lambda x: x.fields[0].type == x.fields[1].type == x.fields[2].type, builtins.div.overloads)),
|
|
122
|
+
# Exclude avg overloads (avg Int -> Float) where the result type is different from the input types
|
|
123
|
+
*(filter(lambda x: x.fields[0].type == x.fields[1].type, builtins.avg.overloads)),
|
|
124
|
+
]
|
|
125
|
+
|
|
126
|
+
#--------------------------------------------------
|
|
127
|
+
# Type helpers
|
|
128
|
+
#--------------------------------------------------
|
|
129
|
+
|
|
130
|
+
def is_abstract(type:ir.Type) -> bool:
|
|
131
|
+
return types.is_abstract_type(type)
|
|
132
|
+
|
|
133
|
+
def to_type(value: ir.Value|ir.Field) -> ir.Type:
|
|
134
|
+
if isinstance(value, (ir.Var, ir.Field, ir.Literal)):
|
|
135
|
+
return value.type
|
|
136
|
+
|
|
137
|
+
if isinstance(value, tuple):
|
|
138
|
+
return types.AnyList
|
|
139
|
+
|
|
140
|
+
if isinstance(value, ir.Relation):
|
|
141
|
+
return types.AnyList
|
|
142
|
+
|
|
143
|
+
raise TypeError(f"Cannot determine IR type for value: {value} of type {type(value).__name__}")
|
|
144
|
+
|
|
145
|
+
def type_matches(actual:ir.Type, expected:ir.Type, allow_expected_parents=False) -> bool:
|
|
146
|
+
"""
|
|
147
|
+
True iff we can use a value of the actual type when expecting the expected type.
|
|
148
|
+
|
|
149
|
+
TODO: document allow_expected_parents (useful for checking that a Person is an Adult)
|
|
150
|
+
"""
|
|
151
|
+
# exact match
|
|
152
|
+
if actual == expected:
|
|
153
|
+
return True
|
|
154
|
+
|
|
155
|
+
# any matches anything
|
|
156
|
+
if actual == types.Any or expected == types.Any:
|
|
157
|
+
return True
|
|
158
|
+
|
|
159
|
+
# any entity matches any entity (surprise surprise!)
|
|
160
|
+
if extends_any_entity(expected) and not is_primitive(actual):
|
|
161
|
+
return True
|
|
162
|
+
|
|
163
|
+
# all decimals match across each other
|
|
164
|
+
if types.is_decimal(actual) and types.is_decimal(expected):
|
|
165
|
+
return True
|
|
166
|
+
|
|
167
|
+
# scalar matches with super-type handling
|
|
168
|
+
is_scalar = isinstance(actual, ir.ScalarType)
|
|
169
|
+
exp_is_scalar = isinstance(expected, ir.ScalarType)
|
|
170
|
+
if is_scalar and any([type_matches(parent, expected) for parent in actual.super_types]):
|
|
171
|
+
return True
|
|
172
|
+
if allow_expected_parents and exp_is_scalar and any([type_matches(actual, parent, allow_expected_parents) for parent in expected.super_types]):
|
|
173
|
+
return True
|
|
174
|
+
if not is_primitive(expected) and exp_is_scalar and actual in ENTITY_LIKES:
|
|
175
|
+
return True
|
|
176
|
+
|
|
177
|
+
# a union type matches if any of its types match the expected type
|
|
178
|
+
if isinstance(actual, ir.UnionType):
|
|
179
|
+
for t in actual.types:
|
|
180
|
+
if type_matches(t, expected):
|
|
181
|
+
return True
|
|
182
|
+
|
|
183
|
+
# types that match Number
|
|
184
|
+
if actual == types.Number and types.is_number(expected):
|
|
185
|
+
return True
|
|
186
|
+
if expected == types.Number and types.is_number(actual):
|
|
187
|
+
return True
|
|
188
|
+
return False
|
|
189
|
+
|
|
190
|
+
|
|
191
|
+
def conversion_allowed(arg:ir.Value, actual:ir.Type, expected:ir.Type, arg_types:set[ir.Type]=set()) -> bool:
|
|
192
|
+
return conversion_cost(arg, actual, expected, arg_types) < float("inf")
|
|
193
|
+
|
|
194
|
+
def literal_conversion_allowed(actual:ir.Type, expected:ir.Type) -> bool:
|
|
195
|
+
if actual in (types.Int64, types.Int128) and (expected == types.Hash or types.is_number(expected)):
|
|
196
|
+
return True
|
|
197
|
+
elif types.is_decimal(actual) and expected == types.Float:
|
|
198
|
+
return True
|
|
199
|
+
elif types.is_decimal(actual) and types.is_decimal(expected):
|
|
200
|
+
return True
|
|
201
|
+
return False
|
|
202
|
+
|
|
203
|
+
def conversion_cost(arg: ir.Value, actual:ir.Type, expected:ir.Type, arg_types:set[ir.Type]) -> float:
|
|
204
|
+
if type_matches(actual, expected):
|
|
205
|
+
return 0
|
|
206
|
+
|
|
207
|
+
# if we have a type variable and all the types in the expression
|
|
208
|
+
# match, then there's no promotion needed and this is a valid overload
|
|
209
|
+
if expected is types.EntityTypeVar and not is_primitive(actual) and len(arg_types) == 1:
|
|
210
|
+
return 0
|
|
211
|
+
|
|
212
|
+
# Literal numbers can be converted to other number types (but not to value types).
|
|
213
|
+
if isinstance(arg, ir.Literal):
|
|
214
|
+
if literal_conversion_allowed(actual, expected):
|
|
215
|
+
return 50
|
|
216
|
+
|
|
217
|
+
# Value types that don't match
|
|
218
|
+
if is_value_type(actual) and is_value_type(expected):
|
|
219
|
+
return float("inf")
|
|
220
|
+
|
|
221
|
+
base_actual = to_base_primitive(actual)
|
|
222
|
+
base_expected = to_base_primitive(expected)
|
|
223
|
+
|
|
224
|
+
if not base_actual or not base_expected:
|
|
225
|
+
return float("inf")
|
|
226
|
+
|
|
227
|
+
if type_matches(base_actual, base_expected):
|
|
228
|
+
return 0
|
|
229
|
+
|
|
230
|
+
return conversion_lattice_cost(base_actual, base_expected)
|
|
231
|
+
|
|
232
|
+
|
|
233
|
+
def merge_types(type1: ir.Type, type2: ir.Type, is_literal1:bool, is_literal2:bool) -> ir.Type:
|
|
234
|
+
if type1 == type2:
|
|
235
|
+
return type1
|
|
236
|
+
|
|
237
|
+
# give precedence to nominal types (e.g. merging USD(decimal) with decimal gives USD(decimal))
|
|
238
|
+
base_primitive_type1 = to_base_primitive(type1)
|
|
239
|
+
base_primitive_type2 = to_base_primitive(type2)
|
|
240
|
+
if base_primitive_type1 == base_primitive_type2:
|
|
241
|
+
if is_base_primitive(type1):
|
|
242
|
+
return type2
|
|
243
|
+
elif is_base_primitive(type2):
|
|
244
|
+
return type1
|
|
245
|
+
|
|
246
|
+
combined = ordered_set()
|
|
247
|
+
types_to_process = [type1, type2]
|
|
248
|
+
|
|
249
|
+
# Iterative flattening of union types
|
|
250
|
+
while types_to_process:
|
|
251
|
+
t = types_to_process.pop()
|
|
252
|
+
if isinstance(t, ir.UnionType):
|
|
253
|
+
types_to_process.extend(t.types)
|
|
254
|
+
else:
|
|
255
|
+
combined.add(t)
|
|
256
|
+
|
|
257
|
+
# If we have multiple types and Any is one of them, remove Any
|
|
258
|
+
if len(combined) > 1 and types.Any in combined:
|
|
259
|
+
combined.remove(types.Any)
|
|
260
|
+
|
|
261
|
+
# If we have multiple types and one is a literal and the other is not,
|
|
262
|
+
# we can try to convert the literal to the other type.
|
|
263
|
+
if len(combined) > 1:
|
|
264
|
+
if is_literal1 and not is_literal2 and literal_conversion_allowed(type1, type2):
|
|
265
|
+
return type2
|
|
266
|
+
if is_literal2 and not is_literal1 and literal_conversion_allowed(type2, type1):
|
|
267
|
+
return type1
|
|
268
|
+
|
|
269
|
+
# Return single type or create a union
|
|
270
|
+
return next(iter(combined)) if len(combined) == 1 else f.union_type(list(combined))
|
|
271
|
+
|
|
272
|
+
def to_base_primitive(type:ir.Type) -> Optional[ir.Type]:
|
|
273
|
+
if isinstance(type, ir.ScalarType):
|
|
274
|
+
if is_base_primitive(type):
|
|
275
|
+
return type
|
|
276
|
+
# walk the hierarchy to find a base primitive
|
|
277
|
+
for parent in type.super_types:
|
|
278
|
+
if found := to_base_primitive(parent):
|
|
279
|
+
return found
|
|
280
|
+
if isinstance(type, ir.UnionType):
|
|
281
|
+
for t in type.types:
|
|
282
|
+
if found := to_base_primitive(t):
|
|
283
|
+
return found
|
|
284
|
+
return None
|
|
285
|
+
|
|
286
|
+
def is_value_type(type:ir.Type):
|
|
287
|
+
return isinstance(type, ir.ScalarType) and is_primitive(type) and not is_base_primitive(type)
|
|
288
|
+
|
|
289
|
+
def is_base_primitive(type:ir.Type) -> bool:
|
|
290
|
+
return type in _NON_PARAMETRIC_PRIMITIVES or types.is_decimal(type)
|
|
291
|
+
|
|
292
|
+
def is_primitive(type:ir.Type) -> bool:
|
|
293
|
+
return to_base_primitive(type) is not None
|
|
294
|
+
|
|
295
|
+
def extends_any_entity(type:ir.Type) -> bool:
|
|
296
|
+
if type == types.AnyEntity:
|
|
297
|
+
return True
|
|
298
|
+
if isinstance(type, ir.ScalarType):
|
|
299
|
+
for parent in type.super_types:
|
|
300
|
+
if extends_any_entity(parent):
|
|
301
|
+
return True
|
|
302
|
+
return False
|
|
303
|
+
|
|
304
|
+
def invalid_type(type:ir.Type) -> bool:
|
|
305
|
+
if isinstance(type, ir.UnionType):
|
|
306
|
+
# if there are multiple primitives, or a primtive and a non-primitive
|
|
307
|
+
# then we have an invalid type
|
|
308
|
+
if len(type.types) > 1:
|
|
309
|
+
return any([is_primitive(t) for t in type.types])
|
|
310
|
+
return False
|
|
311
|
+
|
|
312
|
+
def try_preserve_type(types:set[ir.Type]) -> Optional[ir.Type]:
|
|
313
|
+
# we keep the input type as the output type if either all inputs
|
|
314
|
+
# are the exact same type or there's one nominal and its base primitive
|
|
315
|
+
# type, e.g. USD + Decimal
|
|
316
|
+
if len(types) == 1:
|
|
317
|
+
return next(iter(types))
|
|
318
|
+
if len(types) == 2:
|
|
319
|
+
t1, t2 = types
|
|
320
|
+
# type preservation is not applicable to decimal types
|
|
321
|
+
if isinstance(t1, ir.DecimalType) and isinstance(t2, ir.DecimalType):
|
|
322
|
+
return None
|
|
323
|
+
base_equivalent = type_matches(t1, t2, allow_expected_parents=True)
|
|
324
|
+
if base_equivalent:
|
|
325
|
+
# as long as one of the types is a base primitive, we can use the
|
|
326
|
+
# other type as final preserved type
|
|
327
|
+
if is_base_primitive(t1):
|
|
328
|
+
return t2
|
|
329
|
+
elif is_base_primitive(t2):
|
|
330
|
+
return t1
|
|
331
|
+
return None
|
|
332
|
+
|
|
333
|
+
# Was this variable created for a shared literal?
|
|
334
|
+
def var_is_from_literal(var: ir.Var) -> bool:
|
|
335
|
+
return var.name.startswith("__literal__")
|
|
336
|
+
|
|
337
|
+
#--------------------------------------------------
|
|
338
|
+
# Type Errors
|
|
339
|
+
#--------------------------------------------------
|
|
340
|
+
|
|
341
|
+
@dataclass
|
|
342
|
+
class TyperError():
|
|
343
|
+
node: ir.Node
|
|
344
|
+
|
|
345
|
+
@dataclass
|
|
346
|
+
class TypeMismatch(TyperError):
|
|
347
|
+
expected: ir.Type
|
|
348
|
+
actual: ir.Type
|
|
349
|
+
def __str__(self):
|
|
350
|
+
return f"[red bold][Type Mismatch][/red bold] expected [yellow]{to_name(self.expected)}[/yellow], got [red]{to_name(self.actual)}[/red]: [white]{str(self.node).strip()}[/white]"
|
|
351
|
+
|
|
352
|
+
@dataclass
|
|
353
|
+
class InvalidType(TyperError):
|
|
354
|
+
type: ir.Type
|
|
355
|
+
def __str__(self):
|
|
356
|
+
return f"[red bold][Invalid Type][/red bold] incompatible types [red]{to_name(self.type)}[/red]: [white]{str(self.node).strip()}[/white]"
|
|
357
|
+
|
|
358
|
+
@dataclass
|
|
359
|
+
class UnresolvedOverload(TyperError):
|
|
360
|
+
arg_types: list[ir.Type]
|
|
361
|
+
def __str__(self):
|
|
362
|
+
assert isinstance(self.node, (ir.Lookup, ir.Update, ir.Aggregate))
|
|
363
|
+
rel = to_relation(self.node)
|
|
364
|
+
types = ', '.join([to_name(t) for t in self.arg_types])
|
|
365
|
+
return f"[red bold][Unresolved Overload][/red bold] [yellow]{rel.name}({types})[/yellow]: [white]{str(self.node).strip()}[/white]"
|
|
366
|
+
|
|
367
|
+
@dataclass
|
|
368
|
+
class UnresolvedType(TyperError):
|
|
369
|
+
def __str__(self):
|
|
370
|
+
return f"[red bold][Unresolved Type][/red bold] [white]{str(self.node).strip()}[/white]"
|
|
371
|
+
pass
|
|
372
|
+
|
|
373
|
+
class TyperContextEnricher(visitor.Visitor):
|
|
374
|
+
def __init__(self):
|
|
375
|
+
super().__init__()
|
|
376
|
+
# store mapping from the concept population relation var id to a concept population type
|
|
377
|
+
self.concept_population_types:dict[int, ir.Type] = {}
|
|
378
|
+
|
|
379
|
+
def visit_lookup(self, node: ir.Lookup, parent: Optional[ir.Node]):
|
|
380
|
+
rel = to_relation(node)
|
|
381
|
+
if helpers.is_concept_lookup(rel):
|
|
382
|
+
for arg, field in zip(node.args, rel.fields):
|
|
383
|
+
if isinstance(arg, ir.Var):
|
|
384
|
+
arg_type = arg.type
|
|
385
|
+
field_type = field.type
|
|
386
|
+
# When the rule declares something like `Adult(v::Person)`, we must record that `v` is actually of type
|
|
387
|
+
# `Adult` so this refined type is remembered for later checks.
|
|
388
|
+
if field_type != arg_type and type_matches(arg_type, field_type, True):
|
|
389
|
+
self.concept_population_types[arg.id] = field_type
|
|
390
|
+
|
|
391
|
+
#--------------------------------------------------
|
|
392
|
+
# Propagation Network
|
|
393
|
+
#--------------------------------------------------
|
|
394
|
+
|
|
395
|
+
# The core idea of the typer is to build a propagation network where nodes are vars, fields,
|
|
396
|
+
# or overloaded lookups/updates/aggregates. The intuition is that _all_ types in the IR
|
|
397
|
+
# ultimately flow from relation fields, so if we figure those out we just need to propagate
|
|
398
|
+
# their types to unknown vars, which may then flow into other fields and so on.
|
|
399
|
+
#
|
|
400
|
+
# This means the network only needs to contain nodes that either directly flow into
|
|
401
|
+
# an abstract node or are themselves abstract. We need to track overloads because
|
|
402
|
+
# their arguments effectively act like abstract vars until we've resolved the final types.
|
|
403
|
+
|
|
404
|
+
Node = Union[ir.Var, ir.Field, ir.Lookup, ir.Update, ir.Aggregate, ir.Literal]
|
|
405
|
+
|
|
406
|
+
class PropagationNetwork():
|
|
407
|
+
def __init__(self):
|
|
408
|
+
|
|
409
|
+
# track the set of nodes that represent entry points into the network
|
|
410
|
+
self.roots = ordered_set()
|
|
411
|
+
# we separately want to track nodes that were loaded from a previous run
|
|
412
|
+
# so that even if we have edges to them, we _still_ consider them roots
|
|
413
|
+
# and properly propagate types from them at the beginning
|
|
414
|
+
self.loaded_roots = set()
|
|
415
|
+
self.edges:dict[Node, dict[int, Node]] = defaultdict(dict)
|
|
416
|
+
self.has_incoming = set()
|
|
417
|
+
self.type_requirements:dict[Node, OrderedSet[ir.Field]] = defaultdict(lambda: ordered_set())
|
|
418
|
+
|
|
419
|
+
self.errors:list[TyperError] = []
|
|
420
|
+
# all fields for which types were resolved
|
|
421
|
+
self.fields = set()
|
|
422
|
+
self.resolved_types:dict[int, ir.Type] = {}
|
|
423
|
+
# overloads resolved for a lookup/update/aggregate, by node id
|
|
424
|
+
self.resolved_overloads:dict[int, list[ir.Relation]] = {}
|
|
425
|
+
# if the node resolves to an overload the uses GenericDecimal, the concrete
|
|
426
|
+
# DecimalType to be used (assumes there's a single overload with decimals for a node)
|
|
427
|
+
self.resolved_overload_decimal:dict[int, ir.DecimalType] = {}
|
|
428
|
+
self.node_is_literal:dict[int, bool] = {}
|
|
429
|
+
|
|
430
|
+
#--------------------------------------------------
|
|
431
|
+
# Mismatches
|
|
432
|
+
#--------------------------------------------------
|
|
433
|
+
|
|
434
|
+
def mismatch(self, node:Node, expected:ir.Type, actual:ir.Type):
|
|
435
|
+
self.errors.append(TypeMismatch(node, expected, actual))
|
|
436
|
+
|
|
437
|
+
def invalid_type(self, node:Node, type:ir.Type):
|
|
438
|
+
self.errors.append(InvalidType(node, type))
|
|
439
|
+
|
|
440
|
+
def unresolved_overload(self, node:ir.Lookup|ir.Update|ir.Aggregate):
|
|
441
|
+
self.errors.append(UnresolvedOverload(node, [self.resolve(a) for a in node.args]))
|
|
442
|
+
|
|
443
|
+
def unresolved_type(self, node:Node):
|
|
444
|
+
self.errors.append(UnresolvedType(node))
|
|
445
|
+
|
|
446
|
+
def has_errors(self, node:Node) -> bool:
|
|
447
|
+
for mismatch in self.errors:
|
|
448
|
+
if mismatch.node == node:
|
|
449
|
+
return True
|
|
450
|
+
return False
|
|
451
|
+
|
|
452
|
+
#--------------------------------------------------
|
|
453
|
+
# Types and Edges
|
|
454
|
+
#--------------------------------------------------
|
|
455
|
+
|
|
456
|
+
def add_edge(self, source:Node, target:Node):
|
|
457
|
+
if target not in self.loaded_roots:
|
|
458
|
+
self.roots.remove(target)
|
|
459
|
+
if source not in self.has_incoming:
|
|
460
|
+
self.roots.add(source)
|
|
461
|
+
self.edges[source][target.id] = target
|
|
462
|
+
self.has_incoming.add(target)
|
|
463
|
+
|
|
464
|
+
def add_type(self, node:Node, type:ir.Type, is_literal:bool):
|
|
465
|
+
if isinstance(node, ir.Field):
|
|
466
|
+
self.fields.add(node)
|
|
467
|
+
|
|
468
|
+
if node.id in self.resolved_types:
|
|
469
|
+
if isinstance(node, ir.Literal) and self.resolved_types[node.id] == node.type:
|
|
470
|
+
# if the previous "resolved type" was just the literal type itself, we can
|
|
471
|
+
# replace it; this is safe because literals are only part of one task
|
|
472
|
+
self.resolved_types[node.id] = type
|
|
473
|
+
else:
|
|
474
|
+
self.resolved_types[node.id] = merge_types(self.resolved_types[node.id], type, self.node_is_literal[node.id], is_literal)
|
|
475
|
+
else:
|
|
476
|
+
self.resolved_types[node.id] = type
|
|
477
|
+
|
|
478
|
+
if node.id in self.node_is_literal:
|
|
479
|
+
is_literal &= self.node_is_literal[node.id]
|
|
480
|
+
self.node_is_literal[node.id] = is_literal
|
|
481
|
+
|
|
482
|
+
def add_type_requirement(self, source:Node, field:ir.Field):
|
|
483
|
+
self.type_requirements[source].add(field)
|
|
484
|
+
|
|
485
|
+
#--------------------------------------------------
|
|
486
|
+
# Load previous types
|
|
487
|
+
#--------------------------------------------------
|
|
488
|
+
|
|
489
|
+
def load_types(self, type_dict:dict[ir.Field, ir.Type]):
|
|
490
|
+
for node, type in type_dict.items():
|
|
491
|
+
self.add_type(node, type, False)
|
|
492
|
+
self.loaded_roots.add(node)
|
|
493
|
+
self.roots.add(node)
|
|
494
|
+
|
|
495
|
+
#--------------------------------------------------
|
|
496
|
+
# Resolve
|
|
497
|
+
#--------------------------------------------------
|
|
498
|
+
|
|
499
|
+
def resolve(self, value:Node|ir.Value) -> ir.Type:
|
|
500
|
+
if isinstance(value, (ir.Var, ir.Field)):
|
|
501
|
+
return self.resolved_types.get(value.id) or to_type(value)
|
|
502
|
+
assert not isinstance(value, (ir.Lookup, ir.Update, ir.Aggregate)), "Should never try to resolve a task"
|
|
503
|
+
return to_type(value)
|
|
504
|
+
|
|
505
|
+
#--------------------------------------------------
|
|
506
|
+
# Overloads
|
|
507
|
+
#--------------------------------------------------
|
|
508
|
+
|
|
509
|
+
def resolve_overload_deps(self, op:ir.Lookup|ir.Update|ir.Aggregate, keep_known=False):
|
|
510
|
+
# We need to find which args flow into this task, which flow out, and
|
|
511
|
+
# if some are inputs, then they are required before we can resolve
|
|
512
|
+
incoming:list[Node] = []
|
|
513
|
+
outgoing:list[Node] = []
|
|
514
|
+
required:list[Node] = []
|
|
515
|
+
relation = to_relation(op)
|
|
516
|
+
field_args = zip(relation.fields, [self.resolve(a) for a in op.args], op.args)
|
|
517
|
+
has_inputs = any([field.input for field in relation.fields])
|
|
518
|
+
for field, arg_type, arg in field_args:
|
|
519
|
+
if not isinstance(arg, (ir.Var, ir.Literal)):
|
|
520
|
+
continue
|
|
521
|
+
arg_is_abstract = is_abstract(arg_type)
|
|
522
|
+
if not arg_is_abstract and not keep_known:
|
|
523
|
+
continue
|
|
524
|
+
elif field.input or not arg_is_abstract:
|
|
525
|
+
incoming.append(arg)
|
|
526
|
+
required.append(arg)
|
|
527
|
+
elif not has_inputs:
|
|
528
|
+
incoming.append(arg)
|
|
529
|
+
outgoing.append(arg)
|
|
530
|
+
else:
|
|
531
|
+
outgoing.append(arg)
|
|
532
|
+
|
|
533
|
+
# check if the overloads of this relation are fully resolved, if any aren't
|
|
534
|
+
# then their fields are incoming edges to this node
|
|
535
|
+
for overload in relation.overloads:
|
|
536
|
+
for field in overload.fields:
|
|
537
|
+
resolved_type = self.resolve(field)
|
|
538
|
+
if resolved_type != types.GenericDecimal and is_abstract(resolved_type):
|
|
539
|
+
incoming.append(field)
|
|
540
|
+
required.append(field)
|
|
541
|
+
|
|
542
|
+
return incoming, outgoing, required
|
|
543
|
+
|
|
544
|
+
def resolve_overload(self, op:ir.Lookup|ir.Update|ir.Aggregate) -> Optional[list[ir.Relation]]:
|
|
545
|
+
# check if we have any unresolved args that are required, if all of our args
|
|
546
|
+
# are unresolved (len(incoming) == len(relation.fields)) then we have no information
|
|
547
|
+
# to try and resolve the overloads with
|
|
548
|
+
incoming, outgoing, required = self.resolve_overload_deps(op)
|
|
549
|
+
relation = to_relation(op)
|
|
550
|
+
if required or len(incoming) == len(relation.fields):
|
|
551
|
+
return
|
|
552
|
+
|
|
553
|
+
overloads = relation.overloads
|
|
554
|
+
if not overloads:
|
|
555
|
+
overloads = [relation]
|
|
556
|
+
|
|
557
|
+
# otherwise we compute the cost of each overload and return the set of relations
|
|
558
|
+
# that have the lowest cost. This can be multiple in e.g. the Person.pets.name
|
|
559
|
+
# case where Cat_name and Dog_name are equally valid
|
|
560
|
+
inf = float("inf")
|
|
561
|
+
min_cost = inf
|
|
562
|
+
matches = []
|
|
563
|
+
for overload in overloads:
|
|
564
|
+
arg_types = set(self.resolve(arg) for arg, field in zip(op.args, overload.fields) if field.input)
|
|
565
|
+
total = 0
|
|
566
|
+
for arg, field in zip(op.args, overload.fields):
|
|
567
|
+
arg_type = self.resolve(arg)
|
|
568
|
+
field_type = self.resolve(field)
|
|
569
|
+
total += conversion_cost(arg, arg_type, field_type, arg_types)
|
|
570
|
+
if total == inf:
|
|
571
|
+
break
|
|
572
|
+
if total != inf and total <= min_cost:
|
|
573
|
+
if total < min_cost:
|
|
574
|
+
min_cost = total
|
|
575
|
+
matches.clear()
|
|
576
|
+
matches.append(overload)
|
|
577
|
+
return matches
|
|
578
|
+
|
|
579
|
+
#--------------------------------------------------
|
|
580
|
+
# Propagation
|
|
581
|
+
#--------------------------------------------------
|
|
582
|
+
|
|
583
|
+
def propagate(self):
|
|
584
|
+
edges = self.edges
|
|
585
|
+
work_list = []
|
|
586
|
+
|
|
587
|
+
# go through all the roots and find any that are not abstract, they'll
|
|
588
|
+
# be the first nodes to push types through the network
|
|
589
|
+
unhandled = ordered_set()
|
|
590
|
+
for node in self.roots:
|
|
591
|
+
if not isinstance(node, (ir.Var, ir.Field, ir.Literal)):
|
|
592
|
+
continue
|
|
593
|
+
node_type = self.resolve(node)
|
|
594
|
+
if not is_abstract(node_type):
|
|
595
|
+
is_literal = isinstance(node, ir.Literal) or (isinstance(node, ir.Var) and var_is_from_literal(node))
|
|
596
|
+
self.add_type(node, node_type, is_literal)
|
|
597
|
+
work_list.append(node)
|
|
598
|
+
else:
|
|
599
|
+
unhandled.add(node)
|
|
600
|
+
|
|
601
|
+
# We need to visit nodes in topological order; that is we need to visit all predecessors
|
|
602
|
+
# of a node before the node itself.
|
|
603
|
+
|
|
604
|
+
# push known type nodes through the edges
|
|
605
|
+
while work_list:
|
|
606
|
+
source = work_list.pop(0)
|
|
607
|
+
unhandled.remove(source)
|
|
608
|
+
source_type = self.resolve(source)
|
|
609
|
+
is_literal = self.node_is_literal.get(source.id, False)
|
|
610
|
+
# check to see if the source has ended up with a set of types that
|
|
611
|
+
# aren't valid, e.g. a union of primitives
|
|
612
|
+
if invalid_type(source_type):
|
|
613
|
+
self.invalid_type(source, source_type)
|
|
614
|
+
|
|
615
|
+
# propagate our type to each outgoing edge
|
|
616
|
+
for out in edges.get(source, {}).values():
|
|
617
|
+
# if this is an overload then we need to try and resolve it
|
|
618
|
+
if isinstance(out, (ir.Update, ir.Lookup, ir.Aggregate)):
|
|
619
|
+
found = self.resolve_overload(out)
|
|
620
|
+
if found is not None:
|
|
621
|
+
start_arg_types = [self.resolve(arg) for arg in out.args]
|
|
622
|
+
self.propagate_overloads(out, found)
|
|
623
|
+
final_arg_types = [self.resolve(arg) for arg in out.args]
|
|
624
|
+
for arg, start, final in zip(out.args, start_arg_types, final_arg_types):
|
|
625
|
+
if start != final:
|
|
626
|
+
work_list.append(arg)
|
|
627
|
+
# otherwise, we just add to the outgoing node's type and if it
|
|
628
|
+
# changes we add it to the work list
|
|
629
|
+
elif start := self.resolve(out):
|
|
630
|
+
self.add_type(out, source_type, is_literal)
|
|
631
|
+
if start != self.resolve(out) or out in unhandled:
|
|
632
|
+
work_list.append(out)
|
|
633
|
+
|
|
634
|
+
for source in unhandled:
|
|
635
|
+
self.unresolved_type(source)
|
|
636
|
+
|
|
637
|
+
# now that we've pushed all the types through the network, we need to validate
|
|
638
|
+
# that all type requirements of those nodes are met
|
|
639
|
+
for node, fields in self.type_requirements.items():
|
|
640
|
+
node_type = self.resolve(node)
|
|
641
|
+
for field in fields:
|
|
642
|
+
field_type = self.resolve(field)
|
|
643
|
+
if not type_matches(node_type, field_type):
|
|
644
|
+
node_base = to_base_primitive(node_type)
|
|
645
|
+
field_base = to_base_primitive(field_type)
|
|
646
|
+
if conversion_lattice_cost(node_base, field_base) == float("inf"):
|
|
647
|
+
self.mismatch(node, field_type, node_type)
|
|
648
|
+
|
|
649
|
+
# return the resolved type for fields only
|
|
650
|
+
field_types = {}
|
|
651
|
+
for field in self.fields:
|
|
652
|
+
if field.id in self.resolved_types:
|
|
653
|
+
field_types[field] = self.resolved_types[field.id]
|
|
654
|
+
return field_types
|
|
655
|
+
|
|
656
|
+
|
|
657
|
+
def propagate_overloads(self, node:ir.Lookup|ir.Update|ir.Aggregate, overloads:list[ir.Relation]):
|
|
658
|
+
if not overloads:
|
|
659
|
+
return self.unresolved_overload(node)
|
|
660
|
+
|
|
661
|
+
# we've resolved the overloads, so store that
|
|
662
|
+
self.resolved_overloads[node.id] = overloads
|
|
663
|
+
|
|
664
|
+
# we need to determine the final types of our args but taking all the overloads
|
|
665
|
+
# and adding the type of their fields back to the args.
|
|
666
|
+
resolved_args = [self.resolve(a) for a in node.args]
|
|
667
|
+
for overload in overloads:
|
|
668
|
+
resolved_fields = [self.resolve(f) for f in overload.fields]
|
|
669
|
+
if types.GenericDecimal in resolved_fields:
|
|
670
|
+
# this overload contains generic decimals, so find which specific type of
|
|
671
|
+
# decimal to use given the arguments being passed
|
|
672
|
+
decimal, resolved_fields = self.specialize_decimal_overload(resolved_fields, resolved_args)
|
|
673
|
+
self.resolved_overload_decimal[node.id] = decimal
|
|
674
|
+
|
|
675
|
+
# if our overload preserves types, we check to see if there's a preserved
|
|
676
|
+
# output type given the inputs and if so, shadow the field's type with the
|
|
677
|
+
# preserved type
|
|
678
|
+
if overload in TYPE_PRESERVERS:
|
|
679
|
+
input_types = set([arg_type for field, arg_type
|
|
680
|
+
in zip(overload.fields, resolved_args)
|
|
681
|
+
if field.input])
|
|
682
|
+
if out_type := try_preserve_type(input_types):
|
|
683
|
+
resolved_fields = [field_type if field.input else out_type
|
|
684
|
+
for field, field_type in zip(overload.fields, resolved_fields)]
|
|
685
|
+
|
|
686
|
+
for field_type, arg_type, arg in zip(resolved_fields, resolved_args, node.args):
|
|
687
|
+
if isinstance(arg, ir.Var) and is_abstract(arg_type):
|
|
688
|
+
is_literal = var_is_from_literal(arg)
|
|
689
|
+
self.add_type(arg, field_type, is_literal)
|
|
690
|
+
elif isinstance(arg, ir.Literal) and arg_type != field_type:
|
|
691
|
+
self.add_type(arg, field_type, True)
|
|
692
|
+
|
|
693
|
+
return None
|
|
694
|
+
|
|
695
|
+
def specialize_decimal_overload(self, field_types:list[ir.Type], arg_types:list[ir.Type]) -> Tuple[ir.DecimalType, list[ir.Type]]:
|
|
696
|
+
"""
|
|
697
|
+
Find the decimal type to use for an overload that has GenericDecimals in its field_types,
|
|
698
|
+
and which is being referred to with these arg_types.
|
|
699
|
+
|
|
700
|
+
Return a tuple where the first element is the specialized decimal type, and the second
|
|
701
|
+
element is a new list that contains the same types as field_types but with
|
|
702
|
+
GenericDecimal replaced by this specialized decimal.
|
|
703
|
+
"""
|
|
704
|
+
decimal = None
|
|
705
|
+
for arg_type in arg_types:
|
|
706
|
+
x = types.decimal_supertype(arg_type)
|
|
707
|
+
if isinstance(x, ir.DecimalType):
|
|
708
|
+
# the current specialization policy is to select the decimal with largest
|
|
709
|
+
# scale and, if there multiple with the largest scale, the one with the
|
|
710
|
+
# largest precision. This is safe because when converting a decimal to the
|
|
711
|
+
# specialized decimal, we never truncate fractional digits (because we
|
|
712
|
+
# selected the largest scale) and, if the non-fractional digits are too
|
|
713
|
+
# large to fit the specialized decimal, we will have a runtime overflow,
|
|
714
|
+
# which should alert the user of the problem.
|
|
715
|
+
#
|
|
716
|
+
# In the future we can implement more complex policies. For example,
|
|
717
|
+
# snowflake has well documented behavior for how the output of operations
|
|
718
|
+
# behave in face of different decimal types, and we may use that:
|
|
719
|
+
# https://docs.snowflake.com/en/sql-reference/operators-arithmetic#scale-and-precision-in-arithmetic-operations
|
|
720
|
+
if decimal is None or x.scale > decimal.scale or (x.scale == decimal.scale and x.precision > decimal.precision):
|
|
721
|
+
decimal = x
|
|
722
|
+
assert(isinstance(decimal, ir.DecimalType))
|
|
723
|
+
return decimal, [decimal if field_type == types.GenericDecimal else field_type
|
|
724
|
+
for field_type in field_types]
|
|
725
|
+
|
|
726
|
+
|
|
727
|
+
#--------------------------------------------------
|
|
728
|
+
# Display
|
|
729
|
+
#--------------------------------------------------
|
|
730
|
+
|
|
731
|
+
# draw the network as a mermaid graph for the debugger
|
|
732
|
+
def to_mermaid(self, max_edges=500) -> str:
|
|
733
|
+
resolved = self.resolved_types
|
|
734
|
+
nodes = ordered_set()
|
|
735
|
+
link_strs = []
|
|
736
|
+
for src, dsts in self.edges.items():
|
|
737
|
+
nodes.add(src)
|
|
738
|
+
for dst in dsts.values():
|
|
739
|
+
if len(link_strs) > max_edges:
|
|
740
|
+
break
|
|
741
|
+
nodes.add(dst)
|
|
742
|
+
link_strs.append(f"n{src.id} --> n{dst.id}")
|
|
743
|
+
if len(link_strs) > max_edges:
|
|
744
|
+
break
|
|
745
|
+
|
|
746
|
+
for src, dsts in self.type_requirements.items():
|
|
747
|
+
nodes.add(src)
|
|
748
|
+
for dst in dsts:
|
|
749
|
+
if len(link_strs) > max_edges:
|
|
750
|
+
break
|
|
751
|
+
nodes.add(dst)
|
|
752
|
+
link_strs.append(f"n{src.id} --> n{dst.id}")
|
|
753
|
+
if len(link_strs) > max_edges:
|
|
754
|
+
break
|
|
755
|
+
|
|
756
|
+
def type_span(t:ir.Type) -> str:
|
|
757
|
+
type_str = t.name if isinstance(t, ir.ScalarType) else str(t)
|
|
758
|
+
return f"<span style='color:cyan;'>{type_str.strip()}</span>"
|
|
759
|
+
|
|
760
|
+
def overload_span(rel:ir.Relation, arg_types:list[ir.Type]) -> str:
|
|
761
|
+
args = []
|
|
762
|
+
for field, arg_type in zip(rel.fields, arg_types):
|
|
763
|
+
field_type = self.resolve(field)
|
|
764
|
+
if not type_matches(arg_type, field_type) and field_type != types.EntityTypeVar:
|
|
765
|
+
args.append(f"<span style='color:yellow;'>{str(arg_type).strip()} -> {str(field_type).strip()}</span>")
|
|
766
|
+
elif isinstance(arg_type, ir.UnionType):
|
|
767
|
+
args.append(type_span(field_type))
|
|
768
|
+
else:
|
|
769
|
+
args.append(type_span(arg_type))
|
|
770
|
+
return f'{rel.name}({", ".join(args)})'
|
|
771
|
+
|
|
772
|
+
node_strs = []
|
|
773
|
+
for node in nodes:
|
|
774
|
+
klass = ""
|
|
775
|
+
if isinstance(node, ir.Var):
|
|
776
|
+
ir_type = resolved.get(node.id) or self.resolve(node)
|
|
777
|
+
type_str = type_span(ir_type)
|
|
778
|
+
# if this is one of our vars that is a placeholder for a literal, just use the type
|
|
779
|
+
if isinstance(ir_type, ir.ScalarType) and var_is_from_literal(node):
|
|
780
|
+
label = f'(["Literal {type_str}"])'
|
|
781
|
+
else:
|
|
782
|
+
label = f'(["{node.name}: {type_str}"])'
|
|
783
|
+
elif isinstance(node, ir.Literal):
|
|
784
|
+
ir_type = resolved.get(node.id) or self.resolve(node)
|
|
785
|
+
type_str = type_span(ir_type)
|
|
786
|
+
label = f'(["Literal {type_str}"])'
|
|
787
|
+
elif isinstance(node, ir.Field):
|
|
788
|
+
ir_type = resolved.get(node.id) or self.resolve(node)
|
|
789
|
+
type_str = type_span(ir_type)
|
|
790
|
+
klass = ":::field"
|
|
791
|
+
label = f"{{{{\"{node.name}: {type_str}\"}}}}"
|
|
792
|
+
elif isinstance(node, (ir.Lookup, ir.Update, ir.Aggregate)):
|
|
793
|
+
arg_types = [self.resolve(arg) for arg in node.args]
|
|
794
|
+
if node.id in self.resolved_overloads:
|
|
795
|
+
overloads = self.resolved_overloads[node.id]
|
|
796
|
+
content = "<br/>".join([overload_span(o, arg_types) for o in overloads])
|
|
797
|
+
else:
|
|
798
|
+
content = overload_span(to_relation(node), arg_types)
|
|
799
|
+
label = f'[/"{content}"/]'
|
|
800
|
+
else:
|
|
801
|
+
raise NotImplementedError(f"Unknown node type: {type(node)}")
|
|
802
|
+
if self.has_errors(node):
|
|
803
|
+
klass = ":::error"
|
|
804
|
+
node_strs.append(f"n{node.id}{label}{klass}")
|
|
805
|
+
|
|
806
|
+
node_str = "\n ".join(node_strs)
|
|
807
|
+
link_str = "\n ".join(link_strs)
|
|
808
|
+
template = f"""
|
|
809
|
+
%%{{init: {{'theme':'dark', 'flowchart':{{'useMaxWidth':false, 'htmlLabels': true}}}}}}%%
|
|
810
|
+
flowchart TD
|
|
811
|
+
linkStyle default stroke:#666
|
|
812
|
+
classDef field fill:#245,stroke:#478
|
|
813
|
+
classDef error fill:#624,stroke:#945,color:#f9a
|
|
814
|
+
classDef default stroke:#444,stroke-width:2px, font-size:12px
|
|
815
|
+
|
|
816
|
+
%% nodes
|
|
817
|
+
{node_str}
|
|
818
|
+
|
|
819
|
+
%% edges
|
|
820
|
+
{link_str}
|
|
821
|
+
"""
|
|
822
|
+
return template
|
|
823
|
+
|
|
824
|
+
# simplified, less verbose (compared to mermaid) output for snapshot testing
|
|
825
|
+
def to_fish(self) -> str:
|
|
826
|
+
resolved = self.resolved_types
|
|
827
|
+
nodes = ordered_set()
|
|
828
|
+
for src, dsts in self.edges.items():
|
|
829
|
+
nodes.add(src)
|
|
830
|
+
nodes.update(dsts.values())
|
|
831
|
+
for src, dsts in self.type_requirements.items():
|
|
832
|
+
nodes.add(src)
|
|
833
|
+
nodes.update(dsts)
|
|
834
|
+
|
|
835
|
+
def type_to_str(t:ir.Type) -> str:
|
|
836
|
+
type_str = t.name if isinstance(t, ir.ScalarType) else str(t)
|
|
837
|
+
return type_str.strip()
|
|
838
|
+
|
|
839
|
+
def overload_str(rel:ir.Relation, arg_types:list[ir.Type]) -> str:
|
|
840
|
+
args = []
|
|
841
|
+
for field, arg_type in zip(rel.fields, arg_types):
|
|
842
|
+
field_type = self.resolve(field)
|
|
843
|
+
if not type_matches(arg_type, field_type):
|
|
844
|
+
args.append(f"{str(arg_type).strip()} -?> {str(field_type).strip()}")
|
|
845
|
+
elif isinstance(arg_type, ir.UnionType):
|
|
846
|
+
args.append(type_to_str(field_type))
|
|
847
|
+
else:
|
|
848
|
+
args.append(type_to_str(arg_type))
|
|
849
|
+
return f'{rel.name}({", ".join(args)})'
|
|
850
|
+
|
|
851
|
+
def node_kind(node:ir.Node) -> str:
|
|
852
|
+
if isinstance(node, (ir.Lookup, ir.Update, ir.Aggregate)):
|
|
853
|
+
return "overload"
|
|
854
|
+
return type(node).__name__.lower()
|
|
855
|
+
|
|
856
|
+
def error_info(err:TyperError) -> str:
|
|
857
|
+
if isinstance(err, TypeMismatch):
|
|
858
|
+
return f'Type Mismatch|expected {to_name(err.expected)}, got {to_name(err.actual)}'
|
|
859
|
+
return f'{type(err).__name__}'
|
|
860
|
+
|
|
861
|
+
nl = "\n"
|
|
862
|
+
node_strs = []
|
|
863
|
+
for node in nodes:
|
|
864
|
+
info = ""
|
|
865
|
+
if isinstance(node, ir.Var):
|
|
866
|
+
ir_type = resolved.get(node.id) or self.resolve(node)
|
|
867
|
+
if not(isinstance(ir_type, ir.ScalarType) and var_is_from_literal(node)):
|
|
868
|
+
info = f'{node.name}|{type_to_str(ir_type)}'
|
|
869
|
+
elif isinstance(node, ir.Literal):
|
|
870
|
+
ir_type = resolved.get(node.id) or self.resolve(node)
|
|
871
|
+
if not(isinstance(ir_type, ir.ScalarType)):
|
|
872
|
+
info = f'Literal|{type_to_str(ir_type)}'
|
|
873
|
+
elif isinstance(node, ir.Field):
|
|
874
|
+
ir_type = resolved.get(node.id) or self.resolve(node)
|
|
875
|
+
info = f'{node.name}|{type_to_str(ir_type)}'
|
|
876
|
+
elif isinstance(node, (ir.Lookup, ir.Update, ir.Aggregate)):
|
|
877
|
+
arg_types = [self.resolve(arg) for arg in node.args]
|
|
878
|
+
if node.id in self.resolved_overloads:
|
|
879
|
+
overloads = self.resolved_overloads[node.id]
|
|
880
|
+
content = nl.join([overload_str(o, arg_types) for o in overloads])
|
|
881
|
+
else:
|
|
882
|
+
content = overload_str(to_relation(node), arg_types)
|
|
883
|
+
info = f'{content}'
|
|
884
|
+
else:
|
|
885
|
+
raise NotImplementedError(f"Unknown node type: {type(node)}")
|
|
886
|
+
|
|
887
|
+
if info:
|
|
888
|
+
error_suffix = " !" if self.has_errors(node) else ""
|
|
889
|
+
node_strs.append(f'{node_kind(node)}|{info}{error_suffix}')
|
|
890
|
+
|
|
891
|
+
node_strs.sort()
|
|
892
|
+
if self.errors:
|
|
893
|
+
node_strs.append("---ERRORS---")
|
|
894
|
+
for err in self.errors:
|
|
895
|
+
node_strs.append(f'{error_info(err)} ({str(err.node).strip()})')
|
|
896
|
+
|
|
897
|
+
return f"""{nl.join(node_strs)}"""
|
|
898
|
+
|
|
899
|
+
#--------------------------------------------------
|
|
900
|
+
# Analyzer
|
|
901
|
+
#--------------------------------------------------
|
|
902
|
+
|
|
903
|
+
class Analyzer(visitor.Visitor):
|
|
904
|
+
def __init__(self):
|
|
905
|
+
super().__init__()
|
|
906
|
+
self.net = PropagationNetwork()
|
|
907
|
+
self.context_enricher = TyperContextEnricher()
|
|
908
|
+
|
|
909
|
+
# this is a map of literal types to a var representing that literal in
|
|
910
|
+
# the graph. This allows us to collapse all literals into a single node
|
|
911
|
+
# and avoids exploding node count if lots of data gets added directly
|
|
912
|
+
self.shared_literal_vars:dict[ir.Type, ir.Var] = {
|
|
913
|
+
t: f.var(f"__literal__{t.name}", t)
|
|
914
|
+
for t in types.builtin_types
|
|
915
|
+
if isinstance(t, ir.ScalarType)
|
|
916
|
+
}
|
|
917
|
+
|
|
918
|
+
#--------------------------------------------------
|
|
919
|
+
# Literal types
|
|
920
|
+
#--------------------------------------------------
|
|
921
|
+
|
|
922
|
+
# given a literal type, lookup or create a var for it
|
|
923
|
+
def shared_literal_var(self, node:ir.Type) -> ir.Var:
|
|
924
|
+
assert isinstance(node, ir.ScalarType)
|
|
925
|
+
if node not in self.shared_literal_vars:
|
|
926
|
+
self.shared_literal_vars[node] = f.var(node.name, node)
|
|
927
|
+
return self.shared_literal_vars[node]
|
|
928
|
+
|
|
929
|
+
#--------------------------------------------------
|
|
930
|
+
# Lookups + Aggregates
|
|
931
|
+
#--------------------------------------------------
|
|
932
|
+
|
|
933
|
+
def visit_lookup(self, node: ir.Lookup, parent: Optional[ir.Node]):
|
|
934
|
+
self.visit_rel_op(node, parent)
|
|
935
|
+
|
|
936
|
+
def visit_aggregate(self, node: ir.Aggregate, parent: ir.Node | None):
|
|
937
|
+
self.visit_rel_op(node, parent)
|
|
938
|
+
|
|
939
|
+
def visit_rel_op(self, node: ir.Lookup|ir.Aggregate, parent: Optional[ir.Node]):
|
|
940
|
+
rel = to_relation(node)
|
|
941
|
+
if isinstance(node, ir.Lookup) and builtins.is_eq(rel):
|
|
942
|
+
return self.visit_eq(node, parent)
|
|
943
|
+
if isinstance(node, ir.Lookup) and rel == builtins.cast:
|
|
944
|
+
return self.visit_cast(node, parent)
|
|
945
|
+
|
|
946
|
+
if rel.overloads:
|
|
947
|
+
return self.visit_overloaded(node, parent)
|
|
948
|
+
|
|
949
|
+
# if this is a population check, then it's fine to pass a subtype in to do the check
|
|
950
|
+
# e.g. Employee(Person) is a valid way to check if a person is an employee
|
|
951
|
+
is_concept_lookup = helpers.is_concept_lookup(rel)
|
|
952
|
+
arg_types = set(self.net.resolve(arg) for arg, field in zip(node.args, rel.fields) if field.input)
|
|
953
|
+
for arg, field in zip(node.args, rel.fields):
|
|
954
|
+
field_type = field.type
|
|
955
|
+
arg_type = self.net.resolve(arg)
|
|
956
|
+
is_var = isinstance(arg, ir.Var)
|
|
957
|
+
if not type_matches(arg_type, field_type, is_concept_lookup or is_abstract(arg_type)):
|
|
958
|
+
# If the rule explicitly specifies a refined type, e.g. `Adult(v::Person)`,
|
|
959
|
+
# then `v` should be treated as `Adult` (not just `Person`).
|
|
960
|
+
# In that case, check the declared population type first before checking conversions.
|
|
961
|
+
if is_var and self.context_enricher.concept_population_types.get(arg.id, arg_type) == field_type:
|
|
962
|
+
continue
|
|
963
|
+
# Do not complain if we can convert the arg to the field type.
|
|
964
|
+
if not conversion_allowed(arg, arg_type, field_type, arg_types):
|
|
965
|
+
self.net.mismatch(node, field_type, arg_type)
|
|
966
|
+
continue
|
|
967
|
+
# if we have an abstract var then this field will ultimately propagate to that
|
|
968
|
+
# var's type
|
|
969
|
+
elif is_var and is_abstract(arg_type) and not field.input:
|
|
970
|
+
self.net.add_edge(field, arg)
|
|
971
|
+
continue
|
|
972
|
+
|
|
973
|
+
return None
|
|
974
|
+
|
|
975
|
+
#--------------------------------------------------
|
|
976
|
+
# Overloads
|
|
977
|
+
#--------------------------------------------------
|
|
978
|
+
|
|
979
|
+
def visit_overloaded(self, node: ir.Lookup|ir.Update|ir.Aggregate, parent: Optional[ir.Node]):
|
|
980
|
+
incoming, outgoing, required = self.net.resolve_overload_deps(node, keep_known=True)
|
|
981
|
+
for arg in incoming:
|
|
982
|
+
self.net.add_edge(arg, node)
|
|
983
|
+
for arg in outgoing:
|
|
984
|
+
self.net.add_edge(node, arg)
|
|
985
|
+
|
|
986
|
+
#--------------------------------------------------
|
|
987
|
+
# Eq
|
|
988
|
+
#--------------------------------------------------
|
|
989
|
+
|
|
990
|
+
def visit_eq(self, node: ir.Lookup, parent: Optional[ir.Node]):
|
|
991
|
+
(left, right) = node.args
|
|
992
|
+
left_type = self.net.resolve(left)
|
|
993
|
+
right_type = self.net.resolve(right)
|
|
994
|
+
if is_abstract(left_type) and is_abstract(right_type):
|
|
995
|
+
assert isinstance(left, ir.Var) and isinstance(right, ir.Var)
|
|
996
|
+
# if both sides are abstract, then whatever we find out about
|
|
997
|
+
# either should propagate to the other
|
|
998
|
+
self.net.add_edge(left, right)
|
|
999
|
+
self.net.add_edge(right, left)
|
|
1000
|
+
elif is_abstract(left_type):
|
|
1001
|
+
assert isinstance(left, ir.Var)
|
|
1002
|
+
if isinstance(right, ir.Var):
|
|
1003
|
+
self.net.add_edge(right, left)
|
|
1004
|
+
else:
|
|
1005
|
+
literal_var = self.shared_literal_var(right_type)
|
|
1006
|
+
self.net.add_edge(literal_var, left)
|
|
1007
|
+
elif is_abstract(right_type):
|
|
1008
|
+
assert isinstance(right, ir.Var)
|
|
1009
|
+
if isinstance(left, ir.Var):
|
|
1010
|
+
self.net.add_edge(left, right)
|
|
1011
|
+
else:
|
|
1012
|
+
literal_var = self.shared_literal_var(left_type)
|
|
1013
|
+
self.net.add_edge(literal_var, right)
|
|
1014
|
+
elif not type_matches(left_type, right_type):
|
|
1015
|
+
if not conversion_allowed(left, left_type, right_type) and not conversion_allowed(right, right_type, left_type):
|
|
1016
|
+
self.net.mismatch(node, left_type, right_type)
|
|
1017
|
+
|
|
1018
|
+
#--------------------------------------------------
|
|
1019
|
+
# Cast
|
|
1020
|
+
#--------------------------------------------------
|
|
1021
|
+
|
|
1022
|
+
def visit_cast(self, node: ir.Lookup, parent: Optional[ir.Node]):
|
|
1023
|
+
# Cast has fields: to_type (input), source (input), target (output)
|
|
1024
|
+
assert len(node.args) == 3, f"Expected 3 arguments for cast builtin, but got: {node.args}"
|
|
1025
|
+
(to_type, source, target) = node.args
|
|
1026
|
+
assert isinstance(to_type, ir.Type), f"Invalid target type for cast: {to_type}"
|
|
1027
|
+
assert isinstance(target, ir.Var), f"Invalid target variable for cast: {target}"
|
|
1028
|
+
|
|
1029
|
+
if not is_abstract(to_type):
|
|
1030
|
+
# If target is abstract, it should get the type from to_type
|
|
1031
|
+
target_type = self.net.resolve(target)
|
|
1032
|
+
if is_abstract(target_type) and isinstance(target, ir.Var):
|
|
1033
|
+
self.net.add_type(target, to_type, False)
|
|
1034
|
+
|
|
1035
|
+
#--------------------------------------------------
|
|
1036
|
+
# Update
|
|
1037
|
+
#--------------------------------------------------
|
|
1038
|
+
|
|
1039
|
+
def visit_update(self, node: ir.Update, parent: Optional[ir.Node]):
|
|
1040
|
+
rel = node.relation
|
|
1041
|
+
# if this is a population check, then it's fine to pass a subtype in to do the population
|
|
1042
|
+
# e.g. Employee(Person) should be a valid way to populate a person
|
|
1043
|
+
allow_any_parents = helpers.is_concept_lookup(rel)
|
|
1044
|
+
for arg, field in zip(node.args, rel.fields):
|
|
1045
|
+
field_type = field.type
|
|
1046
|
+
arg_type = self.net.resolve(arg)
|
|
1047
|
+
is_var = isinstance(arg, ir.Var)
|
|
1048
|
+
|
|
1049
|
+
# if the arg is abstract, but the field isn't, then we need to make
|
|
1050
|
+
# sure that once the arg is resolved we check that it matches the field
|
|
1051
|
+
# type
|
|
1052
|
+
if is_var and is_abstract(arg_type) and not is_abstract(field_type):
|
|
1053
|
+
self.net.add_type_requirement(arg, field)
|
|
1054
|
+
|
|
1055
|
+
# if the field is abstract, then eventually this var will help determine
|
|
1056
|
+
# the field's type
|
|
1057
|
+
elif is_abstract(field_type):
|
|
1058
|
+
source = self.shared_literal_var(arg_type) if not is_var else arg
|
|
1059
|
+
self.net.add_edge(source, field)
|
|
1060
|
+
|
|
1061
|
+
elif not type_matches(arg_type, field_type, allow_expected_parents=allow_any_parents):
|
|
1062
|
+
# If the rule explicitly specifies a refined type, e.g. `Adult(v::Person)`,
|
|
1063
|
+
# then `v` should be treated as `Adult` (not just `Person`).
|
|
1064
|
+
# In that case, check the declared population type first before checking conversions.
|
|
1065
|
+
if is_var and self.context_enricher.concept_population_types.get(arg.id, arg_type) == field_type:
|
|
1066
|
+
continue
|
|
1067
|
+
if not conversion_allowed(arg, arg_type, field_type):
|
|
1068
|
+
self.net.mismatch(node, field_type, arg_type)
|
|
1069
|
+
|
|
1070
|
+
#--------------------------------------------------
|
|
1071
|
+
# Replacer
|
|
1072
|
+
#--------------------------------------------------
|
|
1073
|
+
|
|
1074
|
+
# Once we've pushed all the types through the network, we need to replace the types of
|
|
1075
|
+
# fields and vars that we may have discovered. We also need to replace overloaded lookups
|
|
1076
|
+
# with the choosen overloads and do any conversions that are needed.
|
|
1077
|
+
@dataclass
|
|
1078
|
+
class Replacer(visitor.Rewriter):
|
|
1079
|
+
net: PropagationNetwork = dataclasses.field(init=True)
|
|
1080
|
+
new_relations:OrderedSet[ir.Relation] = dataclasses.field(default_factory=OrderedSet[ir.Relation])
|
|
1081
|
+
|
|
1082
|
+
def handle_model(self, model: ir.Model, parent: None):
|
|
1083
|
+
model = super().handle_model(model, parent)
|
|
1084
|
+
return model.reconstruct(
|
|
1085
|
+
relations=model.relations | self.new_relations
|
|
1086
|
+
)
|
|
1087
|
+
|
|
1088
|
+
def handle_field(self, node: ir.Field, parent: ir.Node):
|
|
1089
|
+
if node.id in self.net.resolved_types:
|
|
1090
|
+
return f.field(node.name, self.net.resolved_types[node.id], node.input)
|
|
1091
|
+
return node
|
|
1092
|
+
|
|
1093
|
+
def handle_var(self, node: ir.Var, parent: ir.Node):
|
|
1094
|
+
if node.id in self.net.resolved_types:
|
|
1095
|
+
return f.var(node.name, self.net.resolved_types[node.id])
|
|
1096
|
+
return node
|
|
1097
|
+
|
|
1098
|
+
def handle_literal(self, node: ir.Literal, parent: ir.Node):
|
|
1099
|
+
# Up until now, we allow literals to have a non-builtin types. This code converts the value
|
|
1100
|
+
# to a builtin Python type and lifts the literal to that supertype.
|
|
1101
|
+
t = self.net.resolved_types[node.id] if node.id in self.net.resolved_types else node.type
|
|
1102
|
+
v, t = self.convert_literal_value(node.value, t)
|
|
1103
|
+
return node.reconstruct(value=v, type=t)
|
|
1104
|
+
|
|
1105
|
+
def handle_update(self, node: ir.Update, parent: ir.Node):
|
|
1106
|
+
node = super().handle_update(node, parent)
|
|
1107
|
+
|
|
1108
|
+
# we may do conversions, so we can end up with multiple tasks
|
|
1109
|
+
# in this branch and we need to track what the final args are
|
|
1110
|
+
tasks = []
|
|
1111
|
+
final_args = []
|
|
1112
|
+
for arg, field in zip(node.args, node.relation.fields):
|
|
1113
|
+
arg_type = to_type(arg)
|
|
1114
|
+
field_type = to_type(field)
|
|
1115
|
+
# the typer previously made sure that this should be valid so
|
|
1116
|
+
# a type mismatch means we need to convert as long as this isn't
|
|
1117
|
+
# a type variable
|
|
1118
|
+
if field_type != types.EntityTypeVar and not type_matches(arg_type, field_type):
|
|
1119
|
+
arg_base = to_base_primitive(arg_type)
|
|
1120
|
+
field_base = to_base_primitive(field_type)
|
|
1121
|
+
if arg_base is not None and field_base is not None and conversion_allowed(arg, arg_base, field_base):
|
|
1122
|
+
new_arg = self.convert(arg, arg_base, field_base, tasks)
|
|
1123
|
+
final_args.append(new_arg)
|
|
1124
|
+
else:
|
|
1125
|
+
final_args.append(arg)
|
|
1126
|
+
else:
|
|
1127
|
+
final_args.append(arg)
|
|
1128
|
+
tasks.append(node.reconstruct(args=tuple(final_args)))
|
|
1129
|
+
|
|
1130
|
+
if len(tasks) == 1:
|
|
1131
|
+
return tasks[0]
|
|
1132
|
+
else:
|
|
1133
|
+
return f.logical(tasks, [])
|
|
1134
|
+
|
|
1135
|
+
def handle_aggregate(self, node: ir.Aggregate, parent: ir.Node):
|
|
1136
|
+
node = super().handle_aggregate(node, parent)
|
|
1137
|
+
lookup_args = list(node.args)
|
|
1138
|
+
hoists = helpers.get_agg_outputs(node)
|
|
1139
|
+
|
|
1140
|
+
if len(node.aggregation.fields) == len(lookup_args):
|
|
1141
|
+
tasks = []
|
|
1142
|
+
for i in range(len(lookup_args)):
|
|
1143
|
+
if not conversion_allowed(lookup_args[i], to_type(lookup_args[i]), node.aggregation.fields[i].type):
|
|
1144
|
+
self.net.mismatch(node, node.aggregation.fields[i].type, to_type(lookup_args[i]))
|
|
1145
|
+
elif not type_matches(to_type(lookup_args[i]), node.aggregation.fields[i].type):
|
|
1146
|
+
lookup_args[i] = self.convert(lookup_args[i], to_type(lookup_args[i]), node.aggregation.fields[i].type, tasks)
|
|
1147
|
+
tasks.append(node.reconstruct(args=tuple(lookup_args)))
|
|
1148
|
+
if len(tasks) == 1:
|
|
1149
|
+
return tasks[0]
|
|
1150
|
+
else:
|
|
1151
|
+
return f.logical(tasks, hoists)
|
|
1152
|
+
|
|
1153
|
+
return node
|
|
1154
|
+
|
|
1155
|
+
def handle_lookup(self, node: ir.Lookup, parent: ir.Node):
|
|
1156
|
+
node = super().handle_lookup(node, parent)
|
|
1157
|
+
lookup_args = node.args
|
|
1158
|
+
|
|
1159
|
+
# we can resolve to multiple overloads, that we'd need to union
|
|
1160
|
+
# so we'll need to hoist all the vars being output to make sure
|
|
1161
|
+
# everything joins correctly
|
|
1162
|
+
hoists = helpers.get_outputs(node)
|
|
1163
|
+
branches:list[ir.Logical] = []
|
|
1164
|
+
|
|
1165
|
+
# Special case this solver primitive because the type system cannot handle what it
|
|
1166
|
+
# needs, which is an input field types as List[Union[Int64,Float64,String,Hash]].
|
|
1167
|
+
# So this code makes sure that any in128 that flows into the second argument, which
|
|
1168
|
+
# is represented as a tuple, is converted into an int64, which is lossy but should
|
|
1169
|
+
# work for most cases
|
|
1170
|
+
if node.relation == builtins.rel_primitive_solverlib_fo_appl and len(node.relation.fields) == 3 and len(lookup_args) == 3:
|
|
1171
|
+
tasks = []
|
|
1172
|
+
# Make lookup_args mutable.
|
|
1173
|
+
lookup_args = list(lookup_args)
|
|
1174
|
+
|
|
1175
|
+
# Convert the first and third arguments to the field types.
|
|
1176
|
+
for i in [0, 2]:
|
|
1177
|
+
if not conversion_allowed(lookup_args[i], to_type(lookup_args[i]), node.relation.fields[i].type):
|
|
1178
|
+
self.net.mismatch(node, node.relation.fields[i].type, to_type(lookup_args[i]))
|
|
1179
|
+
elif not type_matches(to_type(lookup_args[i]), node.relation.fields[i].type):
|
|
1180
|
+
lookup_args[i] = self.convert(lookup_args[i], to_type(lookup_args[i]), node.relation.fields[i].type, tasks)
|
|
1181
|
+
|
|
1182
|
+
varargs = lookup_args[1]
|
|
1183
|
+
final_varargs = []
|
|
1184
|
+
assert isinstance(varargs, tuple)
|
|
1185
|
+
for arg in varargs:
|
|
1186
|
+
t = to_type(arg)
|
|
1187
|
+
if t == types.Int128:
|
|
1188
|
+
final_varargs.append(self.convert(arg, t, types.Int64, tasks))
|
|
1189
|
+
else:
|
|
1190
|
+
final_varargs.append(arg)
|
|
1191
|
+
lookup_args[1] = tuple(final_varargs)
|
|
1192
|
+
tasks.append(node.reconstruct(args=tuple(lookup_args)))
|
|
1193
|
+
branches.append(f.logical(tasks, hoists))
|
|
1194
|
+
|
|
1195
|
+
elif builtins.is_eq(node.relation):
|
|
1196
|
+
tasks = []
|
|
1197
|
+
|
|
1198
|
+
# We need to handle eq specially because its arguments can be converted symmetrically
|
|
1199
|
+
(left, right) = lookup_args
|
|
1200
|
+
left_type = to_type(left)
|
|
1201
|
+
right_type = to_type(right)
|
|
1202
|
+
mismatch = False
|
|
1203
|
+
if not type_matches(left_type, right_type):
|
|
1204
|
+
if conversion_allowed(left, left_type, right_type):
|
|
1205
|
+
new_left = self.convert(left, left_type, right_type, tasks)
|
|
1206
|
+
final_args = [new_left, right]
|
|
1207
|
+
elif conversion_allowed(right, right_type, left_type):
|
|
1208
|
+
new_right = self.convert(right, right_type, left_type, tasks)
|
|
1209
|
+
final_args = [left, new_right]
|
|
1210
|
+
else:
|
|
1211
|
+
self.net.mismatch(node, left_type, right_type)
|
|
1212
|
+
final_args = [left, right]
|
|
1213
|
+
mismatch = True
|
|
1214
|
+
else:
|
|
1215
|
+
final_args = [left, right]
|
|
1216
|
+
|
|
1217
|
+
if not mismatch:
|
|
1218
|
+
min_cost = float('inf')
|
|
1219
|
+
arg_types = set(to_type(arg) for arg in final_args)
|
|
1220
|
+
resolved = []
|
|
1221
|
+
for o in node.relation.overloads:
|
|
1222
|
+
total = 0
|
|
1223
|
+
for arg, field in zip(final_args, o.fields):
|
|
1224
|
+
arg_type = to_type(arg)
|
|
1225
|
+
field_type = to_type(field)
|
|
1226
|
+
total += conversion_cost(arg, arg_type, field_type, arg_types)
|
|
1227
|
+
if total <= min_cost:
|
|
1228
|
+
if total < min_cost:
|
|
1229
|
+
resolved.clear()
|
|
1230
|
+
min_cost = total
|
|
1231
|
+
resolved.append(o)
|
|
1232
|
+
|
|
1233
|
+
if len(resolved) == 1:
|
|
1234
|
+
tasks.append(f.lookup(resolved[0], final_args))
|
|
1235
|
+
else:
|
|
1236
|
+
# If we cannot resolve the overload, just leave it.
|
|
1237
|
+
self.net.unresolved_overload(node)
|
|
1238
|
+
tasks.append(f.lookup(node.relation, final_args))
|
|
1239
|
+
else:
|
|
1240
|
+
# If there's a mismatch, just leave the original relation.
|
|
1241
|
+
tasks.append(f.lookup(node.relation, final_args))
|
|
1242
|
+
|
|
1243
|
+
branches.append(f.logical(tasks, hoists))
|
|
1244
|
+
elif node.relation == builtins.cast:
|
|
1245
|
+
assert len(node.args) == 3, f"Invalid number of arguments for cast: {node.args}"
|
|
1246
|
+
(tgt_type, src, tgt) = node.args
|
|
1247
|
+
assert isinstance(tgt_type, ir.Type), f"Invalid target type for cast: {tgt_type}"
|
|
1248
|
+
src_type = to_type(src)
|
|
1249
|
+
|
|
1250
|
+
# if we are casting a literal into a var, we can just set the var to a new literal
|
|
1251
|
+
# with the same value but with the same type as the variable
|
|
1252
|
+
if isinstance(src, ir.Literal) and literal_conversion_allowed(src_type, tgt_type):
|
|
1253
|
+
annos = node.annotations
|
|
1254
|
+
return f.lookup(builtins.eq, (ir.Literal(tgt_type, src.value), tgt), annos=(list(annos) + [builtins.from_cast_annotation]))
|
|
1255
|
+
|
|
1256
|
+
src_base = to_base_primitive(src_type)
|
|
1257
|
+
tgt_base = to_base_primitive(tgt_type)
|
|
1258
|
+
|
|
1259
|
+
if src_base and tgt_base and src_base == tgt_base:
|
|
1260
|
+
annos = node.annotations
|
|
1261
|
+
return f.lookup(builtins.eq, (src, tgt), annos=(list(annos) + [builtins.from_cast_annotation]))
|
|
1262
|
+
|
|
1263
|
+
if conversion_allowed(node.args[0], src_type, tgt_type):
|
|
1264
|
+
if tgt_base:
|
|
1265
|
+
return node.reconstruct(args=(tgt_base, src, tgt))
|
|
1266
|
+
else:
|
|
1267
|
+
return node
|
|
1268
|
+
else:
|
|
1269
|
+
# TODO: we are simply ignoring this, assuming there's some way to cast it
|
|
1270
|
+
# self.net.mismatch(node, src_type, tgt_type)
|
|
1271
|
+
return node
|
|
1272
|
+
else:
|
|
1273
|
+
if node.id in self.net.resolved_overloads:
|
|
1274
|
+
resolved = self.net.resolved_overloads[node.id]
|
|
1275
|
+
else:
|
|
1276
|
+
resolved = [node.relation]
|
|
1277
|
+
decimal_type = self.net.resolved_overload_decimal.get(node.id)
|
|
1278
|
+
|
|
1279
|
+
for overload in resolved:
|
|
1280
|
+
# we may do conversions, so we can end up with multiple tasks
|
|
1281
|
+
# in this branch and we need to track what the final args are
|
|
1282
|
+
tasks = []
|
|
1283
|
+
final_args = []
|
|
1284
|
+
|
|
1285
|
+
for arg, field in zip(lookup_args, overload.fields):
|
|
1286
|
+
arg_type = to_type(arg)
|
|
1287
|
+
field_type = to_type(field)
|
|
1288
|
+
# the typer previously made sure that this should be valid so
|
|
1289
|
+
# a type mismatch means we need to convert as long as this isn't
|
|
1290
|
+
# a type variable
|
|
1291
|
+
if field_type == types.GenericDecimal or (field_type != types.EntityTypeVar and not arg_type == field_type):
|
|
1292
|
+
arg_base = to_base_primitive(arg_type)
|
|
1293
|
+
field_base = to_base_primitive(field_type)
|
|
1294
|
+
if field_base == types.GenericDecimal:
|
|
1295
|
+
field_base = decimal_type
|
|
1296
|
+
if arg_base is not None and field_base is not None and conversion_allowed(arg, arg_base, field_base):
|
|
1297
|
+
new_arg = self.convert(arg, arg_base, field_base, tasks)
|
|
1298
|
+
final_args.append(new_arg)
|
|
1299
|
+
else:
|
|
1300
|
+
final_args.append(arg)
|
|
1301
|
+
else:
|
|
1302
|
+
final_args.append(arg)
|
|
1303
|
+
tasks.append(f.lookup(overload, final_args))
|
|
1304
|
+
branches.append(f.logical(tasks, hoists))
|
|
1305
|
+
# unwrap if we don't actually need a union
|
|
1306
|
+
if len(branches) == 1:
|
|
1307
|
+
if len(branches[0].body) == 1:
|
|
1308
|
+
return branches[0].body[0]
|
|
1309
|
+
else:
|
|
1310
|
+
return branches[0]
|
|
1311
|
+
else:
|
|
1312
|
+
return f.union(branches, hoists)
|
|
1313
|
+
|
|
1314
|
+
def convert(self, arg:ir.Value, actual:ir.Type, expected:ir.Type, tasks:list[ir.Task]) -> ir.Value:
|
|
1315
|
+
if actual == expected:
|
|
1316
|
+
return arg
|
|
1317
|
+
value = None
|
|
1318
|
+
if isinstance(arg, ir.Literal):
|
|
1319
|
+
value = arg.value
|
|
1320
|
+
elif isinstance(arg, (int, float, PyDecimal)):
|
|
1321
|
+
value = arg
|
|
1322
|
+
if value is not None:
|
|
1323
|
+
if isinstance(value, int) and expected in (types.Int64, types.Int128):
|
|
1324
|
+
if expected == types.Int64:
|
|
1325
|
+
check_int64(value)
|
|
1326
|
+
return f.literal(value, expected)
|
|
1327
|
+
if isinstance(value, (int, float, PyDecimal)) and expected == types.Float:
|
|
1328
|
+
return f.literal(float(value), expected)
|
|
1329
|
+
if isinstance(value, (int, float, PyDecimal)) and types.is_decimal(expected):
|
|
1330
|
+
# Converting str(value) rather than value avoids precision loss with Float->Decimal conversion.
|
|
1331
|
+
return f.literal(PyDecimal(str(value)), expected)
|
|
1332
|
+
|
|
1333
|
+
name = helpers.sanitize(arg.name + "_" + to_name(expected) if isinstance(arg, ir.Var) else f"v{to_name(expected)}")
|
|
1334
|
+
expected_base = to_base_primitive(expected) or expected
|
|
1335
|
+
new_arg = f.var(name, expected_base)
|
|
1336
|
+
self.new_relations.add(builtins.cast)
|
|
1337
|
+
tasks.append(f.lookup(builtins.cast, (expected_base, arg, new_arg)))
|
|
1338
|
+
self.new_relations.add(builtins.cast)
|
|
1339
|
+
return new_arg
|
|
1340
|
+
|
|
1341
|
+
@staticmethod
|
|
1342
|
+
def convert_literal_value(value, t: ir.Type):
|
|
1343
|
+
if types.is_subtype(t, types.String):
|
|
1344
|
+
return value, types.String
|
|
1345
|
+
if isinstance(value, str) and types.is_subtype(t, types.DateTime):
|
|
1346
|
+
return datetime.datetime.fromisoformat(value), types.DateTime
|
|
1347
|
+
if isinstance(value, str) and types.is_subtype(t, types.Date):
|
|
1348
|
+
return datetime.date.fromisoformat(value), types.Date
|
|
1349
|
+
if isinstance(value, int) and types.is_subtype(t, types.Int64):
|
|
1350
|
+
check_int64(value)
|
|
1351
|
+
return value, types.Int64
|
|
1352
|
+
if isinstance(value, int) and types.is_subtype(t, types.Int128):
|
|
1353
|
+
return value, types.Int128
|
|
1354
|
+
if isinstance(value, (int, float, PyDecimal)) and types.is_subtype(t, types.Float):
|
|
1355
|
+
return float(value), types.Float
|
|
1356
|
+
if isinstance(value, (int, float, PyDecimal)) and types.is_decimal_subtype(t):
|
|
1357
|
+
return PyDecimal(str(value)), types.decimal_supertype(t)
|
|
1358
|
+
return value, t
|
|
1359
|
+
|
|
1360
|
+
#--------------------------------------------------
|
|
1361
|
+
# Typer pass
|
|
1362
|
+
#--------------------------------------------------
|
|
1363
|
+
|
|
1364
|
+
class InferTypes(compiler.Pass):
|
|
1365
|
+
def __init__(self):
|
|
1366
|
+
super().__init__()
|
|
1367
|
+
self.historical = {}
|
|
1368
|
+
|
|
1369
|
+
def rewrite(self, model: ir.Model, options:dict={}) -> ir.Model:
|
|
1370
|
+
w = Analyzer()
|
|
1371
|
+
w.net.load_types(self.historical)
|
|
1372
|
+
|
|
1373
|
+
# collect some type info before analyzing it
|
|
1374
|
+
with debugging.span("type.collect"):
|
|
1375
|
+
model.root.accept(w.context_enricher, model)
|
|
1376
|
+
|
|
1377
|
+
# build the propagation network
|
|
1378
|
+
with debugging.span("type.analyze"):
|
|
1379
|
+
model.root.accept(w, model)
|
|
1380
|
+
|
|
1381
|
+
# propagate the types through the network
|
|
1382
|
+
with debugging.span("type.propagate") as end_span:
|
|
1383
|
+
field_types = w.net.propagate()
|
|
1384
|
+
self.historical.update(field_types)
|
|
1385
|
+
end_span["type_graph"] = w.net.to_mermaid()
|
|
1386
|
+
end_span["type_report"] = w.net.to_fish()
|
|
1387
|
+
|
|
1388
|
+
# replace the fields in the model with the new types
|
|
1389
|
+
with debugging.span("type.replace"):
|
|
1390
|
+
final = Replacer(w.net).walk(model)
|
|
1391
|
+
|
|
1392
|
+
for err in w.net.errors:
|
|
1393
|
+
rich.print(str(err), file=sys.stderr)
|
|
1394
|
+
|
|
1395
|
+
return final
|