SQL Ranger: Enforcer of partitioning or finite-range check presence in SQL Queries
Project description
SQL Ranger
Enforcer of finite-range partitioning checks presence in SQL Queries.
Purpose
When dealing with big historical tables that may contain terabytes of data, it's a common practice to partition them based on a day or an hour. It's also highly desirable to write queries in a way that absolutely avoids full scans. This project helps check and ensure that queries have explicit finite boundaries on the values of partitioning columns.
Intended usages that drive the development of this project:
- In combination with query plan complexity estimates (or actual cost post-execution), it can be used to alert about queries that are not effectively utilizing partitions.
- The result can be used as feedback to the SQL-generating LLM agent to help it with generating partitioning-aware queries.
Partition Usage Validation
The PartitionChecker validates SQL queries to ensure they properly use partitioning on large tables.
Why Partition Validation?
Large partitioned tables should always be queried with partition filters to:
- Limit the amount of data scanned
- Improve query performance dramatically
- Prevent accidental full table scans
Usage
Installation
pip install sqlranger==0.0.2
Basic Validation
To ensure explicit partition filtering on a sales_history table partitioned by the day column:
from sqlranger import check_partition_usage, TablePartition, PartitionViolationType
# Basic validation with TablePartition
sql = """
SELECT day, count(*) AS total
FROM gridhive.fact.sales_history
WHERE day = '2021-09-13'
"""
violations = check_partition_usage(
sql,
partitioned_tables=[TablePartition("sales_history", ["day"])]
)
if not violations:
print("✓ All partitioned tables have proper filtering")
else:
for violation in violations:
print(f"✗ {violation.message}")
Hierarchical Partitions
Use TablePartition with multiple columns to enforce hierarchical partitioning:
from sqlranger import TablePartition, PartitionChecker
# Configure hierarchical partitions: City > Warehouse > BuildingNumber
partition_config = TablePartition(
"warehouse.inventory",
partitions=["City", "Warehouse", "BuildingNumber"],
enforced_level=2 # Only enforce City and Warehouse, BuildingNumber is optional
)
checker = PartitionChecker(partitioned_tables=[partition_config])
# This query is valid - both City and Warehouse are filtered
sql = """
SELECT * FROM warehouse.inventory
WHERE City = 'Seattle' AND Warehouse = 'W1' AND product_id = 100
"""
violations = checker.find_violations(sql)
# violations will be empty
# This query is invalid - missing Warehouse filter
sql = """
SELECT * FROM warehouse.inventory
WHERE City = 'Seattle' AND product_id = 100
"""
violations = checker.find_violations(sql)
# violations will contain an error about missing 'Warehouse' filter
Date Range Limits
Use DateTablePartition to enforce different maximum date ranges for different tables:
from datetime import timedelta
from sqlranger import PartitionChecker, DateTablePartition, DatePartitionColumn
# Configure different max date ranges per table
partition_cols = [
DateTablePartition(
"gridhive.fact.sales_history",
partitions=[DatePartitionColumn("day", "YYYY-mm-dd")],
max_date_range=timedelta(days=30)
),
DateTablePartition(
"events.log_table",
partitions=[DatePartitionColumn("event_time", "YYYY-mm-dd")],
max_date_range=timedelta(days=7)
),
]
checker = PartitionChecker(partitioned_tables=partition_cols)
# This query will have a violation for log_table (15 days > 7 max)
# but not for sales_history (15 days <= 30 max)
sql = """
SELECT a.day, b.event_time
FROM gridhive.fact.sales_history a
JOIN events.log_table b ON a.day = b.event_time
WHERE a.day BETWEEN '2021-09-01' AND '2021-09-15'
AND b.event_time BETWEEN '2021-09-01' AND '2021-09-15'
"""
violations = checker.find_violations(sql)
# violations will contain one entry for log_table only
Hierarchical Date Partitions
Configure hierarchical date partitions for tables partitioned by year, month, day, and hour:
from datetime import timedelta
from sqlranger import DateTablePartition, DatePartitionColumn
# Hierarchical date partitions: year > month > day > hour
partition_config = DateTablePartition(
"gridhive.fact.sales_history",
partitions=[
DatePartitionColumn("year", "YYYY"),
DatePartitionColumn("month", "mm"),
DatePartitionColumn("day", "dd"),
DatePartitionColumn("hour", "HH")
],
enforced_level=4, # All 4 levels must be specified
max_date_range=timedelta(days=1, hours=12) # Max 1.5 days
)
# This query is valid - all 4 partition levels are filtered
sql = """
SELECT * FROM gridhive.fact.sales_history
WHERE year = 2021 AND month = 9 AND day = 13 AND hour >= 10 AND hour <= 15
"""
Configuration Classes
TablePartition
TablePartition is the base class for defining partition configuration. It specifies:
- The full table name (including schema/catalog if applicable)
- An ordered list of partition column names (from root to smallest sub-partition)
- Optional
enforced_levelparameter (int) specifying how many levels are enforced
Example:
from sqlranger import TablePartition
# Simple single-level partition
tp = TablePartition("sales_history", ["day"])
# Hierarchical partition with 3 levels, only 2 enforced
tp = TablePartition(
"warehouse.inventory",
partitions=["City", "Warehouse", "BuildingNumber"],
enforced_level=2 # Only City and Warehouse are enforced
)
DateTablePartition
DateTablePartition extends TablePartition with additional date-specific configuration:
partitions: List ofDatePartitionColumnobjects (each with column name and format pattern)enforced_level: Optional number of partition levels to enforcemax_date_range: Optional maximum allowed date range as timedelta object
Example:
from datetime import timedelta
from sqlranger import DateTablePartition, DatePartitionColumn
# Single date partition with max range
dtp = DateTablePartition(
"gridhive.fact.sales_history",
partitions=[DatePartitionColumn("day", "YYYY-mm-dd")],
max_date_range=timedelta(days=30)
)
# Hierarchical date partition
dtp = DateTablePartition(
"gridhive.fact.sales_history",
partitions=[
DatePartitionColumn("year", "YYYY"),
DatePartitionColumn("month", "mm"),
DatePartitionColumn("day", "dd")
],
enforced_level=3, # All 3 levels enforced
max_date_range=timedelta(days=90)
)
DatePartitionColumn
DatePartitionColumn is a dataclass that represents a single date partition column:
column_name: Name of the partition columnformat_pattern: Date format pattern
Supported Format Patterns:
| Pattern | Description | Example Value |
|---|---|---|
YYYY |
4-digit year | 2021 |
Y |
Year (short form) | 2021 |
mm |
2-digit month | 09 |
m |
Month (short form) | 9 |
dd |
2-digit day | 13 |
d |
Day (short form) | 13 |
HH |
2-digit hour (24-hour format) | 14 |
H |
Hour (short form) | 14 |
MM |
2-digit minute | 03 |
M |
Minutes (short form) | 3 |
SS |
2-digit second | 45 |
S |
Second (short form) | 45 |
YYYY-mm-dd |
Full date string | 2021-09-13 |
YYYY-mm |
Year-month string | 2021-09 |
Notes:
- Format patterns are case-sensitive for disambiguation (e.g.,
mmfor month vsMMfor minute) - Composite patterns like
YYYY-mm-ddare parsed as complete date strings - When using individual components, each partition column should have its own
DatePartitionColumn
Example:
from sqlranger import DatePartitionColumn
# Define individual date partition columns
year_col = DatePartitionColumn("year", "YYYY")
month_col = DatePartitionColumn("month", "mm")
day_col = DatePartitionColumn("day", "dd")
hour_col = DatePartitionColumn("hour", "HH")
# Or use a full date format for a single column
full_date_col = DatePartitionColumn("day", "YYYY-mm-dd")
Validation Rules
The partition checker enforces these rules:
- Partition Filter Required: Any query using a partitioned table must include filters for all enforced partition columns in the WHERE clause
- Raw Column Only: Partition columns must be used without functions (e.g.,
day = '2021-09-13'is OK, butDATE_FORMAT(day, '%Y-%m')breaks partitioning) - Finite Range: Queries must define a finite range for each partition column using:
column = 'value'(single value)column BETWEEN 'start' AND 'end'- Both
column >= 'start'ANDcolumn <= 'end'
- Optional Max Range: When
max_date_rangeis configured (viaDateTablePartition), it enforces a maximum date range (best-effort estimation) - Hierarchical Enforcement: All partition columns from the root to
enforced_levelmust be filtered
PartitionViolationType
| Violation | Description |
|---|---|
MISSING_PARTITION_FILTER |
Query doesn't have a required partition column filter in the WHERE clause |
PARTITION_COLUMN_WITH_FUNCTION |
Partition column is wrapped in a function (breaks partitioning) |
NO_FINITE_RANGE |
Query doesn't define a finite range for a partition column |
EXCESSIVE_DATE_RANGE |
Date range exceeds the configured maximum |
PartitionViolation
find_violations returns a list of PartitionViolation objects, each containing:
violation: Type of violation, seePartitionViolationTypeenummessage: Human-or-LLM-readable description of the violationtable_name: Name of the table with the violationestimated_range: Estimated excessive duration of time range forEXCESSIVE_DATE_RANGEviolation type
Return Values
The validation functions return a list of PartitionViolation objects:
- Empty list: All partitioned tables are properly filtered (no violations)
- Non-empty list: Contains violation details for each table that fails validation
Example Validation Results
Valid Query (no violations returned):
SELECT * FROM gridhive.fact.sales_history
WHERE day BETWEEN '2021-09-13' AND '2021-09-26'
Returns: [] (empty list - no violations)
Invalid Query (function on day):
SELECT * FROM gridhive.fact.sales_history
WHERE DATE_FORMAT(day, '%Y-%M') = '2021-09'
Returns violation: PARTITION_COLUMN_WITH_FUNCTION - Table 'sales_history' uses 'day' column with a function, which disables partitioning.
Invalid Query (no upper bound):
SELECT * FROM gridhive.fact.sales_history
WHERE day >= '2021-09-13'
Returns violation: NO_FINITE_RANGE - Table 'sales_history' does not have a finite date range
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file sqlranger-0.0.3.tar.gz.
File metadata
- Download URL: sqlranger-0.0.3.tar.gz
- Upload date:
- Size: 17.7 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.11.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
de1f8a70844057a82569f641bb67b4c2347dba9d864700ab1a5eb9e7d200b4ed
|
|
| MD5 |
87557df63810d7569450039b7d933dc9
|
|
| BLAKE2b-256 |
eac84f5e127b1414b42ac6d833566c44f96cc526f9e985920bfb0d2530cca987
|
File details
Details for the file sqlranger-0.0.3-py3-none-any.whl.
File metadata
- Download URL: sqlranger-0.0.3-py3-none-any.whl
- Upload date:
- Size: 15.0 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.11.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
8c60ebc0255bed2c6fc1fe629347b3cceae38cf27ba8b59ef7571f63a17ad355
|
|
| MD5 |
3540e93cce0e310aba6c6033c69b0b23
|
|
| BLAKE2b-256 |
2a25cffcfb99a0ea55bbd2b8f87b79c9c5b90d5c1d340bb9eddf8c20998593ba
|