-
Notifications
You must be signed in to change notification settings - Fork 24
feat: Add distribution graph below 20 distinct count [#DCS-1244] #352
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
d859779
a11a16f
7350c04
13ffe95
e2f9cf0
d8c7045
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -331,12 +331,16 @@ def build_table_metrics_query( | |
| for col in column_info: | ||
| name = col["column_name"] | ||
| dtype = col["data_type"].lower() | ||
| quoted = self.quote_column(name) | ||
|
|
||
| if dtype in ("json", "jsonb"): | ||
| distinct_expr = f"{quoted}::text" | ||
| else: | ||
| distinct_expr = f"{quoted}" | ||
|
|
||
| query_parts.append(f'COUNT(DISTINCT {distinct_expr}) AS "{name}_distinct"') | ||
| query_parts.append( | ||
| f'COUNT(DISTINCT {self.quote_column(name)}) AS "{name}_distinct"' | ||
| ) | ||
| query_parts.append( | ||
| f'COUNT(*) - COUNT(DISTINCT {self.quote_column(name)}) AS "{name}_duplicate"' | ||
| f'COUNT(*) - COUNT(DISTINCT {distinct_expr}) AS "{name}_duplicate"' | ||
| ) | ||
| query_parts.append( | ||
| f'SUM(CASE WHEN {self.quote_column(name)} IS NULL THEN 1 ELSE 0 END) AS "{name}_is_null"' | ||
|
|
@@ -415,6 +419,62 @@ def _normalize_metrics(value): | |
| col_metrics[metric_name] = _normalize_metrics(value) | ||
|
|
||
| column_wise.append({"column_name": name, "metrics": col_metrics}) | ||
|
|
||
| for col_data in column_wise: | ||
| metrics = col_data["metrics"] | ||
| distinct_count = metrics.get("distinct") | ||
| col_name = col_data["column_name"] | ||
| dtype = next( | ||
| c["data_type"].lower() | ||
| for c in column_info | ||
| if c["column_name"] == col_name | ||
| ) | ||
|
|
||
| if isinstance(distinct_count, (int, float)) and distinct_count < 20: | ||
| quoted = self.quote_column(col_name) | ||
|
|
||
| if dtype in ("json", "jsonb"): | ||
| group_expr = f"{quoted}::text" | ||
| else: | ||
| group_expr = quoted | ||
|
|
||
| dist_query = ( | ||
| f"SELECT {group_expr}, COUNT(*) " | ||
| f"FROM {qualified_table} GROUP BY {group_expr} ORDER BY COUNT(*) DESC" | ||
| ) | ||
|
Comment on lines
+441
to
+444
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧩 Analysis chain🏁 Script executed: #!/bin/bash
# First, let's locate and examine the postgres.py file around lines 446-449
cat -n dcs_core/integrations/databases/postgres.py | sed -n '430,470p'Repository: datachecks/dcs-core Length of output: 1865 🏁 Script executed: #!/bin/bash
# Find the quote_column implementation in postgres.py
rg -n "def quote_column" dcs_core/integrations/databases/postgres.py -A 5Repository: datachecks/dcs-core Length of output: 260 🏁 Script executed: #!/bin/bash
# Check for quote_column implementations across all datasources
rg -n "def quote_column" dcs_core/integrations/databases/ -A 3 --type pyRepository: datachecks/dcs-core Length of output: 2268 🏁 Script executed: #!/bin/bash
# Check for logging imports in postgres.py
rg -n "^import|^from" dcs_core/integrations/databases/postgres.py | head -20Repository: datachecks/dcs-core Length of output: 502 🏁 Script executed: #!/bin/bash
# Find the function containing this code to understand column_info origin
rg -n "def.*distribution" dcs_core/integrations/databases/postgres.py -B 5 -A 40 | head -60Repository: datachecks/dcs-core Length of output: 45 🏁 Script executed: #!/bin/bash
# Get full quote_column implementation in postgres.py
sed -n '69,80p' dcs_core/integrations/databases/postgres.pyRepository: datachecks/dcs-core Length of output: 393 🏁 Script executed: #!/bin/bash
# Get quote_column implementations from other datasources to compare
echo "=== MySQL quote_column ===" && sed -n '87,95p' dcs_core/integrations/databases/mysql.py
echo ""
echo "=== Oracle quote_column ===" && sed -n '83,91p' dcs_core/integrations/databases/oracle.pyRepository: datachecks/dcs-core Length of output: 605 🏁 Script executed: #!/bin/bash
# Search for the function containing the distribution graph code
rg -n "def.*" dcs_core/integrations/databases/postgres.py | rg -B 5 -A 5 "430:"Repository: datachecks/dcs-core Length of output: 45 🏁 Script executed: #!/bin/bash
# Find what function contains line 430
awk '/^[[:space:]]*(def|class) / { func=$0; line=NR } NR==430 { print func " at line " line }' dcs_core/integrations/databases/postgres.pyRepository: datachecks/dcs-core Length of output: 232 🏁 Script executed: #!/bin/bash
# Better approach: find function by looking backwards from line 430
sed -n '1,430p' dcs_core/integrations/databases/postgres.py | tail -50 | grep -n "def "</method> to see function nameRepository: datachecks/dcs-core Length of output: 116 🏁 Script executed: #!/bin/bash
# Find the function containing line 430 by searching backwards
sed -n '400,430p' dcs_core/integrations/databases/postgres.py | grep -E "^\s*(def|class) " | tail -1Repository: datachecks/dcs-core Length of output: 45 🏁 Script executed: #!/bin/bash
# Alternative: look at more context before line 430
sed -n '350,435p' dcs_core/integrations/databases/postgres.py | head -100Repository: datachecks/dcs-core Length of output: 3081 🏁 Script executed: #!/bin/bash
# Search for where column_info is used and its origin
rg -n "column_info" dcs_core/integrations/databases/postgres.py -B 3 -A 3 | head -40Repository: datachecks/dcs-core Length of output: 1403 🏁 Script executed: #!/bin/bash
# Check if logging is used elsewhere in postgres.py for error handling
rg -n "logging\.|logger\." dcs_core/integrations/databases/postgres.pyRepository: datachecks/dcs-core Length of output: 45 🏁 Script executed: #!/bin/bash
# Check for other exception handlers using print() in postgres.py
rg -n "except.*:|print\(" dcs_core/integrations/databases/postgres.py | grep -A 2 "except"Repository: datachecks/dcs-core Length of output: 280 🏁 Script executed: #!/bin/bash
# Check how other datasources handle exceptions (logging vs print)
rg -n "except.*:|print\(|logging\." dcs_core/integrations/databases/mysql.py | head -20Repository: datachecks/dcs-core Length of output: 131 🏁 Script executed: #!/bin/bash
# Check if logging is imported/used in the base SQLDataSource class
rg -n "import logging|logger" dcs_core/core/datasource/sql_datasource.py | head -10Repository: datachecks/dcs-core Length of output: 477 🏁 Script executed: #!/bin/bash
# Check what the exception handlers in mysql.py do
sed -n '72,80p' dcs_core/integrations/databases/mysql.py
sed -n '423,430p' dcs_core/integrations/databases/mysql.pyRepository: datachecks/dcs-core Length of output: 647 Use loguru logger instead of print() for exception handling; SQL injection risk is low but inherent to quote_column pattern. The distribution graph query at lines 446-449 has two concerns:
🧰 Tools🪛 Ruff (0.14.6)447-448: Possible SQL injection vector through string-based query construction (S608) 🤖 Prompt for AI Agents |
||
|
|
||
| try: | ||
| dist_result = self.connection.execute(text(dist_query)).fetchall() | ||
|
|
||
| distribution = [] | ||
| for r in dist_result: | ||
| val = _normalize_metrics(r[0]) | ||
| distribution.append( | ||
| { | ||
| "col_val": val, | ||
| "count": r[1], | ||
| } | ||
| ) | ||
|
|
||
| metrics["distribution_graph"] = distribution | ||
|
|
||
| except Exception as e: | ||
| print( | ||
| f"Failed to generate distribution graph for column {col_name}: {e}" | ||
| ) | ||
|
|
||
| for col_data in column_wise: | ||
| metrics = col_data["metrics"] | ||
| formatted_metrics_data = { | ||
| "general_data": { | ||
| key: value | ||
| for key, value in metrics.items() | ||
| if key != "distribution_graph" | ||
| }, | ||
| "distribution_data": metrics.get("distribution_graph", []), | ||
| } | ||
| col_data["metrics"] = formatted_metrics_data | ||
|
|
||
| return column_wise | ||
|
|
||
| def get_table_foreign_key_info(self, table_name: str, schema: str | None = None): | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Add a default to prevent StopIteration exception.
The
next()call without a default value will raiseStopIterationifcol_nameis not found incolumn_info. While this scenario should not occur under normal circumstances, defensive coding warrants a fallback.Apply this diff to add a default:
📝 Committable suggestion
🤖 Prompt for AI Agents