splink 5.0.0.dev2__tar.gz → 5.0.0.dev3__tar.gz
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.
- {splink-5.0.0.dev2 → splink-5.0.0.dev3}/PKG-INFO +1 -1
- {splink-5.0.0.dev2 → splink-5.0.0.dev3}/pyproject.toml +1 -1
- {splink-5.0.0.dev2 → splink-5.0.0.dev3}/splink/__init__.py +1 -1
- splink-5.0.0.dev3/splink/backends/duckdb.py +6 -0
- splink-5.0.0.dev3/splink/backends/spark.py +5 -0
- splink-5.0.0.dev3/splink/exploratory.py +4 -0
- {splink-5.0.0.dev2 → splink-5.0.0.dev3}/splink/internals/accuracy.py +6 -11
- {splink-5.0.0.dev2 → splink-5.0.0.dev3}/splink/internals/blocking.py +14 -2
- {splink-5.0.0.dev2 → splink-5.0.0.dev3}/splink/internals/blocking_analysis.py +30 -26
- {splink-5.0.0.dev2 → splink-5.0.0.dev3}/splink/internals/clustering.py +2 -1
- {splink-5.0.0.dev2 → splink-5.0.0.dev3}/splink/internals/comparison.py +7 -6
- {splink-5.0.0.dev2 → splink-5.0.0.dev3}/splink/internals/comparison_level.py +19 -16
- {splink-5.0.0.dev2 → splink-5.0.0.dev3}/splink/internals/comparison_vector_values.py +17 -14
- {splink-5.0.0.dev2 → splink-5.0.0.dev3}/splink/internals/completeness.py +2 -1
- {splink-5.0.0.dev2 → splink-5.0.0.dev3}/splink/internals/connected_components.py +2 -1
- {splink-5.0.0.dev2 → splink-5.0.0.dev3}/splink/internals/database_api.py +1 -12
- splink-5.0.0.dev3/splink/internals/duckdb/database_api_with_profiling.py +71 -0
- {splink-5.0.0.dev2 → splink-5.0.0.dev3}/splink/internals/em_training_session.py +9 -6
- {splink-5.0.0.dev2 → splink-5.0.0.dev3}/splink/internals/estimate_u.py +69 -29
- {splink-5.0.0.dev2 → splink-5.0.0.dev3}/splink/internals/expectation_maximisation.py +5 -4
- {splink-5.0.0.dev2 → splink-5.0.0.dev3}/splink/internals/labelling_tool.py +2 -4
- {splink-5.0.0.dev2 → splink-5.0.0.dev3}/splink/internals/linker.py +8 -8
- {splink-5.0.0.dev2 → splink-5.0.0.dev3}/splink/internals/linker_components/inference.py +55 -73
- {splink-5.0.0.dev2 → splink-5.0.0.dev3}/splink/internals/linker_components/table_management.py +0 -41
- {splink-5.0.0.dev2 → splink-5.0.0.dev3}/splink/internals/linker_components/training.py +5 -12
- {splink-5.0.0.dev2 → splink-5.0.0.dev3}/splink/internals/m_from_labels.py +2 -3
- {splink-5.0.0.dev2 → splink-5.0.0.dev3}/splink/internals/m_training.py +8 -9
- {splink-5.0.0.dev2 → splink-5.0.0.dev3}/splink/internals/misc.py +28 -11
- {splink-5.0.0.dev2 → splink-5.0.0.dev3}/splink/internals/pipeline.py +7 -4
- {splink-5.0.0.dev2 → splink-5.0.0.dev3}/splink/internals/predict.py +34 -19
- {splink-5.0.0.dev2 → splink-5.0.0.dev3}/splink/internals/profile_data.py +3 -3
- {splink-5.0.0.dev2 → splink-5.0.0.dev3}/splink/internals/settings_validation/settings_column_cleaner.py +3 -3
- splink-5.0.0.dev3/splink/internals/spark/database_api_with_profiling.py +174 -0
- {splink-5.0.0.dev2 → splink-5.0.0.dev3}/splink/internals/splink_dataframe.py +3 -2
- {splink-5.0.0.dev2 → splink-5.0.0.dev3}/splink/internals/term_frequencies.py +55 -114
- {splink-5.0.0.dev2 → splink-5.0.0.dev3}/splink/internals/vertically_concatenate.py +25 -104
- splink-5.0.0.dev2/splink/backends/duckdb.py +0 -3
- splink-5.0.0.dev2/splink/backends/spark.py +0 -4
- splink-5.0.0.dev2/splink/exploratory.py +0 -5
- splink-5.0.0.dev2/splink/internals/default_from_jsonschema.py +0 -20
- splink-5.0.0.dev2/splink/internals/validate_jsonschema.py +0 -44
- {splink-5.0.0.dev2 → splink-5.0.0.dev3}/.gitignore +0 -0
- {splink-5.0.0.dev2 → splink-5.0.0.dev3}/LICENSE +0 -0
- {splink-5.0.0.dev2 → splink-5.0.0.dev3}/README.md +0 -0
- {splink-5.0.0.dev2 → splink-5.0.0.dev3}/splink/backends/postgres.py +0 -0
- {splink-5.0.0.dev2 → splink-5.0.0.dev3}/splink/backends/sqlite.py +0 -0
- {splink-5.0.0.dev2 → splink-5.0.0.dev3}/splink/blocking_analysis.py +0 -0
- {splink-5.0.0.dev2 → splink-5.0.0.dev3}/splink/blocking_rule_library.py +0 -0
- {splink-5.0.0.dev2 → splink-5.0.0.dev3}/splink/clustering.py +0 -0
- {splink-5.0.0.dev2 → splink-5.0.0.dev3}/splink/comparison_level_library.py +0 -0
- {splink-5.0.0.dev2 → splink-5.0.0.dev3}/splink/comparison_library.py +0 -0
- {splink-5.0.0.dev2 → splink-5.0.0.dev3}/splink/datasets.py +0 -0
- {splink-5.0.0.dev2 → splink-5.0.0.dev3}/splink/internals/__init__.py +0 -0
- {splink-5.0.0.dev2 → splink-5.0.0.dev3}/splink/internals/block_from_labels.py +0 -0
- {splink-5.0.0.dev2 → splink-5.0.0.dev3}/splink/internals/blocking_rule_creator.py +0 -0
- {splink-5.0.0.dev2 → splink-5.0.0.dev3}/splink/internals/blocking_rule_creator_utils.py +0 -0
- {splink-5.0.0.dev2 → splink-5.0.0.dev3}/splink/internals/blocking_rule_library.py +0 -0
- {splink-5.0.0.dev2 → splink-5.0.0.dev3}/splink/internals/cache_dict_with_logging.py +0 -0
- {splink-5.0.0.dev2 → splink-5.0.0.dev3}/splink/internals/charts.py +0 -0
- {splink-5.0.0.dev2 → splink-5.0.0.dev3}/splink/internals/chunking.py +0 -0
- {splink-5.0.0.dev2 → splink-5.0.0.dev3}/splink/internals/cluster_studio.py +0 -0
- {splink-5.0.0.dev2 → splink-5.0.0.dev3}/splink/internals/column_expression.py +0 -0
- {splink-5.0.0.dev2 → splink-5.0.0.dev3}/splink/internals/comparison_creator.py +0 -0
- {splink-5.0.0.dev2 → splink-5.0.0.dev3}/splink/internals/comparison_level_composition.py +0 -0
- {splink-5.0.0.dev2 → splink-5.0.0.dev3}/splink/internals/comparison_level_creator.py +0 -0
- {splink-5.0.0.dev2 → splink-5.0.0.dev3}/splink/internals/comparison_level_library.py +0 -0
- {splink-5.0.0.dev2 → splink-5.0.0.dev3}/splink/internals/comparison_level_sql.py +0 -0
- {splink-5.0.0.dev2 → splink-5.0.0.dev3}/splink/internals/comparison_library.py +0 -0
- {splink-5.0.0.dev2 → splink-5.0.0.dev3}/splink/internals/comparison_vector_distribution.py +0 -0
- {splink-5.0.0.dev2 → splink-5.0.0.dev3}/splink/internals/constants.py +0 -0
- {splink-5.0.0.dev2 → splink-5.0.0.dev3}/splink/internals/cost_of_blocking_rules.py +0 -0
- {splink-5.0.0.dev2 → splink-5.0.0.dev3}/splink/internals/databricks/__init__.py +0 -0
- {splink-5.0.0.dev2 → splink-5.0.0.dev3}/splink/internals/databricks/enable_splink.py +0 -0
- {splink-5.0.0.dev2 → splink-5.0.0.dev3}/splink/internals/datasets/__init__.py +0 -0
- {splink-5.0.0.dev2 → splink-5.0.0.dev3}/splink/internals/datasets/metadata.py +0 -0
- {splink-5.0.0.dev2 → splink-5.0.0.dev3}/splink/internals/datasets/splink_datasets.py +0 -0
- {splink-5.0.0.dev2 → splink-5.0.0.dev3}/splink/internals/datasets/utils.py +0 -0
- {splink-5.0.0.dev2 → splink-5.0.0.dev3}/splink/internals/dialects.py +0 -0
- {splink-5.0.0.dev2 → splink-5.0.0.dev3}/splink/internals/duckdb/__init__.py +0 -0
- {splink-5.0.0.dev2 → splink-5.0.0.dev3}/splink/internals/duckdb/database_api.py +0 -0
- {splink-5.0.0.dev2 → splink-5.0.0.dev3}/splink/internals/duckdb/dataframe.py +0 -0
- {splink-5.0.0.dev2 → splink-5.0.0.dev3}/splink/internals/duckdb/duckdb_helpers/__init__.py +0 -0
- {splink-5.0.0.dev2 → splink-5.0.0.dev3}/splink/internals/duckdb/duckdb_helpers/duckdb_helpers.py +0 -0
- {splink-5.0.0.dev2 → splink-5.0.0.dev3}/splink/internals/edge_metrics.py +0 -0
- {splink-5.0.0.dev2 → splink-5.0.0.dev3}/splink/internals/exceptions.py +0 -0
- {splink-5.0.0.dev2 → splink-5.0.0.dev3}/splink/internals/files/DEPENDENCY_LICENSES.txt +0 -0
- {splink-5.0.0.dev2 → splink-5.0.0.dev3}/splink/internals/files/chart_defs/accuracy_chart.json +0 -0
- {splink-5.0.0.dev2 → splink-5.0.0.dev3}/splink/internals/files/chart_defs/blocking_rule_generated_comparisons.json +0 -0
- {splink-5.0.0.dev2 → splink-5.0.0.dev3}/splink/internals/files/chart_defs/comparator_score_chart.json +0 -0
- {splink-5.0.0.dev2 → splink-5.0.0.dev3}/splink/internals/files/chart_defs/comparator_score_threshold_chart.json +0 -0
- {splink-5.0.0.dev2 → splink-5.0.0.dev3}/splink/internals/files/chart_defs/completeness.json +0 -0
- {splink-5.0.0.dev2 → splink-5.0.0.dev3}/splink/internals/files/chart_defs/m_u_parameters_interactive_history.json +0 -0
- {splink-5.0.0.dev2 → splink-5.0.0.dev3}/splink/internals/files/chart_defs/match_weight_histogram.json +0 -0
- {splink-5.0.0.dev2 → splink-5.0.0.dev3}/splink/internals/files/chart_defs/match_weights_interactive_history.json +0 -0
- {splink-5.0.0.dev2 → splink-5.0.0.dev3}/splink/internals/files/chart_defs/match_weights_waterfall.json +0 -0
- {splink-5.0.0.dev2 → splink-5.0.0.dev3}/splink/internals/files/chart_defs/parameter_estimate_comparisons.json +0 -0
- {splink-5.0.0.dev2 → splink-5.0.0.dev3}/splink/internals/files/chart_defs/phonetic_match_chart.json +0 -0
- {splink-5.0.0.dev2 → splink-5.0.0.dev3}/splink/internals/files/chart_defs/precision_recall.json +0 -0
- {splink-5.0.0.dev2 → splink-5.0.0.dev3}/splink/internals/files/chart_defs/probability_two_random_records_match_iteration.json +0 -0
- {splink-5.0.0.dev2 → splink-5.0.0.dev3}/splink/internals/files/chart_defs/profile_data.json +0 -0
- {splink-5.0.0.dev2 → splink-5.0.0.dev3}/splink/internals/files/chart_defs/roc.json +0 -0
- {splink-5.0.0.dev2 → splink-5.0.0.dev3}/splink/internals/files/chart_defs/tf_adjustment_chart.json +0 -0
- {splink-5.0.0.dev2 → splink-5.0.0.dev3}/splink/internals/files/chart_defs/threshold_selection_tool.json +0 -0
- {splink-5.0.0.dev2 → splink-5.0.0.dev3}/splink/internals/files/chart_defs/unlinkables_chart_def.json +0 -0
- {splink-5.0.0.dev2 → splink-5.0.0.dev3}/splink/internals/files/external_js/d3@7.8.5 +0 -0
- {splink-5.0.0.dev2 → splink-5.0.0.dev3}/splink/internals/files/external_js/stdlib.js@5.8.3 +0 -0
- {splink-5.0.0.dev2 → splink-5.0.0.dev3}/splink/internals/files/external_js/vega-embed@6.20.2 +0 -0
- {splink-5.0.0.dev2 → splink-5.0.0.dev3}/splink/internals/files/external_js/vega-lite@5.2.0 +0 -0
- {splink-5.0.0.dev2 → splink-5.0.0.dev3}/splink/internals/files/external_js/vega@5.31.0 +0 -0
- {splink-5.0.0.dev2 → splink-5.0.0.dev3}/splink/internals/files/labelling_tool/slt.js +0 -0
- {splink-5.0.0.dev2 → splink-5.0.0.dev3}/splink/internals/files/labelling_tool/template.j2 +0 -0
- {splink-5.0.0.dev2 → splink-5.0.0.dev3}/splink/internals/files/settings_jsonschema.json +0 -0
- {splink-5.0.0.dev2 → splink-5.0.0.dev3}/splink/internals/files/spark_jars/scala-udf-similarity-0.1.2_spark3.x.jar +0 -0
- {splink-5.0.0.dev2 → splink-5.0.0.dev3}/splink/internals/files/spark_jars/scala-udf-similarity-0.2.0_spark4.x.jar +0 -0
- {splink-5.0.0.dev2 → splink-5.0.0.dev3}/splink/internals/files/splink_cluster_studio/cluster_template.j2 +0 -0
- {splink-5.0.0.dev2 → splink-5.0.0.dev3}/splink/internals/files/splink_cluster_studio/custom.css +0 -0
- {splink-5.0.0.dev2 → splink-5.0.0.dev3}/splink/internals/files/splink_comparison_viewer/custom.css +0 -0
- {splink-5.0.0.dev2 → splink-5.0.0.dev3}/splink/internals/files/splink_comparison_viewer/template.j2 +0 -0
- {splink-5.0.0.dev2 → splink-5.0.0.dev3}/splink/internals/files/splink_vis_utils/splink_vis_utils.js +0 -0
- {splink-5.0.0.dev2 → splink-5.0.0.dev3}/splink/internals/files/templates/single_chart_template.html +0 -0
- {splink-5.0.0.dev2 → splink-5.0.0.dev3}/splink/internals/find_brs_with_comparison_counts_below_threshold.py +0 -0
- {splink-5.0.0.dev2 → splink-5.0.0.dev3}/splink/internals/find_matches_to_new_records.py +0 -0
- {splink-5.0.0.dev2 → splink-5.0.0.dev3}/splink/internals/graph_metrics.py +0 -0
- {splink-5.0.0.dev2 → splink-5.0.0.dev3}/splink/internals/input_column.py +0 -0
- {splink-5.0.0.dev2 → splink-5.0.0.dev3}/splink/internals/linker_components/__init__.py +0 -0
- {splink-5.0.0.dev2 → splink-5.0.0.dev3}/splink/internals/linker_components/clustering.py +0 -0
- {splink-5.0.0.dev2 → splink-5.0.0.dev3}/splink/internals/linker_components/evaluation.py +0 -0
- {splink-5.0.0.dev2 → splink-5.0.0.dev3}/splink/internals/linker_components/misc.py +0 -0
- {splink-5.0.0.dev2 → splink-5.0.0.dev3}/splink/internals/linker_components/visualisations.py +0 -0
- {splink-5.0.0.dev2 → splink-5.0.0.dev3}/splink/internals/logging_messages.py +0 -0
- {splink-5.0.0.dev2 → splink-5.0.0.dev3}/splink/internals/lower_id_on_lhs.py +0 -0
- {splink-5.0.0.dev2 → splink-5.0.0.dev3}/splink/internals/m_u_records_to_parameters.py +0 -0
- {splink-5.0.0.dev2 → splink-5.0.0.dev3}/splink/internals/match_weights_histogram.py +0 -0
- {splink-5.0.0.dev2 → splink-5.0.0.dev3}/splink/internals/one_to_one_clustering.py +0 -0
- {splink-5.0.0.dev2 → splink-5.0.0.dev3}/splink/internals/optimise_cost_of_brs.py +0 -0
- {splink-5.0.0.dev2 → splink-5.0.0.dev3}/splink/internals/parse_sql.py +0 -0
- {splink-5.0.0.dev2 → splink-5.0.0.dev3}/splink/internals/postgres/__init__.py +0 -0
- {splink-5.0.0.dev2 → splink-5.0.0.dev3}/splink/internals/postgres/database_api.py +0 -0
- {splink-5.0.0.dev2 → splink-5.0.0.dev3}/splink/internals/postgres/dataframe.py +0 -0
- {splink-5.0.0.dev2 → splink-5.0.0.dev3}/splink/internals/realtime.py +0 -0
- {splink-5.0.0.dev2 → splink-5.0.0.dev3}/splink/internals/settings.py +0 -0
- {splink-5.0.0.dev2 → splink-5.0.0.dev3}/splink/internals/settings_creator.py +0 -0
- {splink-5.0.0.dev2 → splink-5.0.0.dev3}/splink/internals/settings_validation/__init__.py +0 -0
- {splink-5.0.0.dev2 → splink-5.0.0.dev3}/splink/internals/settings_validation/log_invalid_columns.py +0 -0
- {splink-5.0.0.dev2 → splink-5.0.0.dev3}/splink/internals/settings_validation/settings_validation_log_strings.py +0 -0
- {splink-5.0.0.dev2 → splink-5.0.0.dev3}/splink/internals/settings_validation/valid_types.py +0 -0
- {splink-5.0.0.dev2 → splink-5.0.0.dev3}/splink/internals/similarity_analysis.py +0 -0
- {splink-5.0.0.dev2 → splink-5.0.0.dev3}/splink/internals/spark/__init__.py +0 -0
- {splink-5.0.0.dev2 → splink-5.0.0.dev3}/splink/internals/spark/database_api.py +0 -0
- {splink-5.0.0.dev2 → splink-5.0.0.dev3}/splink/internals/spark/dataframe.py +0 -0
- {splink-5.0.0.dev2 → splink-5.0.0.dev3}/splink/internals/spark/jar_location.py +0 -0
- {splink-5.0.0.dev2 → splink-5.0.0.dev3}/splink/internals/spark/spark_helpers/__init__.py +0 -0
- {splink-5.0.0.dev2 → splink-5.0.0.dev3}/splink/internals/spark/spark_helpers/custom_spark_dialect.py +0 -0
- {splink-5.0.0.dev2 → splink-5.0.0.dev3}/splink/internals/spark/spark_helpers/version.py +0 -0
- {splink-5.0.0.dev2 → splink-5.0.0.dev3}/splink/internals/splink_comparison_viewer.py +0 -0
- {splink-5.0.0.dev2 → splink-5.0.0.dev3}/splink/internals/splinkdataframe_utils.py +0 -0
- {splink-5.0.0.dev2 → splink-5.0.0.dev3}/splink/internals/sql_transform.py +0 -0
- {splink-5.0.0.dev2 → splink-5.0.0.dev3}/splink/internals/sqlite/__init__.py +0 -0
- {splink-5.0.0.dev2 → splink-5.0.0.dev3}/splink/internals/sqlite/database_api.py +0 -0
- {splink-5.0.0.dev2 → splink-5.0.0.dev3}/splink/internals/sqlite/dataframe.py +0 -0
- {splink-5.0.0.dev2 → splink-5.0.0.dev3}/splink/internals/testing.py +0 -0
- {splink-5.0.0.dev2 → splink-5.0.0.dev3}/splink/internals/unique_id_concat.py +0 -0
- {splink-5.0.0.dev2 → splink-5.0.0.dev3}/splink/internals/unlinkables.py +0 -0
- {splink-5.0.0.dev2 → splink-5.0.0.dev3}/splink/internals/waterfall_chart.py +0 -0
- {splink-5.0.0.dev2 → splink-5.0.0.dev3}/splink/py.typed +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: splink
|
|
3
|
-
Version: 5.0.0.
|
|
3
|
+
Version: 5.0.0.dev3
|
|
4
4
|
Summary: Fast probabilistic data linkage at scale
|
|
5
5
|
Project-URL: Homepage, https://github.com/moj-analytical-services/splink
|
|
6
6
|
Project-URL: Repository, https://github.com/moj-analytical-services/splink
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
from splink.internals.spark.database_api import SparkAPI
|
|
2
|
+
from splink.internals.spark.database_api_with_profiling import SparkAPIWithProfiling
|
|
3
|
+
from splink.internals.spark.jar_location import similarity_jar_location
|
|
4
|
+
|
|
5
|
+
__all__ = ["similarity_jar_location", "SparkAPI", "SparkAPIWithProfiling"]
|
|
@@ -14,8 +14,8 @@ from splink.internals.predict import predict_from_comparison_vectors_sqls_using_
|
|
|
14
14
|
from splink.internals.splink_dataframe import SplinkDataFrame
|
|
15
15
|
from splink.internals.sql_transform import move_l_r_table_prefix_to_column_suffix
|
|
16
16
|
from splink.internals.vertically_concatenate import (
|
|
17
|
-
|
|
18
|
-
|
|
17
|
+
enqueue_df_concat,
|
|
18
|
+
enqueue_df_concat_with_tf,
|
|
19
19
|
)
|
|
20
20
|
|
|
21
21
|
if TYPE_CHECKING:
|
|
@@ -316,9 +316,7 @@ def truth_space_table_from_labels_table(
|
|
|
316
316
|
match_weight_round_to_nearest: Optional[float] = None,
|
|
317
317
|
) -> SplinkDataFrame:
|
|
318
318
|
pipeline = CTEPipeline()
|
|
319
|
-
|
|
320
|
-
nodes_with_tf = compute_df_concat_with_tf(linker, pipeline)
|
|
321
|
-
pipeline = CTEPipeline([nodes_with_tf])
|
|
319
|
+
enqueue_df_concat_with_tf(linker, pipeline)
|
|
322
320
|
|
|
323
321
|
sqls = predictions_from_sample_of_pairwise_labels_sql(linker, labels_tablename)
|
|
324
322
|
pipeline.enqueue_list_of_sqls(sqls)
|
|
@@ -351,13 +349,11 @@ def truth_space_table_from_labels_column(
|
|
|
351
349
|
group_by_statement = "group by source_dataset"
|
|
352
350
|
|
|
353
351
|
pipeline = CTEPipeline()
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
pipeline = CTEPipeline([concat])
|
|
352
|
+
enqueue_df_concat(linker, pipeline)
|
|
357
353
|
|
|
358
354
|
sql = f"""
|
|
359
355
|
select count(*) as count
|
|
360
|
-
from
|
|
356
|
+
from __splink__df_concat
|
|
361
357
|
{group_by_statement}
|
|
362
358
|
"""
|
|
363
359
|
|
|
@@ -451,8 +447,7 @@ def prediction_errors_from_labels_table(
|
|
|
451
447
|
threshold_match_probability: float = 0.5,
|
|
452
448
|
) -> SplinkDataFrame:
|
|
453
449
|
pipeline = CTEPipeline()
|
|
454
|
-
|
|
455
|
-
pipeline = CTEPipeline([nodes_with_tf])
|
|
450
|
+
enqueue_df_concat_with_tf(linker, pipeline)
|
|
456
451
|
|
|
457
452
|
sqls = predictions_from_sample_of_pairwise_labels_sql(linker, labels_tablename)
|
|
458
453
|
|
|
@@ -14,7 +14,11 @@ from splink.internals.chunking import _chunk_assignment_sql
|
|
|
14
14
|
from splink.internals.database_api import DatabaseAPISubClass
|
|
15
15
|
from splink.internals.dialects import SplinkDialect
|
|
16
16
|
from splink.internals.input_column import InputColumn
|
|
17
|
-
from splink.internals.misc import
|
|
17
|
+
from splink.internals.misc import (
|
|
18
|
+
dedupe_preserving_order,
|
|
19
|
+
ensure_is_list,
|
|
20
|
+
join_sql_with_union_all,
|
|
21
|
+
)
|
|
18
22
|
from splink.internals.parse_sql import parse_columns_in_sql
|
|
19
23
|
from splink.internals.pipeline import CTEPipeline
|
|
20
24
|
from splink.internals.splink_dataframe import SplinkDataFrame
|
|
@@ -117,6 +121,10 @@ class BlockingRule:
|
|
|
117
121
|
def sqlglot_dialect(self) -> str:
|
|
118
122
|
return SplinkDialect.from_string(self._sql_dialect_str).sqlglot_dialect
|
|
119
123
|
|
|
124
|
+
@property
|
|
125
|
+
def requires_blocking_input_materialisation(self) -> bool:
|
|
126
|
+
return False
|
|
127
|
+
|
|
120
128
|
@property
|
|
121
129
|
def sql_dialect(self) -> SplinkDialect:
|
|
122
130
|
return SplinkDialect.from_string(self._sql_dialect_str)
|
|
@@ -403,6 +411,10 @@ class ExplodingBlockingRule(BlockingRule):
|
|
|
403
411
|
|
|
404
412
|
return "false"
|
|
405
413
|
|
|
414
|
+
@property
|
|
415
|
+
def requires_blocking_input_materialisation(self) -> bool:
|
|
416
|
+
return True
|
|
417
|
+
|
|
406
418
|
def create_blocked_pairs_sql(
|
|
407
419
|
self,
|
|
408
420
|
*,
|
|
@@ -760,7 +772,7 @@ def block_using_rules_sqls(
|
|
|
760
772
|
)
|
|
761
773
|
br_sqls.append(sql)
|
|
762
774
|
|
|
763
|
-
sql =
|
|
775
|
+
sql = join_sql_with_union_all(br_sqls)
|
|
764
776
|
|
|
765
777
|
if any(isinstance(br, ExplodingBlockingRule) for br in blocking_rules):
|
|
766
778
|
sqls.append(
|
|
@@ -15,7 +15,6 @@ from typing import (
|
|
|
15
15
|
cast,
|
|
16
16
|
)
|
|
17
17
|
|
|
18
|
-
import duckdb
|
|
19
18
|
import sqlglot
|
|
20
19
|
|
|
21
20
|
from splink.internals.blocking import (
|
|
@@ -77,22 +76,23 @@ def _count_comparisons_from_blocking_rule_pre_filter_conditions_sqls(
|
|
|
77
76
|
input_tablename_l = "__splink__df_concat"
|
|
78
77
|
input_tablename_r = "__splink__df_concat"
|
|
79
78
|
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
79
|
+
if blocking_rule.requires_blocking_input_materialisation:
|
|
80
|
+
sql = blocking_rule.create_blocking_input_sql(
|
|
81
|
+
input_tablename=input_tablename_l,
|
|
82
|
+
input_columns=input_dataframes[0].columns,
|
|
83
|
+
)
|
|
84
84
|
|
|
85
|
-
|
|
85
|
+
sqls.append({"sql": sql, "output_table_name": "__splink__br_input_l"})
|
|
86
86
|
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
87
|
+
sql = blocking_rule.create_blocking_input_sql(
|
|
88
|
+
input_tablename=input_tablename_r,
|
|
89
|
+
input_columns=input_dataframes[0].columns,
|
|
90
|
+
)
|
|
91
91
|
|
|
92
|
-
|
|
92
|
+
sqls.append({"sql": sql, "output_table_name": "__splink__br_input_r"})
|
|
93
93
|
|
|
94
|
-
|
|
95
|
-
|
|
94
|
+
input_tablename_l = "__splink__br_input_l"
|
|
95
|
+
input_tablename_r = "__splink__br_input_r"
|
|
96
96
|
|
|
97
97
|
l_cols_sel = []
|
|
98
98
|
r_cols_sel = []
|
|
@@ -380,18 +380,19 @@ def _cumulative_comparisons_to_be_scored_from_blocking_rules(
|
|
|
380
380
|
"""
|
|
381
381
|
pipeline.enqueue_sql(sql, "__splink__df_count_cumulative_blocks")
|
|
382
382
|
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
con = duckdb.connect()
|
|
383
|
+
result_df = db_api.sql_pipeline_to_splink_dataframe(pipeline).as_duckdbpyrelation()
|
|
384
|
+
con = db_api.duckdb_con
|
|
385
|
+
con.register("result_df", result_df)
|
|
387
386
|
|
|
388
387
|
# The above table won't include rules that have no matches
|
|
388
|
+
all_rules_table_name = "__splink__df_blocking_rule_counts"
|
|
389
389
|
con.execute(
|
|
390
|
-
"CREATE TABLE
|
|
391
|
-
"(match_key
|
|
390
|
+
f"CREATE OR REPLACE TABLE {all_rules_table_name} "
|
|
391
|
+
"(match_key BIGINT, blocking_rule VARCHAR, cartesian BIGINT);"
|
|
392
392
|
)
|
|
393
393
|
con.executemany(
|
|
394
|
-
"INSERT INTO
|
|
394
|
+
f"INSERT INTO {all_rules_table_name} VALUES "
|
|
395
|
+
"($match_key, $blocking_rule, $cartesian);",
|
|
395
396
|
[
|
|
396
397
|
{
|
|
397
398
|
"match_key": str(i),
|
|
@@ -402,8 +403,8 @@ def _cumulative_comparisons_to_be_scored_from_blocking_rules(
|
|
|
402
403
|
],
|
|
403
404
|
)
|
|
404
405
|
|
|
406
|
+
table_name = "__splink__cumulative_blocking_rule_counts"
|
|
405
407
|
if len(result_df) > 0:
|
|
406
|
-
table_name = "__splink__cumulative_blocking_rule_counts"
|
|
407
408
|
con.register(table_name, result_df)
|
|
408
409
|
sql = f"""
|
|
409
410
|
WITH simple_counts AS (
|
|
@@ -413,7 +414,7 @@ def _cumulative_comparisons_to_be_scored_from_blocking_rules(
|
|
|
413
414
|
cast(rules.match_key as int) as match_key,
|
|
414
415
|
rules.cartesian
|
|
415
416
|
FROM
|
|
416
|
-
|
|
417
|
+
{all_rules_table_name} AS rules
|
|
417
418
|
LEFT JOIN
|
|
418
419
|
{table_name} AS counts
|
|
419
420
|
ON
|
|
@@ -437,7 +438,7 @@ def _cumulative_comparisons_to_be_scored_from_blocking_rules(
|
|
|
437
438
|
else:
|
|
438
439
|
# TODO: can we join onto empty arrow table? if so, we don't need separate case
|
|
439
440
|
# simpler sql as we have no data to join onto
|
|
440
|
-
sql = """
|
|
441
|
+
sql = f"""
|
|
441
442
|
select
|
|
442
443
|
blocking_rule,
|
|
443
444
|
0 as row_count,
|
|
@@ -446,15 +447,18 @@ def _cumulative_comparisons_to_be_scored_from_blocking_rules(
|
|
|
446
447
|
match_key,
|
|
447
448
|
0 as start,
|
|
448
449
|
from
|
|
449
|
-
|
|
450
|
+
{all_rules_table_name}
|
|
450
451
|
"""
|
|
451
452
|
|
|
452
453
|
[b.drop_materialised_id_pairs_dataframe() for b in exploding_br_with_id_tables]
|
|
453
454
|
complete_df = con.sql(sql)
|
|
454
|
-
|
|
455
|
-
return cast(
|
|
455
|
+
counts_data = cast(
|
|
456
456
|
list[CumulativeComparisonRecord], record_dicts_from_relation(complete_df)
|
|
457
457
|
)
|
|
458
|
+
# clean up temporary tables
|
|
459
|
+
con.execute(f"DROP VIEW IF EXISTS {table_name}")
|
|
460
|
+
con.execute(f"DROP TABLE {all_rules_table_name}")
|
|
461
|
+
return counts_data
|
|
458
462
|
|
|
459
463
|
|
|
460
464
|
def _count_comparisons_generated_from_blocking_rule(
|
|
@@ -9,6 +9,7 @@ from splink.internals.database_api import AcceptableInputTableType, DatabaseAPIS
|
|
|
9
9
|
from splink.internals.input_column import InputColumn
|
|
10
10
|
from splink.internals.misc import (
|
|
11
11
|
ascii_uid,
|
|
12
|
+
join_sql_with_union_all,
|
|
12
13
|
prob_to_match_weight,
|
|
13
14
|
threshold_args_to_match_prob,
|
|
14
15
|
threshold_args_to_match_prob_list,
|
|
@@ -338,7 +339,7 @@ def _generate_cluster_summary_stats_sql(
|
|
|
338
339
|
for threshold in thresholds
|
|
339
340
|
]
|
|
340
341
|
|
|
341
|
-
sql =
|
|
342
|
+
sql = join_sql_with_union_all(select_statements)
|
|
342
343
|
|
|
343
344
|
return sql
|
|
344
345
|
|
|
@@ -7,6 +7,7 @@ from splink.internals.dialects import SplinkDialect
|
|
|
7
7
|
from splink.internals.input_column import InputColumn
|
|
8
8
|
from splink.internals.misc import (
|
|
9
9
|
dedupe_preserving_order,
|
|
10
|
+
indent_sql,
|
|
10
11
|
join_list_with_commas_final_and,
|
|
11
12
|
)
|
|
12
13
|
|
|
@@ -161,8 +162,8 @@ class Comparison:
|
|
|
161
162
|
sqls = [
|
|
162
163
|
cl._when_then_comparison_vector_value_sql for cl in self.comparison_levels
|
|
163
164
|
]
|
|
164
|
-
sql = "
|
|
165
|
-
sql = f"CASE
|
|
165
|
+
sql = "\n".join(sqls)
|
|
166
|
+
sql = f"CASE\n{indent_sql(sql)}\nEND as {self._gamma_column_name}"
|
|
166
167
|
|
|
167
168
|
return sql
|
|
168
169
|
|
|
@@ -253,8 +254,8 @@ class Comparison:
|
|
|
253
254
|
cl._match_weight_sql(self._gamma_column_name)
|
|
254
255
|
for cl in self.comparison_levels
|
|
255
256
|
]
|
|
256
|
-
sql = "
|
|
257
|
-
sql = f"CASE
|
|
257
|
+
sql = "\n".join(sqls)
|
|
258
|
+
sql = f"CASE\n{indent_sql(sql)}\nEND as {self._mw_column_name}"
|
|
258
259
|
output_cols.append(sql)
|
|
259
260
|
|
|
260
261
|
if self._has_tf_adjustments:
|
|
@@ -262,8 +263,8 @@ class Comparison:
|
|
|
262
263
|
cl._tf_adjustment_sql(self._gamma_column_name, self.comparison_levels)
|
|
263
264
|
for cl in self.comparison_levels
|
|
264
265
|
]
|
|
265
|
-
sql = "
|
|
266
|
-
sql = f"\
|
|
266
|
+
sql = "\n".join(sqls)
|
|
267
|
+
sql = f"CASE\n{indent_sql(sql)}\nEND as {self._mw_tf_adj_column_name}"
|
|
267
268
|
output_cols.append(sql)
|
|
268
269
|
output_cols.append(self._gamma_column_name)
|
|
269
270
|
|
|
@@ -10,7 +10,7 @@ from textwrap import dedent
|
|
|
10
10
|
from typing import Any, Optional, Union, cast
|
|
11
11
|
|
|
12
12
|
import sqlglot
|
|
13
|
-
from sqlglot.expressions import Column, Identifier
|
|
13
|
+
from sqlglot.expressions import Column, Expression, Identifier
|
|
14
14
|
from sqlglot.optimizer.normalize import normalize
|
|
15
15
|
from sqlglot.optimizer.simplify import simplify
|
|
16
16
|
|
|
@@ -84,12 +84,12 @@ def _exact_match_colname(sql_syntax_tree):
|
|
|
84
84
|
return cols[0]
|
|
85
85
|
|
|
86
86
|
|
|
87
|
-
def _get_and_subclauses(expr:
|
|
87
|
+
def _get_and_subclauses(expr: Expression) -> list[Expression]:
|
|
88
88
|
# get list of subclauses joined together by 'AND' at top-level
|
|
89
89
|
# e.g. 'A AND B AND C' -> ['A', 'B', 'C']
|
|
90
90
|
# or if no AND, return expression as a list, e.g. 'A' -> ['A']
|
|
91
91
|
if isinstance(expr, sqlglot.exp.And):
|
|
92
|
-
return
|
|
92
|
+
return [cast(Expression, subclause) for subclause in expr.flatten()]
|
|
93
93
|
return [expr]
|
|
94
94
|
|
|
95
95
|
|
|
@@ -660,11 +660,10 @@ class ComparisonLevel:
|
|
|
660
660
|
|
|
661
661
|
def _match_weight_sql(self, gamma_column_name: str) -> str:
|
|
662
662
|
sql = f"""
|
|
663
|
-
WHEN
|
|
664
|
-
|
|
665
|
-
THEN cast({self._match_weight} as float8)
|
|
663
|
+
WHEN {gamma_column_name} = {self.comparison_vector_value} THEN
|
|
664
|
+
cast({self._match_weight} as float8)
|
|
666
665
|
"""
|
|
667
|
-
return dedent(sql)
|
|
666
|
+
return dedent(sql).strip()
|
|
668
667
|
|
|
669
668
|
def _tf_adjustment_sql(
|
|
670
669
|
self, gamma_column_name: str, comparison_levels: list[ComparisonLevel]
|
|
@@ -689,7 +688,9 @@ class ComparisonLevel:
|
|
|
689
688
|
or self._tf_adjustment_weight == 0
|
|
690
689
|
or self._is_else_level
|
|
691
690
|
):
|
|
692
|
-
sql = f"
|
|
691
|
+
sql = f"""
|
|
692
|
+
WHEN {gamma_colname_value_is_this_level} THEN cast(0 as float8)
|
|
693
|
+
"""
|
|
693
694
|
else:
|
|
694
695
|
tf_adj_col = self._tf_adjustment_input_column
|
|
695
696
|
|
|
@@ -714,14 +715,16 @@ class ComparisonLevel:
|
|
|
714
715
|
|
|
715
716
|
log2_u_prob = math.log2(u_prob_exact_match)
|
|
716
717
|
|
|
717
|
-
sql = f"""
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
718
|
+
sql = f"""
|
|
719
|
+
WHEN {gamma_colname_value_is_this_level} THEN
|
|
720
|
+
CASE
|
|
721
|
+
WHEN {tf_adjustment_exists} THEN
|
|
722
|
+
cast({self._tf_adjustment_weight} as float8) * (
|
|
723
|
+
cast({log2_u_prob} as float8) - log2({tf_u_value_sql})
|
|
724
|
+
)
|
|
725
|
+
ELSE cast(0 as float8)
|
|
726
|
+
END
|
|
727
|
+
"""
|
|
725
728
|
return dedent(sql).strip()
|
|
726
729
|
|
|
727
730
|
def as_dict(self):
|
|
@@ -4,6 +4,7 @@ import logging
|
|
|
4
4
|
from typing import List, Optional
|
|
5
5
|
|
|
6
6
|
from splink.internals.input_column import InputColumn
|
|
7
|
+
from splink.internals.misc import indent_sql
|
|
7
8
|
from splink.internals.unique_id_concat import (
|
|
8
9
|
_composite_unique_id_from_nodes_sql,
|
|
9
10
|
)
|
|
@@ -22,15 +23,15 @@ def compute_comparison_vector_values_sql(
|
|
|
22
23
|
See [the fastlink paper](https://imai.fas.harvard.edu/research/files/linkage.pdf)
|
|
23
24
|
for more details of what is meant by comparison vectors.
|
|
24
25
|
"""
|
|
25
|
-
|
|
26
|
-
|
|
26
|
+
select_columns = list(columns_to_select_for_comparison_vector_values)
|
|
27
27
|
if include_clerical_match_score:
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
28
|
+
select_columns.append("clerical_match_score")
|
|
29
|
+
|
|
30
|
+
select_cols_expr = ",\n".join(indent_sql(col) for col in select_columns)
|
|
31
31
|
|
|
32
32
|
sql = f"""
|
|
33
|
-
select
|
|
33
|
+
select
|
|
34
|
+
{select_cols_expr}
|
|
34
35
|
from __splink__df_blocked
|
|
35
36
|
"""
|
|
36
37
|
|
|
@@ -61,7 +62,8 @@ def compute_comparison_vector_values_from_id_pairs_sqls(
|
|
|
61
62
|
else:
|
|
62
63
|
unique_id_columns = [unique_id_input_column]
|
|
63
64
|
|
|
64
|
-
|
|
65
|
+
select_columns = [*columns_to_select_for_blocking, "b.match_key"]
|
|
66
|
+
select_cols_expr = ",\n".join(indent_sql(col) for col in select_columns)
|
|
65
67
|
|
|
66
68
|
# Where there are large numbers of unmatched records, the DuckDB query planner
|
|
67
69
|
# can struggle with the double inner join below. It should
|
|
@@ -101,7 +103,8 @@ def compute_comparison_vector_values_from_id_pairs_sqls(
|
|
|
101
103
|
# using the __splink__blocked_id_pairs as an associated (junction) table
|
|
102
104
|
# That is, it does the join, but doesn't compute the comparison vectors
|
|
103
105
|
sql = f"""
|
|
104
|
-
select
|
|
106
|
+
select
|
|
107
|
+
{select_cols_expr}
|
|
105
108
|
from __splink__blocked_id_pairs as b
|
|
106
109
|
inner join {input_tablename_l} as l
|
|
107
110
|
on {uid_l_expr} = b.join_key_l
|
|
@@ -111,16 +114,16 @@ def compute_comparison_vector_values_from_id_pairs_sqls(
|
|
|
111
114
|
|
|
112
115
|
sqls.append({"sql": sql, "output_table_name": "blocked_with_cols"})
|
|
113
116
|
|
|
114
|
-
|
|
115
|
-
|
|
117
|
+
select_columns = list(columns_to_select_for_comparison_vector_values)
|
|
116
118
|
if include_clerical_match_score:
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
119
|
+
select_columns.append("clerical_match_score")
|
|
120
|
+
|
|
121
|
+
select_cols_expr = ",\n".join(indent_sql(col) for col in select_columns)
|
|
120
122
|
|
|
121
123
|
# The second table computes the comparison vectors from these aliases
|
|
122
124
|
sql = f"""
|
|
123
|
-
select
|
|
125
|
+
select
|
|
126
|
+
{select_cols_expr}
|
|
124
127
|
from blocked_with_cols
|
|
125
128
|
"""
|
|
126
129
|
|
|
@@ -7,6 +7,7 @@ from splink.internals.charts import (
|
|
|
7
7
|
)
|
|
8
8
|
from splink.internals.database_api import DatabaseAPISubClass
|
|
9
9
|
from splink.internals.input_column import InputColumn
|
|
10
|
+
from splink.internals.misc import join_sql_with_union_all
|
|
10
11
|
from splink.internals.pipeline import CTEPipeline
|
|
11
12
|
from splink.internals.splink_dataframe import SplinkDataFrame
|
|
12
13
|
from splink.internals.splinkdataframe_utils import (
|
|
@@ -75,7 +76,7 @@ def completeness_data(
|
|
|
75
76
|
"""
|
|
76
77
|
sqls.append(sql)
|
|
77
78
|
|
|
78
|
-
sql =
|
|
79
|
+
sql = join_sql_with_union_all(sqls)
|
|
79
80
|
|
|
80
81
|
pipeline.enqueue_sql(sql, "__splink__df_all_column_completeness")
|
|
81
82
|
|
|
@@ -16,6 +16,7 @@ import time
|
|
|
16
16
|
from typing import Optional
|
|
17
17
|
|
|
18
18
|
from splink.internals.database_api import DatabaseAPISubClass
|
|
19
|
+
from splink.internals.misc import join_sql_with_union_all
|
|
19
20
|
from splink.internals.pipeline import CTEPipeline
|
|
20
21
|
from splink.internals.splink_dataframe import SplinkDataFrame
|
|
21
22
|
|
|
@@ -316,7 +317,7 @@ def solve_connected_components(
|
|
|
316
317
|
|
|
317
318
|
pipeline = CTEPipeline()
|
|
318
319
|
|
|
319
|
-
sql =
|
|
320
|
+
sql = join_sql_with_union_all(
|
|
320
321
|
[
|
|
321
322
|
f"""select node_id as {node_id_column_name}, representative as cluster_id
|
|
322
323
|
from {t.physical_name}"""
|
|
@@ -16,7 +16,6 @@ from typing import (
|
|
|
16
16
|
)
|
|
17
17
|
|
|
18
18
|
import duckdb
|
|
19
|
-
import sqlglot
|
|
20
19
|
|
|
21
20
|
from splink.internals.cache_dict_with_logging import CacheDictWithLogging
|
|
22
21
|
from splink.internals.logging_messages import execute_sql_logging_message_info, log_sql
|
|
@@ -106,16 +105,6 @@ class DatabaseAPI(ABC, Generic[TablishType]):
|
|
|
106
105
|
try:
|
|
107
106
|
return self._execute_sql_against_backend(final_sql)
|
|
108
107
|
except Exception as e:
|
|
109
|
-
# Parse our SQL through sqlglot to pretty print
|
|
110
|
-
try:
|
|
111
|
-
final_sql = sqlglot.parse_one(
|
|
112
|
-
final_sql,
|
|
113
|
-
read=self.sql_dialect.sqlglot_dialect,
|
|
114
|
-
).sql(pretty=True)
|
|
115
|
-
# if sqlglot produces any errors, just report the raw SQL
|
|
116
|
-
except Exception:
|
|
117
|
-
pass
|
|
118
|
-
|
|
119
108
|
raise SplinkException(
|
|
120
109
|
f"Error executing the following sql for table "
|
|
121
110
|
f"`{templated_name}`({physical_name}):\n{final_sql}"
|
|
@@ -311,7 +300,7 @@ class DatabaseAPI(ABC, Generic[TablishType]):
|
|
|
311
300
|
# returns sql
|
|
312
301
|
# sensible default:
|
|
313
302
|
self.delete_table_from_database(physical_name)
|
|
314
|
-
sql = f"CREATE TABLE {physical_name} AS
|
|
303
|
+
sql = f"CREATE TABLE {physical_name} AS\n{sql}"
|
|
315
304
|
return sql
|
|
316
305
|
|
|
317
306
|
def _cleanup_for_execute_sql(
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import re
|
|
4
|
+
from os import PathLike
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
from typing import Union
|
|
7
|
+
|
|
8
|
+
import duckdb
|
|
9
|
+
|
|
10
|
+
from .database_api import DuckDBAPI
|
|
11
|
+
from .dataframe import DuckDBDataFrame
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class DuckDBAPIWithProfiling(DuckDBAPI):
|
|
15
|
+
def __init__(
|
|
16
|
+
self,
|
|
17
|
+
connection: Union[str, duckdb.DuckDBPyConnection] = ":memory:",
|
|
18
|
+
output_schema: str = None,
|
|
19
|
+
query_profiling_dir: str | PathLike[str] = "tmp_query_profiling",
|
|
20
|
+
):
|
|
21
|
+
super().__init__(connection=connection, output_schema=output_schema)
|
|
22
|
+
self.query_profiling_dir = Path(query_profiling_dir)
|
|
23
|
+
self.query_profiling_dir.mkdir(parents=True, exist_ok=True)
|
|
24
|
+
self._query_profile_counter = 0
|
|
25
|
+
self._pending_profile_path: Path | None = None
|
|
26
|
+
self._pending_profile_sql: str | None = None
|
|
27
|
+
|
|
28
|
+
def _should_profile_sql(self, sql: str) -> bool:
|
|
29
|
+
stripped_sql = sql.lstrip().upper()
|
|
30
|
+
return stripped_sql.startswith("SELECT") or stripped_sql.startswith("WITH")
|
|
31
|
+
|
|
32
|
+
def _next_query_profile_path(self, templated_name: str) -> Path:
|
|
33
|
+
self._query_profile_counter += 1
|
|
34
|
+
safe_name = re.sub(r"[^A-Za-z0-9_.-]+", "_", templated_name).strip("_")
|
|
35
|
+
if not safe_name:
|
|
36
|
+
safe_name = "query"
|
|
37
|
+
filename = f"{self._query_profile_counter:04d}_{safe_name}_duckdb.txt"
|
|
38
|
+
return self.query_profiling_dir / filename
|
|
39
|
+
|
|
40
|
+
def _setup_for_execute_sql(self, sql: str, physical_name: str) -> str:
|
|
41
|
+
if self._should_profile_sql(sql):
|
|
42
|
+
self._pending_profile_path = self._next_query_profile_path(physical_name)
|
|
43
|
+
self._pending_profile_sql = sql
|
|
44
|
+
else:
|
|
45
|
+
self._pending_profile_path = None
|
|
46
|
+
self._pending_profile_sql = None
|
|
47
|
+
|
|
48
|
+
return super()._setup_for_execute_sql(sql, physical_name)
|
|
49
|
+
|
|
50
|
+
def _cleanup_for_execute_sql(
|
|
51
|
+
self, table: duckdb.DuckDBPyRelation, templated_name: str, physical_name: str
|
|
52
|
+
) -> DuckDBDataFrame:
|
|
53
|
+
try:
|
|
54
|
+
output_df = self.table_to_splink_dataframe(templated_name, physical_name)
|
|
55
|
+
if self._pending_profile_path is not None and self._pending_profile_sql:
|
|
56
|
+
explain_result = super()._execute_sql_against_backend(
|
|
57
|
+
f"EXPLAIN ANALYZE {self._pending_profile_sql}"
|
|
58
|
+
)
|
|
59
|
+
rows = explain_result.fetchall()
|
|
60
|
+
if len(rows) == 1 and len(rows[0]) == 2:
|
|
61
|
+
profile_text = rows[0][1]
|
|
62
|
+
else:
|
|
63
|
+
profile_text = "\n".join(
|
|
64
|
+
" | ".join("" if value is None else str(value) for value in row)
|
|
65
|
+
for row in rows
|
|
66
|
+
)
|
|
67
|
+
self._pending_profile_path.write_text(profile_text, encoding="utf-8")
|
|
68
|
+
return output_df
|
|
69
|
+
finally:
|
|
70
|
+
self._pending_profile_path = None
|
|
71
|
+
self._pending_profile_sql = None
|
|
@@ -26,7 +26,10 @@ from splink.internals.settings import (
|
|
|
26
26
|
Settings,
|
|
27
27
|
TrainingSettings,
|
|
28
28
|
)
|
|
29
|
-
from splink.internals.vertically_concatenate import
|
|
29
|
+
from splink.internals.vertically_concatenate import (
|
|
30
|
+
enqueue_df_concat,
|
|
31
|
+
enqueue_df_concat_with_tf,
|
|
32
|
+
)
|
|
30
33
|
|
|
31
34
|
from .database_api import DatabaseAPISubClass
|
|
32
35
|
from .exceptions import EMTrainingException
|
|
@@ -195,13 +198,12 @@ class EMTrainingSession:
|
|
|
195
198
|
self._training_log_message()
|
|
196
199
|
|
|
197
200
|
pipeline = CTEPipeline()
|
|
198
|
-
|
|
199
|
-
pipeline = CTEPipeline([nodes_with_tf])
|
|
201
|
+
enqueue_df_concat(self._original_linker, pipeline)
|
|
200
202
|
|
|
201
203
|
orig_settings = self._original_linker._settings_obj
|
|
202
204
|
sqls = block_using_rules_sqls(
|
|
203
|
-
input_tablename_l="
|
|
204
|
-
input_tablename_r="
|
|
205
|
+
input_tablename_l="__splink__df_concat",
|
|
206
|
+
input_tablename_r="__splink__df_concat",
|
|
205
207
|
blocking_rules=[self._blocking_rule_for_training],
|
|
206
208
|
link_type=orig_settings._link_type,
|
|
207
209
|
source_dataset_input_column=orig_settings.column_info_settings.source_dataset_input_column,
|
|
@@ -211,7 +213,8 @@ class EMTrainingSession:
|
|
|
211
213
|
|
|
212
214
|
blocked_pairs = self.db_api.sql_pipeline_to_splink_dataframe(pipeline)
|
|
213
215
|
|
|
214
|
-
pipeline = CTEPipeline([blocked_pairs
|
|
216
|
+
pipeline = CTEPipeline([blocked_pairs])
|
|
217
|
+
enqueue_df_concat_with_tf(self._original_linker, pipeline)
|
|
215
218
|
|
|
216
219
|
sqls = compute_comparison_vector_values_from_id_pairs_sqls(
|
|
217
220
|
orig_settings._columns_to_select_for_blocking,
|