Skip to content

Commit ef2948a

Browse files
Add SKILL.md files for connection debugging, core run, and output analysis
1 parent d2e07e1 commit ef2948a

File tree

4 files changed

+282
-0
lines changed

4 files changed

+282
-0
lines changed
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
---
2+
name: sqlcompare-connection-debug
3+
description: Debug SQLCompare connection and credential problems, especially connector resolution, environment variables, YAML configs, and permission issues. Use when a run fails to connect or create comparison tables.
4+
---
5+
6+
# SQLCompare Connection Debug
7+
8+
## Overview
9+
Diagnose connection failures by validating connector resolution, credentials, and required privileges. Focus on issues around `-c` selectors, environment variables, and YAML config files.
10+
11+
## Quick Triage
12+
1. Capture the exact error text from the failing command.
13+
2. Verify the command and connector name used (for example, `-c snowflake_prod`).
14+
3. Run a simple connectivity check with `sqlcompare query` if possible.
15+
16+
## Connector Resolution Order
17+
SQLCompare resolves connections in this order.
18+
1. Default connector (if `-c` is omitted).
19+
2. Direct URL passed in the command.
20+
3. Environment variables: `SQLCOMPARE_CONN_DEFAULT`, `SQLCOMPARE_CONN_<NAME>`.
21+
4. YAML file: `~/.sqlcompare/connections.yml`.
22+
23+
## Environment Variable Checklist
24+
1. Confirm `SQLCOMPARE_CONN_DEFAULT` when no `-c` is supplied.
25+
2. Confirm `SQLCOMPARE_CONN_<NAME>` matches the `-c <name>` value.
26+
3. Ensure values are valid SQLAlchemy URLs and include driver, credentials, host, and database.
27+
4. Check for common format issues: missing scheme, extra quotes, unescaped special characters in passwords, or a missing database name.
28+
29+
For URL parsing and credential encoding warnings, run:\n`python3 skills/sqlcompare-connection-debug/scripts/parse_sqlalchemy_url.py \"<sqlalchemy_url>\"`
30+
31+
### SQLAlchemy URL Formats (Examples)
32+
1. Postgres: `postgresql://user:pass@host:5432/dbname`
33+
2. DuckDB file: `duckdb:////absolute/path/to/db.duckdb`
34+
3. DuckDB in-memory: `duckdb:///:memory:`
35+
4. Snowflake (password): `snowflake://user:pass@account/db/schema?warehouse=WH&role=ROLE`
36+
5. Snowflake (private key): `snowflake://user@account/db/schema?warehouse=WH&role=ROLE&private_key_file=/absolute/path/key.p8&private_key_file_pwd=YOUR_PASSPHRASE`
37+
38+
If a password includes `@`, `:`, or `/`, URL-encode it before using it in the connection string.
39+
40+
## YAML Connection File Checklist
41+
1. Check `~/.sqlcompare/connections.yml` exists and is readable.
42+
2. Ensure the top-level key matches the `-c` name.
43+
3. Validate required fields per connector (drivername, username, password, host, database, schema).
44+
4. If you use YAML, prefer it when URL encoding becomes error-prone.
45+
46+
## Permission And Schema Issues
47+
1. SQLCompare creates a physical join table in the comparison schema.
48+
2. Ensure the connector user has `CREATE SCHEMA` and `CREATE TABLE` privileges.
49+
3. Verify `SQLCOMPARE_COMPARISON_SCHEMA` if a non-default schema is required.
50+
51+
## Debug Logging
52+
1. Enable verbose logging by setting `SQLCOMPARE_DEBUG=1`.
53+
2. Re-run the failing command and capture the expanded logs.
54+
55+
## Connection Format Troubleshooting
56+
1. If the error mentions an unknown dialect or driver, confirm the URL scheme (for example `postgresql://`, `snowflake://`, `duckdb://`).
57+
2. If the error mentions authentication, verify username/password and URL-encode special characters.
58+
3. If the error mentions database or schema not found, confirm the database and schema names in the URL or YAML config.
59+
60+
## Connectivity Smoke Tests
61+
Use these to isolate authentication vs query issues.
62+
1. Simple query against the target connector.
63+
`sqlcompare query "SELECT 1" -c <name>`
64+
2. List available diffs to confirm local metadata access.
65+
`sqlcompare list-diffs`
66+
67+
## Common Fixes
68+
1. Use `-c <name>` explicitly to avoid accidental default selection.
69+
2. Correct the connection name casing to match env vars and YAML keys.
70+
3. Quote or fully qualify table names if your database requires it.
Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
#!/usr/bin/env python3
2+
"""Parse a SQLAlchemy URL and highlight encoding issues in credentials."""
3+
4+
from __future__ import annotations
5+
6+
import argparse
7+
import sys
8+
from urllib.parse import parse_qs, unquote, urlparse
9+
10+
try:
11+
from sqlalchemy.engine import make_url
12+
except Exception: # pragma: no cover - optional dependency
13+
make_url = None
14+
15+
16+
RESERVED = set("@:/?#&=%+ ")
17+
18+
19+
def _raw_userinfo(netloc: str) -> str | None:
20+
if "@" not in netloc:
21+
return None
22+
return netloc.split("@", 1)[0]
23+
24+
25+
def _split_user_pass(userinfo: str) -> tuple[str | None, str | None]:
26+
if ":" in userinfo:
27+
user, pwd = userinfo.split(":", 1)
28+
return user, pwd
29+
return userinfo, None
30+
31+
32+
def _parse_with_sqlalchemy(url: str) -> tuple[object, dict[str, list[str]]]:
33+
parsed = make_url(url)
34+
query = parsed.query or {}
35+
return parsed, {k: [str(v)] if not isinstance(v, list) else [str(x) for x in v] for k, v in query.items()}
36+
37+
38+
def _parse_with_stdlib(url: str) -> tuple[object, dict[str, list[str]]]:
39+
parsed = urlparse(url)
40+
return parsed, parse_qs(parsed.query)
41+
42+
43+
def parse_sqlalchemy_url(url: str) -> int:
44+
if make_url is not None:
45+
try:
46+
parsed, query = _parse_with_sqlalchemy(url)
47+
except Exception as exc:
48+
print(f\"ERROR: SQLAlchemy failed to parse URL: {exc}\")\n return 1
49+
else:
50+
parsed, query = _parse_with_stdlib(url)
51+
52+
scheme = getattr(parsed, \"drivername\", None) or getattr(parsed, \"scheme\", \"\")
53+
if not scheme:
54+
print("ERROR: Missing scheme (for example, postgresql://, snowflake://, duckdb://)")
55+
return 1
56+
57+
netloc = getattr(parsed, \"host\", None)
58+
if netloc is None and hasattr(parsed, \"netloc\"):
59+
netloc = parsed.netloc
60+
61+
raw_userinfo = _raw_userinfo(netloc or \"\") if isinstance(netloc, str) else None
62+
raw_user, raw_pass = (None, None)
63+
if raw_userinfo:
64+
raw_user, raw_pass = _split_user_pass(raw_userinfo)
65+
66+
if make_url is not None and hasattr(parsed, \"username\"):
67+
decoded_user = parsed.username
68+
decoded_pass = parsed.password
69+
else:
70+
decoded_user = unquote(raw_user) if raw_user is not None else None
71+
decoded_pass = unquote(raw_pass) if raw_pass is not None else None
72+
73+
print("Parsed URL")
74+
print(f"scheme: {scheme}")
75+
print(f"username: {decoded_user}")
76+
print(f"password: {decoded_pass}")
77+
host = getattr(parsed, \"host\", None) or getattr(parsed, \"hostname\", None)
78+
port = getattr(parsed, \"port\", None)
79+
database = getattr(parsed, \"database\", None)
80+
path = getattr(parsed, \"path\", None)
81+
print(f\"host: {host}\")
82+
print(f\"port: {port}\")
83+
print(f\"database/path: {database or path or None}\")
84+
85+
if query:
86+
print("query parameters:")
87+
for key in sorted(query):
88+
print(f" {key}: {query[key]}")
89+
else:
90+
print("query parameters: none")
91+
92+
warnings = []
93+
if raw_userinfo:
94+
if raw_user and any(ch in RESERVED for ch in raw_user):
95+
warnings.append("Username contains reserved characters; URL-encode it.")
96+
if raw_pass and any(ch in RESERVED for ch in raw_pass):
97+
warnings.append("Password contains reserved characters; URL-encode it.")
98+
if warnings:
99+
print("warnings:")
100+
for warn in warnings:
101+
print(f" - {warn}")
102+
return 0
103+
104+
105+
def main() -> int:
106+
parser = argparse.ArgumentParser(description="Parse a SQLAlchemy URL")
107+
parser.add_argument("url", help="SQLAlchemy URL to parse")
108+
args = parser.parse_args()
109+
return parse_sqlalchemy_url(args.url)
110+
111+
112+
if __name__ == "__main__":
113+
sys.exit(main())
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
---
2+
name: sqlcompare-core-run
3+
description: Run SQLCompare core comparisons across tables, SQL queries, and files to produce a diff_id. Use when the user wants to compare two datasets, choose index keys, set connectors, or run dataset configs via sqlcompare run/table/dataset/stats/query.
4+
---
5+
6+
# SQLCompare Core Run
7+
8+
## Overview
9+
Run the primary SQLCompare commands that create comparison artifacts and return a diff_id for later analysis. Focus on selecting inputs, defining index keys, and choosing the right connector.
10+
11+
## Quick Start
12+
1. Set a default connection or pass one explicitly.
13+
2. Run a comparison command and capture the diff_id from output.
14+
3. Hand the diff_id to output analysis for inspection and reporting.
15+
16+
## Choose The Comparison Type
17+
Use these patterns to pick the right entry point.
18+
19+
1. Compare two tables or views.
20+
`sqlcompare run analytics.fact_sales analytics.fact_sales_new id`
21+
2. Compare two SQL queries (inline or .sql files).
22+
`sqlcompare run "SELECT ..." "SELECT ..." id -c snowflake_prod`
23+
`sqlcompare run queries/previous.sql queries/current.sql id -c snowflake_prod`
24+
3. Compare local files (CSV/XLSX) with DuckDB.
25+
`sqlcompare run path/to/previous.csv path/to/current.xlsx id`
26+
4. Use a dataset config for repeatable runs.
27+
`sqlcompare dataset path/to/dataset.yaml`
28+
5. Run a fast statistical comparison (no diff_id needed for row-level drilldown).
29+
`sqlcompare stats analytics.users analytics.users_new -c snowflake_prod`
30+
6. Run a quick sanity query against a connector.
31+
`sqlcompare query "SELECT COUNT(*) FROM analytics.users" -c snowflake_prod`
32+
33+
## Inputs And Keys
34+
1. Provide index columns that exist in both datasets.
35+
2. Use comma-separated keys for composite indexes.
36+
`sqlcompare run analytics.users analytics.users_new user_id,tenant_id`
37+
3. Fully qualify or quote identifiers when required by your database.
38+
39+
## Connections And Defaults
40+
1. Prefer `-c <name>` to select a named connection for a run.
41+
2. Set `SQLCOMPARE_CONN_DEFAULT` for the default connector.
42+
3. Set `SQLCOMPARE_CONN_<NAME>` for named connectors.
43+
4. Use `~/.sqlcompare/connections.yml` for YAML-based connection configs.
44+
45+
## Expected Output
46+
1. A successful compare prints a diff_id.
47+
2. Use that diff_id with `sqlcompare-output-analysis` for inspection, exports, and queries.
48+
49+
## Notes
50+
1. SQLCompare creates comparison tables in the `SQLCOMPARE_COMPARISON_SCHEMA` (default `sqlcompare`).
51+
2. Ensure the connector has `CREATE SCHEMA` and `CREATE TABLE` privileges.
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
---
2+
name: sqlcompare-output-analysis
3+
description: Analyze SQLCompare outputs after a core run. Use when the user has a diff_id and needs to inspect stats, missing rows, column-level diffs, exports, or AI-friendly diff-queries.
4+
---
5+
6+
# SQLCompare Output Analysis
7+
8+
## Overview
9+
Interpret and drill into a SQLCompare diff_id using inspect, list-diffs, and diff-queries. Produce summaries, targeted column reviews, and exportable reports.
10+
11+
## Start From A diff_id
12+
1. If you already have a diff_id, proceed to inspection commands.
13+
2. If you do not, list recent diffs and pick the correct one.
14+
`sqlcompare list-diffs`
15+
`sqlcompare list-diffs users`
16+
17+
## Core Inspection Commands
18+
1. Overall statistics summary.
19+
`sqlcompare inspect <diff_id> --stats`
20+
2. Inspect a specific column with row samples.
21+
`sqlcompare inspect <diff_id> --column revenue --limit 100`
22+
3. Show rows missing on either side.
23+
`sqlcompare inspect <diff_id> --missing-current`
24+
`sqlcompare inspect <diff_id> --missing-previous`
25+
4. List available columns.
26+
`sqlcompare inspect <diff_id> --list-columns`
27+
28+
## Export Reports (XLSX)
29+
1. Summary report with capped row samples per column.
30+
`sqlcompare inspect <diff_id> --save summary`
31+
2. Full report without per-column row caps.
32+
`sqlcompare inspect <diff_id> --save complete --file-path ./reports/full_diff.xlsx`
33+
3. Single-column summary report.
34+
`sqlcompare inspect <diff_id> --column revenue --save summary --file-path ./reports/revenue_diff.xlsx`
35+
36+
Notes:
37+
1. `--save summary|complete` is for the standard diff view.
38+
2. Do not combine `--save summary|complete` with `--stats`, `--missing-current`, `--missing-previous`, or `--list-columns`.
39+
40+
## AI-Friendly Diff Queries
41+
Use diff-queries to get structured metadata and SQL templates.
42+
`sqlcompare diff-queries <diff_id>`
43+
44+
## Interpretation Hints
45+
1. Start with `--stats` to see which columns change most.
46+
2. Use `--missing-current` and `--missing-previous` to find dropped or new rows.
47+
3. Use `--column` to inspect high-impact fields.
48+
4. Export a summary report for sharing, then refine analysis with targeted column checks.

0 commit comments

Comments
 (0)