Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion docs/dqx/docs/reference/quality_checks.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ You can also define your own custom checks in Python (see [Creating custom check
| `regex_match` | Checks whether the values in the input column match a given regex. | `column`: column to check (can be a string column name or a column expression); regex: regex to check; `negate`: if the condition should be negated (true) or not |
| `is_valid_email` | Checks whether the values in the input column have valid email address format. | `column`: column to check (can be a string column name or a column expression) |
| `has_valid_string_case` | Checks whether string values match the requested letter case: `upper` requires all alphabetic characters to be uppercase; `lower` requires all alphabetic characters to be lowercase; `title` requires the first character of each space-delimited word to be uppercase; `sentence` requires each period-delimited segment's first non-whitespace character to be uppercase. | `column`: column to check (can be a string column name or a column expression); `case`: one of `upper`, `lower`, `title`, or `sentence` |
| `is_valid_national_id` | Checks whether the values in the input column are valid national identification numbers (e.g., US Social Security Numbers) for the given country. | `column`: column to check (can be a string column name or a column expression); `country`: ISO 3166 alpha-2 country code (optional, default: `US`) |
| `is_valid_national_id` | Checks whether the values in the input column match a supported national identification number format. Supports US Social Security Numbers (`US`), UK National Insurance numbers (`GB`), and Indian Permanent Account Numbers (`IN`). These are format checks only; they do not verify whether an identifier was issued. | `column`: column to check (can be a string column name or a column expression); `country`: ISO 3166 alpha-2 country code (optional, default: `US`) |
| `is_valid_uuid` | Checks whether the values in the input column are valid UUIDs (RFC 9562, canonical 8-4-4-4-12 hyphenated hex form). By default validates the shape only; set `strict` to also enforce the version nibble (1-8) and variant bits per RFC 9562. | `column`: column to check (can be a string column name or a column expression); `strict`: if True, also validate the version nibble (1-8) and variant bits (8/9/a/b) per RFC 9562 (default: False) |
| `is_valid_country_code` | Checks whether the values in the input column are valid ISO 3166-1 country codes (alpha-2, e.g. US, alpha-3, e.g. USA, or numeric, e.g. 840). Source: https://www.iso.org/iso-3166-country-codes.html | `column`: column to check (can be a string column name or a column expression); `code_format`: ISO 3166-1 representation, `alpha-2` (default), `alpha-3`, or `numeric`; `case_sensitive`: optional boolean flag for case-sensitive comparison (default: True) |
| `is_valid_currency_code` | Checks whether the values in the input column are valid ISO 4217 currency codes (alphabetic, e.g. USD, or numeric, e.g. 840). Source: https://www.iso.org/iso-4217-currency-codes.html | `column`: column to check (can be a string column name or a column expression); `code_format`: ISO 4217 representation, `alphabetic` (default) or `numeric`; `case_sensitive`: optional boolean flag for case-sensitive comparison (default: True) |
Expand Down
27 changes: 21 additions & 6 deletions src/databricks/labs/dqx/check_funcs.py
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,15 @@ class DQPattern(Enum):
# none) must be consistent via backreference \1. Excludes invalid ranges - area
# 000/666/9xx (9xx covers ITINs), group 00, serial 0000. Anchored, fixed-width; ReDoS-safe.
SSN_US = r"\A(?!000|666|9\d{2})\d{3}([- ]?)(?!00)\d{2}\1(?!0000)\d{4}\z"
# UK National Insurance Number: two-letter prefix, six digits, and an A-D
# suffix. Exclude prefixes that HMRC does not allocate.
NINO_GB = (
r"\A(?!(?:BG|GB|KN|NK|NT|TN|ZZ))(?!(?:[DFIQUV]))[A-Z]"
r"(?![DFIOQUV])[A-Z] ?\d{2} ?\d{2} ?\d{2} ?[ABCD]\z"
)
# Indian Permanent Account Number (PAN): five letters, four digits, and a
# final letter.
PAN_IN = r"\A[A-Z]{5}\d{4}[A-Z]\z"
Comment thread
AtomicGlance marked this conversation as resolved.
Outdated

# Canonical UUID form per RFC 9562: 8-4-4-4-12 hex groups. UUID validates the shape
# only, so RFC-defined Nil/Max sentinels and legacy variant GUIDs pass; UUID_STRICT
Expand All @@ -130,6 +139,8 @@ class DQPattern(Enum):
# alpha-2 code here.
_NATIONAL_ID_PATTERNS_BY_COUNTRY: dict[str, DQPattern] = {
"US": DQPattern.SSN_US,
"GB": DQPattern.NINO_GB,
"IN": DQPattern.PAN_IN,
}


Expand Down Expand Up @@ -1173,12 +1184,16 @@ def is_valid_national_id(column: str | Column, country: str = "US") -> Column:
Validation is limited to *format* and *number ranges*; it does not verify that a
number was actually issued.

Supported countries are keyed by ISO 3166 alpha-2 code. Currently only *US* is
supported: the *AAA-GG-SSSS* form is required, where the separators may be all
hyphens, all single spaces, or omitted entirely (e.g. *123-45-6789*, *123 45 6789*
or *123456789*), but must be used consistently. Structurally invalid ranges are
rejected (area *000*, *666* and *900-999* - the latter covering ITINs; group *00*;
serial *0000*).
Supported countries are keyed by ISO 3166 alpha-2 code. For *US*, the
*AAA-GG-SSSS* form is required, where the separators may be all hyphens, all
single spaces, or omitted entirely (e.g. *123-45-6789*, *123 45 6789* or
*123456789*), but must be used consistently. Structurally invalid ranges are
rejected (area *000*, *666* and *900-999* - the latter covering ITINs; group
*00*; serial *0000*). For *GB*, a National Insurance number consists of two
letters, six digits, and a final *A*, *B*, *C*, or *D*; unallocated prefixes
are rejected. For *IN*, a PAN consists of five letters, four digits, and a
final letter. These checks validate format only, not whether an identifier
was issued.

Null values will pass the check with no violation reported.

Expand Down
34 changes: 34 additions & 0 deletions tests/unit/test_row_checks.py
Comment thread
AtomicGlance marked this conversation as resolved.
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
import re
from typing import cast
import pytest
from databricks.labs.dqx.utils import get_column_name_or_alias
from databricks.labs.dqx.check_funcs import (
DQPattern,
_pattern_for_python_re,
is_equal_to,
is_not_equal_to,
is_in_range,
Expand Down Expand Up @@ -197,6 +200,37 @@ def test_is_valid_national_id_country_is_case_insensitive():
assert get_column_name_or_alias(result) == "a_does_not_match_pattern_ssn_us"


def test_is_valid_national_id_supports_uk_nino():
result = is_valid_national_id("a", country="GB")
assert get_column_name_or_alias(result) == "a_does_not_match_pattern_nino_gb"


def test_is_valid_national_id_supports_indian_pan():
result = is_valid_national_id("a", country="IN")
assert get_column_name_or_alias(result) == "a_does_not_match_pattern_pan_in"


@pytest.mark.parametrize(
"value",
["AB123456A", "AB 12 34 56 A", "BX586745C"],
)
def test_nino_pattern_accepts_valid_formats(value):
assert re.fullmatch(_pattern_for_python_re(DQPattern.NINO_GB), value)


@pytest.mark.parametrize("value", ["DF123456A", "BG123456A", "AB123456E"])
def test_nino_pattern_rejects_invalid_prefixes_and_suffixes(value):
assert not re.fullmatch(_pattern_for_python_re(DQPattern.NINO_GB), value)


def test_pan_pattern_accepts_ten_character_format():
assert re.fullmatch(_pattern_for_python_re(DQPattern.PAN_IN), "ABCDE1234F")


def test_pan_pattern_rejects_wrong_character_positions():
assert not re.fullmatch(_pattern_for_python_re(DQPattern.PAN_IN), "AB12E1234F")


def test_is_valid_national_id_missing_country():
with pytest.raises(MissingParameterError, match="'country' is not provided."):
is_valid_national_id("a", country=None)
Expand Down