collate-data-diff 0.11.12__tar.gz → 0.11.14__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.
Files changed (53) hide show
  1. {collate_data_diff-0.11.12 → collate_data_diff-0.11.14}/PKG-INFO +1 -1
  2. {collate_data_diff-0.11.12 → collate_data_diff-0.11.14}/data_diff/databases/snowflake.py +1 -0
  3. {collate_data_diff-0.11.12 → collate_data_diff-0.11.14}/data_diff/diff_tables.py +36 -6
  4. {collate_data_diff-0.11.12 → collate_data_diff-0.11.14}/data_diff/errors.py +6 -0
  5. {collate_data_diff-0.11.12 → collate_data_diff-0.11.14}/pyproject.toml +1 -1
  6. {collate_data_diff-0.11.12 → collate_data_diff-0.11.14}/LICENSE +0 -0
  7. {collate_data_diff-0.11.12 → collate_data_diff-0.11.14}/README.md +0 -0
  8. {collate_data_diff-0.11.12 → collate_data_diff-0.11.14}/data_diff/__init__.py +0 -0
  9. {collate_data_diff-0.11.12 → collate_data_diff-0.11.14}/data_diff/__main__.py +0 -0
  10. {collate_data_diff-0.11.12 → collate_data_diff-0.11.14}/data_diff/abcs/__init__.py +0 -0
  11. {collate_data_diff-0.11.12 → collate_data_diff-0.11.14}/data_diff/abcs/compiler.py +0 -0
  12. {collate_data_diff-0.11.12 → collate_data_diff-0.11.14}/data_diff/abcs/database_types.py +0 -0
  13. {collate_data_diff-0.11.12 → collate_data_diff-0.11.14}/data_diff/cloud/__init__.py +0 -0
  14. {collate_data_diff-0.11.12 → collate_data_diff-0.11.14}/data_diff/cloud/data_source.py +0 -0
  15. {collate_data_diff-0.11.12 → collate_data_diff-0.11.14}/data_diff/cloud/datafold_api.py +0 -0
  16. {collate_data_diff-0.11.12 → collate_data_diff-0.11.14}/data_diff/config.py +0 -0
  17. {collate_data_diff-0.11.12 → collate_data_diff-0.11.14}/data_diff/databases/__init__.py +0 -0
  18. {collate_data_diff-0.11.12 → collate_data_diff-0.11.14}/data_diff/databases/_connect.py +0 -0
  19. {collate_data_diff-0.11.12 → collate_data_diff-0.11.14}/data_diff/databases/base.py +0 -0
  20. {collate_data_diff-0.11.12 → collate_data_diff-0.11.14}/data_diff/databases/bigquery.py +0 -0
  21. {collate_data_diff-0.11.12 → collate_data_diff-0.11.14}/data_diff/databases/clickhouse.py +0 -0
  22. {collate_data_diff-0.11.12 → collate_data_diff-0.11.14}/data_diff/databases/databricks.py +0 -0
  23. {collate_data_diff-0.11.12 → collate_data_diff-0.11.14}/data_diff/databases/duckdb.py +0 -0
  24. {collate_data_diff-0.11.12 → collate_data_diff-0.11.14}/data_diff/databases/mssql.py +0 -0
  25. {collate_data_diff-0.11.12 → collate_data_diff-0.11.14}/data_diff/databases/mysql.py +0 -0
  26. {collate_data_diff-0.11.12 → collate_data_diff-0.11.14}/data_diff/databases/oracle.py +0 -0
  27. {collate_data_diff-0.11.12 → collate_data_diff-0.11.14}/data_diff/databases/postgresql.py +0 -0
  28. {collate_data_diff-0.11.12 → collate_data_diff-0.11.14}/data_diff/databases/presto.py +0 -0
  29. {collate_data_diff-0.11.12 → collate_data_diff-0.11.14}/data_diff/databases/redshift.py +0 -0
  30. {collate_data_diff-0.11.12 → collate_data_diff-0.11.14}/data_diff/databases/trino.py +0 -0
  31. {collate_data_diff-0.11.12 → collate_data_diff-0.11.14}/data_diff/databases/vertica.py +0 -0
  32. {collate_data_diff-0.11.12 → collate_data_diff-0.11.14}/data_diff/dbt.py +0 -0
  33. {collate_data_diff-0.11.12 → collate_data_diff-0.11.14}/data_diff/dbt_config_validators.py +0 -0
  34. {collate_data_diff-0.11.12 → collate_data_diff-0.11.14}/data_diff/dbt_parser.py +0 -0
  35. {collate_data_diff-0.11.12 → collate_data_diff-0.11.14}/data_diff/format.py +0 -0
  36. {collate_data_diff-0.11.12 → collate_data_diff-0.11.14}/data_diff/hashdiff_tables.py +0 -0
  37. {collate_data_diff-0.11.12 → collate_data_diff-0.11.14}/data_diff/info_tree.py +0 -0
  38. {collate_data_diff-0.11.12 → collate_data_diff-0.11.14}/data_diff/joindiff_tables.py +0 -0
  39. {collate_data_diff-0.11.12 → collate_data_diff-0.11.14}/data_diff/lexicographic_space.py +0 -0
  40. {collate_data_diff-0.11.12 → collate_data_diff-0.11.14}/data_diff/parse_time.py +0 -0
  41. {collate_data_diff-0.11.12 → collate_data_diff-0.11.14}/data_diff/py.typed +0 -0
  42. {collate_data_diff-0.11.12 → collate_data_diff-0.11.14}/data_diff/queries/__init__.py +0 -0
  43. {collate_data_diff-0.11.12 → collate_data_diff-0.11.14}/data_diff/queries/api.py +0 -0
  44. {collate_data_diff-0.11.12 → collate_data_diff-0.11.14}/data_diff/queries/ast_classes.py +0 -0
  45. {collate_data_diff-0.11.12 → collate_data_diff-0.11.14}/data_diff/queries/base.py +0 -0
  46. {collate_data_diff-0.11.12 → collate_data_diff-0.11.14}/data_diff/queries/extras.py +0 -0
  47. {collate_data_diff-0.11.12 → collate_data_diff-0.11.14}/data_diff/query_utils.py +0 -0
  48. {collate_data_diff-0.11.12 → collate_data_diff-0.11.14}/data_diff/schema.py +0 -0
  49. {collate_data_diff-0.11.12 → collate_data_diff-0.11.14}/data_diff/table_segment.py +0 -0
  50. {collate_data_diff-0.11.12 → collate_data_diff-0.11.14}/data_diff/thread_utils.py +0 -0
  51. {collate_data_diff-0.11.12 → collate_data_diff-0.11.14}/data_diff/tracking.py +0 -0
  52. {collate_data_diff-0.11.12 → collate_data_diff-0.11.14}/data_diff/utils.py +0 -0
  53. {collate_data_diff-0.11.12 → collate_data_diff-0.11.14}/data_diff/version.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: collate-data-diff
3
- Version: 0.11.12
3
+ Version: 0.11.14
4
4
  Summary: Command-line tool and Python library to efficiently diff rows across two different databases.
5
5
  License: MIT
6
6
  License-File: LICENSE
@@ -160,6 +160,7 @@ class Snowflake(Database):
160
160
  format=serialization.PrivateFormat.PKCS8,
161
161
  encryption_algorithm=serialization.NoEncryption(),
162
162
  )
163
+ kw.pop("private_key_passphrase", None)
163
164
 
164
165
  self._conn = snowflake.connector.connect(schema=f'"{schema}"', **kw)
165
166
 
@@ -11,13 +11,13 @@ from concurrent.futures import ThreadPoolExecutor, as_completed
11
11
 
12
12
  import attrs
13
13
 
14
- from data_diff.errors import DataDiffMismatchingKeyTypesError
14
+ from data_diff.errors import DataDiffMismatchingKeyTypesError, DataDiffUnsupportedKeyValueError
15
15
  from data_diff.info_tree import InfoTree, SegmentInfo
16
16
  from data_diff.utils import dbt_diff_string_template, run_as_daemon, safezip, getLogger, truncate_error, Vector
17
17
  from data_diff.thread_utils import ThreadedYielder
18
18
  from data_diff.table_segment import TableSegment, create_mesh_from_points
19
19
  from data_diff.tracking import create_end_event_json, create_start_event_json, send_event_json, is_tracking_enabled
20
- from data_diff.abcs.database_types import IKey
20
+ from data_diff.abcs.database_types import IKey, ColType_Alphanum
21
21
 
22
22
  logger = getLogger(__name__)
23
23
 
@@ -309,7 +309,7 @@ class TableDiffer(ThreadBase, ABC):
309
309
  key_ranges = self._threaded_call_as_completed("query_key_range", [table1, table2])
310
310
 
311
311
  # Start with the first completed value, so we don't waste time waiting
312
- min_key1, max_key1 = self._parse_key_range_result(key_types1, next(key_ranges))
312
+ min_key1, max_key1 = self._parse_key_range_result(key_types1, next(key_ranges), table1.key_columns)
313
313
 
314
314
  btable1 = table1.new_key_bounds(min_key=min_key1, max_key=max_key1, key_types=key_types1)
315
315
  btable2 = table2.new_key_bounds(min_key=min_key1, max_key=max_key1, key_types=key_types2)
@@ -337,7 +337,7 @@ class TableDiffer(ThreadBase, ABC):
337
337
  # Overall, the max number of new regions in this 2nd pass is 3^|k| - 1
338
338
 
339
339
  # Note: python types can be the same, but the rendering parameters (e.g. casing) can differ.
340
- min_key2, max_key2 = self._parse_key_range_result(key_types2, next(key_ranges))
340
+ min_key2, max_key2 = self._parse_key_range_result(key_types2, next(key_ranges), table2.key_columns)
341
341
 
342
342
  points = [list(sorted(p)) for p in safezip(min_key1, min_key2, max_key1, max_key2)]
343
343
  box_mesh = create_mesh_from_points(*points)
@@ -351,18 +351,48 @@ class TableDiffer(ThreadBase, ABC):
351
351
 
352
352
  return ti
353
353
 
354
- def _parse_key_range_result(self, key_types, key_range) -> Tuple[Vector, Vector]:
354
+ def _parse_key_range_result(self, key_types, key_range, key_columns=None) -> Tuple[Vector, Vector]:
355
355
  min_key_values, max_key_values = key_range
356
356
 
357
357
  # We add 1 because our ranges are exclusive of the end (like in Python)
358
358
  try:
359
359
  min_key = Vector(key_type.make_value(mn) for key_type, mn in safezip(key_types, min_key_values))
360
360
  max_key = Vector(key_type.make_value(mx) + 1 for key_type, mx in safezip(key_types, max_key_values))
361
- except (TypeError, ValueError) as e:
361
+ except ValueError as e:
362
+ # A key value could not be mapped to a bisectable range. For an
363
+ # alphanumeric string key this means its min/max contain characters
364
+ # outside data-diff's bisection alphabet (e.g. '.', accents, other
365
+ # non-ASCII) — such keys can't be bisected safely, so we surface an
366
+ # actionable message. Other value errors (e.g. a malformed UUID) keep
367
+ # the original generic error.
368
+ raise self._key_range_error(key_types, min_key_values, max_key_values, key_columns) from e
369
+ except TypeError as e:
362
370
  raise type(e)(f"Cannot apply {key_types} to '{min_key_values}', '{max_key_values}'.") from e
363
371
 
364
372
  return min_key, max_key
365
373
 
374
+ @staticmethod
375
+ def _key_range_error(key_types, min_key_values, max_key_values, key_columns) -> ValueError:
376
+ names = list(key_columns) if key_columns else [f"#{i}" for i in range(len(key_types))]
377
+ offenders = []
378
+ for name, key_type, mn, mx in safezip(names, key_types, min_key_values, max_key_values):
379
+ if not isinstance(key_type, ColType_Alphanum):
380
+ continue # only alphanum keys get the tailored, actionable guidance
381
+ for bound, value in (("min", mn), ("max", mx)):
382
+ try:
383
+ key_type.make_value(value)
384
+ except ValueError as inner:
385
+ offenders.append(f"column '{name}' ({bound}={value!r}): {inner}")
386
+ if offenders:
387
+ return DataDiffUnsupportedKeyValueError(
388
+ f"Cannot bisect on key {'; '.join(offenders)}. data-diff can only bisect string keys built "
389
+ "from spaces, hyphens, underscores, digits and unaccented ASCII letters (a-z, A-Z); keys "
390
+ "containing other characters (e.g. '.', accented or non-ASCII characters) cannot be mapped to "
391
+ "a comparable range. Use a numeric or UUID key column for this diff instead."
392
+ )
393
+ # Non-alphanum failure (e.g. a malformed UUID): preserve the original error.
394
+ return ValueError(f"Cannot apply {key_types} to '{min_key_values}', '{max_key_values}'.")
395
+
366
396
  def _bisect_and_diff_segments(
367
397
  self,
368
398
  ti: ThreadedYielder,
@@ -72,3 +72,9 @@ class DataDiffSimpleSelectNotFound(Exception):
72
72
 
73
73
  class DataDiffMismatchingKeyTypesError(Exception):
74
74
  "Raised when the key types of two tables do not match, like VARCHAR and INT."
75
+
76
+
77
+ # Subclasses ValueError because it replaces the ValueError previously raised from
78
+ # _parse_key_range_result — keeps the exception contract for existing callers.
79
+ class DataDiffUnsupportedKeyValueError(ValueError):
80
+ "Raised when a key column holds values that cannot be mapped to a bisectable range (e.g. text keys with '.', accents or other non-ASCII characters)."
@@ -1,6 +1,6 @@
1
1
  [tool.poetry]
2
2
  name = "collate-data-diff"
3
- version = "0.11.12"
3
+ version = "0.11.14"
4
4
  description = "Command-line tool and Python library to efficiently diff rows across two different databases."
5
5
  authors = ["Collate <info@getcollate.io>", "Datafold <data-diff@datafold.com>"]
6
6
  license = "MIT"