sqlsaber 0.10.0__tar.gz → 0.12.0__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.
Potentially problematic release.
This version of sqlsaber might be problematic. Click here for more details.
- sqlsaber-0.12.0/.github/workflows/claude-code-review.yml +78 -0
- sqlsaber-0.12.0/.github/workflows/claude.yml +64 -0
- {sqlsaber-0.10.0 → sqlsaber-0.12.0}/CHANGELOG.md +6 -0
- {sqlsaber-0.10.0 → sqlsaber-0.12.0}/PKG-INFO +10 -1
- {sqlsaber-0.10.0 → sqlsaber-0.12.0}/README.md +9 -0
- {sqlsaber-0.10.0 → sqlsaber-0.12.0}/pyproject.toml +1 -1
- sqlsaber-0.12.0/src/sqlsaber/__init__.py +1 -0
- {sqlsaber-0.10.0 → sqlsaber-0.12.0}/src/sqlsaber/cli/commands.py +14 -3
- {sqlsaber-0.10.0 → sqlsaber-0.12.0}/src/sqlsaber/cli/display.py +1 -6
- {sqlsaber-0.10.0 → sqlsaber-0.12.0}/src/sqlsaber/database/schema.py +41 -53
- {sqlsaber-0.10.0 → sqlsaber-0.12.0}/uv.lock +1 -1
- sqlsaber-0.10.0/src/sqlsaber/__init__.py +0 -3
- {sqlsaber-0.10.0 → sqlsaber-0.12.0}/.github/workflows/publish.yml +0 -0
- {sqlsaber-0.10.0 → sqlsaber-0.12.0}/.gitignore +0 -0
- {sqlsaber-0.10.0 → sqlsaber-0.12.0}/.python-version +0 -0
- {sqlsaber-0.10.0 → sqlsaber-0.12.0}/AGENT.md +0 -0
- {sqlsaber-0.10.0 → sqlsaber-0.12.0}/CLAUDE.md +0 -0
- {sqlsaber-0.10.0 → sqlsaber-0.12.0}/LICENSE +0 -0
- {sqlsaber-0.10.0 → sqlsaber-0.12.0}/pytest.ini +0 -0
- {sqlsaber-0.10.0 → sqlsaber-0.12.0}/sqlsaber.svg +0 -0
- {sqlsaber-0.10.0 → sqlsaber-0.12.0}/src/sqlsaber/__main__.py +0 -0
- {sqlsaber-0.10.0 → sqlsaber-0.12.0}/src/sqlsaber/agents/__init__.py +0 -0
- {sqlsaber-0.10.0 → sqlsaber-0.12.0}/src/sqlsaber/agents/anthropic.py +0 -0
- {sqlsaber-0.10.0 → sqlsaber-0.12.0}/src/sqlsaber/agents/base.py +0 -0
- {sqlsaber-0.10.0 → sqlsaber-0.12.0}/src/sqlsaber/agents/mcp.py +0 -0
- {sqlsaber-0.10.0 → sqlsaber-0.12.0}/src/sqlsaber/agents/streaming.py +0 -0
- {sqlsaber-0.10.0 → sqlsaber-0.12.0}/src/sqlsaber/cli/__init__.py +0 -0
- {sqlsaber-0.10.0 → sqlsaber-0.12.0}/src/sqlsaber/cli/auth.py +0 -0
- {sqlsaber-0.10.0 → sqlsaber-0.12.0}/src/sqlsaber/cli/completers.py +0 -0
- {sqlsaber-0.10.0 → sqlsaber-0.12.0}/src/sqlsaber/cli/database.py +0 -0
- {sqlsaber-0.10.0 → sqlsaber-0.12.0}/src/sqlsaber/cli/interactive.py +0 -0
- {sqlsaber-0.10.0 → sqlsaber-0.12.0}/src/sqlsaber/cli/memory.py +0 -0
- {sqlsaber-0.10.0 → sqlsaber-0.12.0}/src/sqlsaber/cli/models.py +0 -0
- {sqlsaber-0.10.0 → sqlsaber-0.12.0}/src/sqlsaber/cli/streaming.py +0 -0
- {sqlsaber-0.10.0 → sqlsaber-0.12.0}/src/sqlsaber/clients/__init__.py +0 -0
- {sqlsaber-0.10.0 → sqlsaber-0.12.0}/src/sqlsaber/clients/anthropic.py +0 -0
- {sqlsaber-0.10.0 → sqlsaber-0.12.0}/src/sqlsaber/clients/base.py +0 -0
- {sqlsaber-0.10.0 → sqlsaber-0.12.0}/src/sqlsaber/clients/exceptions.py +0 -0
- {sqlsaber-0.10.0 → sqlsaber-0.12.0}/src/sqlsaber/clients/models.py +0 -0
- {sqlsaber-0.10.0 → sqlsaber-0.12.0}/src/sqlsaber/clients/streaming.py +0 -0
- {sqlsaber-0.10.0 → sqlsaber-0.12.0}/src/sqlsaber/config/__init__.py +0 -0
- {sqlsaber-0.10.0 → sqlsaber-0.12.0}/src/sqlsaber/config/api_keys.py +0 -0
- {sqlsaber-0.10.0 → sqlsaber-0.12.0}/src/sqlsaber/config/auth.py +0 -0
- {sqlsaber-0.10.0 → sqlsaber-0.12.0}/src/sqlsaber/config/database.py +0 -0
- {sqlsaber-0.10.0 → sqlsaber-0.12.0}/src/sqlsaber/config/oauth_flow.py +0 -0
- {sqlsaber-0.10.0 → sqlsaber-0.12.0}/src/sqlsaber/config/oauth_tokens.py +0 -0
- {sqlsaber-0.10.0 → sqlsaber-0.12.0}/src/sqlsaber/config/settings.py +0 -0
- {sqlsaber-0.10.0 → sqlsaber-0.12.0}/src/sqlsaber/database/__init__.py +0 -0
- {sqlsaber-0.10.0 → sqlsaber-0.12.0}/src/sqlsaber/database/connection.py +0 -0
- {sqlsaber-0.10.0 → sqlsaber-0.12.0}/src/sqlsaber/mcp/__init__.py +0 -0
- {sqlsaber-0.10.0 → sqlsaber-0.12.0}/src/sqlsaber/mcp/mcp.py +0 -0
- {sqlsaber-0.10.0 → sqlsaber-0.12.0}/src/sqlsaber/memory/__init__.py +0 -0
- {sqlsaber-0.10.0 → sqlsaber-0.12.0}/src/sqlsaber/memory/manager.py +0 -0
- {sqlsaber-0.10.0 → sqlsaber-0.12.0}/src/sqlsaber/memory/storage.py +0 -0
- {sqlsaber-0.10.0 → sqlsaber-0.12.0}/src/sqlsaber/models/__init__.py +0 -0
- {sqlsaber-0.10.0 → sqlsaber-0.12.0}/src/sqlsaber/models/events.py +0 -0
- {sqlsaber-0.10.0 → sqlsaber-0.12.0}/src/sqlsaber/models/types.py +0 -0
- {sqlsaber-0.10.0 → sqlsaber-0.12.0}/tests/__init__.py +0 -0
- {sqlsaber-0.10.0 → sqlsaber-0.12.0}/tests/conftest.py +0 -0
- {sqlsaber-0.10.0 → sqlsaber-0.12.0}/tests/test_agents/test_anthropic_oauth.py +0 -0
- {sqlsaber-0.10.0 → sqlsaber-0.12.0}/tests/test_cli/__init__.py +0 -0
- {sqlsaber-0.10.0 → sqlsaber-0.12.0}/tests/test_cli/test_commands.py +0 -0
- {sqlsaber-0.10.0 → sqlsaber-0.12.0}/tests/test_clients/test_anthropic_client.py +0 -0
- {sqlsaber-0.10.0 → sqlsaber-0.12.0}/tests/test_clients/test_streaming.py +0 -0
- {sqlsaber-0.10.0 → sqlsaber-0.12.0}/tests/test_config/__init__.py +0 -0
- {sqlsaber-0.10.0 → sqlsaber-0.12.0}/tests/test_config/test_database.py +0 -0
- {sqlsaber-0.10.0 → sqlsaber-0.12.0}/tests/test_config/test_oauth.py +0 -0
- {sqlsaber-0.10.0 → sqlsaber-0.12.0}/tests/test_config/test_settings.py +0 -0
- {sqlsaber-0.10.0 → sqlsaber-0.12.0}/tests/test_database/__init__.py +0 -0
- {sqlsaber-0.10.0 → sqlsaber-0.12.0}/tests/test_database/test_connection.py +0 -0
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
name: Claude Code Review
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
pull_request:
|
|
5
|
+
types: [opened, synchronize]
|
|
6
|
+
# Optional: Only run on specific file changes
|
|
7
|
+
# paths:
|
|
8
|
+
# - "src/**/*.ts"
|
|
9
|
+
# - "src/**/*.tsx"
|
|
10
|
+
# - "src/**/*.js"
|
|
11
|
+
# - "src/**/*.jsx"
|
|
12
|
+
|
|
13
|
+
jobs:
|
|
14
|
+
claude-review:
|
|
15
|
+
# Optional: Filter by PR author
|
|
16
|
+
# if: |
|
|
17
|
+
# github.event.pull_request.user.login == 'external-contributor' ||
|
|
18
|
+
# github.event.pull_request.user.login == 'new-developer' ||
|
|
19
|
+
# github.event.pull_request.author_association == 'FIRST_TIME_CONTRIBUTOR'
|
|
20
|
+
|
|
21
|
+
runs-on: ubuntu-latest
|
|
22
|
+
permissions:
|
|
23
|
+
contents: read
|
|
24
|
+
pull-requests: read
|
|
25
|
+
issues: read
|
|
26
|
+
id-token: write
|
|
27
|
+
|
|
28
|
+
steps:
|
|
29
|
+
- name: Checkout repository
|
|
30
|
+
uses: actions/checkout@v4
|
|
31
|
+
with:
|
|
32
|
+
fetch-depth: 1
|
|
33
|
+
|
|
34
|
+
- name: Run Claude Code Review
|
|
35
|
+
id: claude-review
|
|
36
|
+
uses: anthropics/claude-code-action@beta
|
|
37
|
+
with:
|
|
38
|
+
claude_code_oauth_token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }}
|
|
39
|
+
|
|
40
|
+
# Optional: Specify model (defaults to Claude Sonnet 4, uncomment for Claude Opus 4)
|
|
41
|
+
# model: "claude-opus-4-20250514"
|
|
42
|
+
|
|
43
|
+
# Direct prompt for automated review (no @claude mention needed)
|
|
44
|
+
direct_prompt: |
|
|
45
|
+
Please review this pull request and provide feedback on:
|
|
46
|
+
- Code quality and best practices
|
|
47
|
+
- Potential bugs or issues
|
|
48
|
+
- Performance considerations
|
|
49
|
+
- Security concerns
|
|
50
|
+
- Test coverage
|
|
51
|
+
|
|
52
|
+
Be constructive and helpful in your feedback.
|
|
53
|
+
|
|
54
|
+
# Optional: Use sticky comments to make Claude reuse the same comment on subsequent pushes to the same PR
|
|
55
|
+
# use_sticky_comment: true
|
|
56
|
+
|
|
57
|
+
# Optional: Customize review based on file types
|
|
58
|
+
# direct_prompt: |
|
|
59
|
+
# Review this PR focusing on:
|
|
60
|
+
# - For TypeScript files: Type safety and proper interface usage
|
|
61
|
+
# - For API endpoints: Security, input validation, and error handling
|
|
62
|
+
# - For React components: Performance, accessibility, and best practices
|
|
63
|
+
# - For tests: Coverage, edge cases, and test quality
|
|
64
|
+
|
|
65
|
+
# Optional: Different prompts for different authors
|
|
66
|
+
# direct_prompt: |
|
|
67
|
+
# ${{ github.event.pull_request.author_association == 'FIRST_TIME_CONTRIBUTOR' &&
|
|
68
|
+
# 'Welcome! Please review this PR from a first-time contributor. Be encouraging and provide detailed explanations for any suggestions.' ||
|
|
69
|
+
# 'Please provide a thorough code review focusing on our coding standards and best practices.' }}
|
|
70
|
+
|
|
71
|
+
# Optional: Add specific tools for running tests or linting
|
|
72
|
+
# allowed_tools: "Bash(npm run test),Bash(npm run lint),Bash(npm run typecheck)"
|
|
73
|
+
|
|
74
|
+
# Optional: Skip review for certain conditions
|
|
75
|
+
# if: |
|
|
76
|
+
# !contains(github.event.pull_request.title, '[skip-review]') &&
|
|
77
|
+
# !contains(github.event.pull_request.title, '[WIP]')
|
|
78
|
+
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
name: Claude Code
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
issue_comment:
|
|
5
|
+
types: [created]
|
|
6
|
+
pull_request_review_comment:
|
|
7
|
+
types: [created]
|
|
8
|
+
issues:
|
|
9
|
+
types: [opened, assigned]
|
|
10
|
+
pull_request_review:
|
|
11
|
+
types: [submitted]
|
|
12
|
+
|
|
13
|
+
jobs:
|
|
14
|
+
claude:
|
|
15
|
+
if: |
|
|
16
|
+
(github.event_name == 'issue_comment' && contains(github.event.comment.body, '@claude')) ||
|
|
17
|
+
(github.event_name == 'pull_request_review_comment' && contains(github.event.comment.body, '@claude')) ||
|
|
18
|
+
(github.event_name == 'pull_request_review' && contains(github.event.review.body, '@claude')) ||
|
|
19
|
+
(github.event_name == 'issues' && (contains(github.event.issue.body, '@claude') || contains(github.event.issue.title, '@claude')))
|
|
20
|
+
runs-on: ubuntu-latest
|
|
21
|
+
permissions:
|
|
22
|
+
contents: read
|
|
23
|
+
pull-requests: read
|
|
24
|
+
issues: read
|
|
25
|
+
id-token: write
|
|
26
|
+
actions: read # Required for Claude to read CI results on PRs
|
|
27
|
+
steps:
|
|
28
|
+
- name: Checkout repository
|
|
29
|
+
uses: actions/checkout@v4
|
|
30
|
+
with:
|
|
31
|
+
fetch-depth: 1
|
|
32
|
+
|
|
33
|
+
- name: Run Claude Code
|
|
34
|
+
id: claude
|
|
35
|
+
uses: anthropics/claude-code-action@beta
|
|
36
|
+
with:
|
|
37
|
+
claude_code_oauth_token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }}
|
|
38
|
+
|
|
39
|
+
# This is an optional setting that allows Claude to read CI results on PRs
|
|
40
|
+
additional_permissions: |
|
|
41
|
+
actions: read
|
|
42
|
+
|
|
43
|
+
# Optional: Specify model (defaults to Claude Sonnet 4, uncomment for Claude Opus 4)
|
|
44
|
+
# model: "claude-opus-4-20250514"
|
|
45
|
+
|
|
46
|
+
# Optional: Customize the trigger phrase (default: @claude)
|
|
47
|
+
# trigger_phrase: "/claude"
|
|
48
|
+
|
|
49
|
+
# Optional: Trigger when specific user is assigned to an issue
|
|
50
|
+
# assignee_trigger: "claude-bot"
|
|
51
|
+
|
|
52
|
+
# Optional: Allow Claude to run specific commands
|
|
53
|
+
# allowed_tools: "Bash(npm install),Bash(npm run build),Bash(npm run test:*),Bash(npm run lint:*)"
|
|
54
|
+
|
|
55
|
+
# Optional: Add custom instructions for Claude to customize its behavior for your project
|
|
56
|
+
# custom_instructions: |
|
|
57
|
+
# Follow our coding standards
|
|
58
|
+
# Ensure all new code has tests
|
|
59
|
+
# Use TypeScript for new files
|
|
60
|
+
|
|
61
|
+
# Optional: Custom environment variables for Claude
|
|
62
|
+
# claude_env: |
|
|
63
|
+
# NODE_ENV: test
|
|
64
|
+
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: sqlsaber
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.12.0
|
|
4
4
|
Summary: SQLSaber - Agentic SQL assistant like Claude Code
|
|
5
5
|
License-File: LICENSE
|
|
6
6
|
Requires-Python: >=3.12
|
|
@@ -21,6 +21,15 @@ Description-Content-Type: text/markdown
|
|
|
21
21
|
|
|
22
22
|
# SQLSaber
|
|
23
23
|
|
|
24
|
+
```
|
|
25
|
+
███████ ██████ ██ ███████ █████ ██████ ███████ ██████
|
|
26
|
+
██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██
|
|
27
|
+
███████ ██ ██ ██ ███████ ███████ ██████ █████ ██████
|
|
28
|
+
██ ██ ▄▄ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██
|
|
29
|
+
███████ ██████ ███████ ███████ ██ ██ ██████ ███████ ██ ██
|
|
30
|
+
▀▀
|
|
31
|
+
```
|
|
32
|
+
|
|
24
33
|
> Use the agent Luke!
|
|
25
34
|
|
|
26
35
|
SQLSaber is an agentic SQL assistant. Think Claude Code but for SQL.
|
|
@@ -1,5 +1,14 @@
|
|
|
1
1
|
# SQLSaber
|
|
2
2
|
|
|
3
|
+
```
|
|
4
|
+
███████ ██████ ██ ███████ █████ ██████ ███████ ██████
|
|
5
|
+
██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██
|
|
6
|
+
███████ ██ ██ ██ ███████ ███████ ██████ █████ ██████
|
|
7
|
+
██ ██ ▄▄ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██
|
|
8
|
+
███████ ██████ ███████ ███████ ██ ██ ██████ ███████ ██ ██
|
|
9
|
+
▀▀
|
|
10
|
+
```
|
|
11
|
+
|
|
3
12
|
> Use the agent Luke!
|
|
4
13
|
|
|
5
14
|
SQLSaber is an agentic SQL assistant. Think Claude Code but for SQL.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""SQLSaber CLI - Agentic SQL assistant like Claude Code but for SQL."""
|
|
@@ -43,7 +43,7 @@ def meta_handler(
|
|
|
43
43
|
str | None,
|
|
44
44
|
cyclopts.Parameter(
|
|
45
45
|
["--database", "-d"],
|
|
46
|
-
help="Database connection name (uses default if not specified)",
|
|
46
|
+
help="Database connection name or direct file path for CSV/SQLite files (uses default if not specified)",
|
|
47
47
|
),
|
|
48
48
|
] = None,
|
|
49
49
|
):
|
|
@@ -54,6 +54,8 @@ def meta_handler(
|
|
|
54
54
|
saber # Start interactive mode
|
|
55
55
|
saber "show me all users" # Run a single query with default database
|
|
56
56
|
saber -d mydb "show me users" # Run a query with specific database
|
|
57
|
+
saber -d data.csv "show me users" # Run a query with ad-hoc CSV file
|
|
58
|
+
saber -d data.db "show me users" # Run a query with ad-hoc SQLite file
|
|
57
59
|
echo "show me all users" | saber # Read query from stdin
|
|
58
60
|
cat query.txt | saber # Read query from file via stdin
|
|
59
61
|
"""
|
|
@@ -73,7 +75,7 @@ def query(
|
|
|
73
75
|
str | None,
|
|
74
76
|
cyclopts.Parameter(
|
|
75
77
|
["--database", "-d"],
|
|
76
|
-
help="Database connection name (uses default if not specified)",
|
|
78
|
+
help="Database connection name or direct file path for CSV/SQLite files (uses default if not specified)",
|
|
77
79
|
),
|
|
78
80
|
] = None,
|
|
79
81
|
):
|
|
@@ -88,6 +90,8 @@ def query(
|
|
|
88
90
|
Examples:
|
|
89
91
|
saber # Start interactive mode
|
|
90
92
|
saber "show me all users" # Run a single query
|
|
93
|
+
saber -d data.csv "show users" # Run a query with ad-hoc CSV file
|
|
94
|
+
saber -d data.db "show users" # Run a query with ad-hoc SQLite file
|
|
91
95
|
echo "show me all users" | saber # Read query from stdin
|
|
92
96
|
"""
|
|
93
97
|
|
|
@@ -101,7 +105,7 @@ def query(
|
|
|
101
105
|
# If stdin was empty, fall back to interactive mode
|
|
102
106
|
actual_query = None
|
|
103
107
|
|
|
104
|
-
# Get database configuration or handle direct
|
|
108
|
+
# Get database configuration or handle direct file paths
|
|
105
109
|
if database:
|
|
106
110
|
# Check if this is a direct CSV file path
|
|
107
111
|
if database.endswith(".csv"):
|
|
@@ -110,6 +114,13 @@ def query(
|
|
|
110
114
|
raise CLIError(f"CSV file '{database}' not found.")
|
|
111
115
|
connection_string = f"csv:///{csv_path}"
|
|
112
116
|
db_name = csv_path.stem
|
|
117
|
+
# Check if this is a direct SQLite file path
|
|
118
|
+
elif database.endswith((".db", ".sqlite", ".sqlite3")):
|
|
119
|
+
sqlite_path = Path(database).expanduser().resolve()
|
|
120
|
+
if not sqlite_path.exists():
|
|
121
|
+
raise CLIError(f"SQLite file '{database}' not found.")
|
|
122
|
+
connection_string = f"sqlite:///{sqlite_path}"
|
|
123
|
+
db_name = sqlite_path.stem
|
|
113
124
|
else:
|
|
114
125
|
# Look up configured database connection
|
|
115
126
|
db_config = config_manager.get_database(database)
|
|
@@ -125,7 +125,6 @@ class DisplayManager:
|
|
|
125
125
|
{"name": "Schema", "style": "cyan"},
|
|
126
126
|
{"name": "Table Name", "style": "white"},
|
|
127
127
|
{"name": "Type", "style": "yellow"},
|
|
128
|
-
{"name": "Row Count", "justify": "right", "style": "magenta"},
|
|
129
128
|
]
|
|
130
129
|
table = self._create_table(columns)
|
|
131
130
|
|
|
@@ -134,12 +133,8 @@ class DisplayManager:
|
|
|
134
133
|
schema = table_info.get("schema", "")
|
|
135
134
|
name = table_info.get("name", "")
|
|
136
135
|
table_type = table_info.get("type", "")
|
|
137
|
-
row_count = table_info.get("row_count", 0)
|
|
138
136
|
|
|
139
|
-
|
|
140
|
-
formatted_count = f"{row_count:,}" if row_count else "0"
|
|
141
|
-
|
|
142
|
-
table.add_row(schema, name, table_type, formatted_count)
|
|
137
|
+
table.add_row(schema, name, table_type)
|
|
143
138
|
|
|
144
139
|
self.console.print(table)
|
|
145
140
|
|
|
@@ -42,7 +42,7 @@ class BaseSchemaIntrospector(ABC):
|
|
|
42
42
|
pass
|
|
43
43
|
|
|
44
44
|
@abstractmethod
|
|
45
|
-
async def list_tables_info(self, connection) -> dict[str, Any]:
|
|
45
|
+
async def list_tables_info(self, connection) -> list[dict[str, Any]]:
|
|
46
46
|
"""Get list of tables with basic information."""
|
|
47
47
|
pass
|
|
48
48
|
|
|
@@ -182,32 +182,31 @@ class PostgreSQLSchemaIntrospector(BaseSchemaIntrospector):
|
|
|
182
182
|
"""
|
|
183
183
|
return await conn.fetch(pk_query)
|
|
184
184
|
|
|
185
|
-
async def list_tables_info(self, connection) -> dict[str, Any]:
|
|
185
|
+
async def list_tables_info(self, connection) -> list[dict[str, Any]]:
|
|
186
186
|
"""Get list of tables with basic information for PostgreSQL."""
|
|
187
187
|
pool = await connection.get_pool()
|
|
188
188
|
async with pool.acquire() as conn:
|
|
189
|
-
# Get tables
|
|
189
|
+
# Get tables without row counts for better performance
|
|
190
190
|
tables_query = """
|
|
191
|
-
WITH table_stats AS (
|
|
192
|
-
SELECT
|
|
193
|
-
schemaname,
|
|
194
|
-
relname as tablename,
|
|
195
|
-
n_live_tup as approximate_row_count
|
|
196
|
-
FROM pg_stat_user_tables
|
|
197
|
-
)
|
|
198
191
|
SELECT
|
|
199
192
|
t.table_schema,
|
|
200
193
|
t.table_name,
|
|
201
|
-
t.table_type
|
|
202
|
-
COALESCE(ts.approximate_row_count, 0) as row_count
|
|
194
|
+
t.table_type
|
|
203
195
|
FROM information_schema.tables t
|
|
204
|
-
LEFT JOIN table_stats ts
|
|
205
|
-
ON t.table_schema = ts.schemaname
|
|
206
|
-
AND t.table_name = ts.tablename
|
|
207
196
|
WHERE t.table_schema NOT IN ('pg_catalog', 'information_schema')
|
|
208
197
|
ORDER BY t.table_schema, t.table_name;
|
|
209
198
|
"""
|
|
210
|
-
|
|
199
|
+
records = await conn.fetch(tables_query)
|
|
200
|
+
|
|
201
|
+
# Convert asyncpg.Record objects to dictionaries
|
|
202
|
+
return [
|
|
203
|
+
{
|
|
204
|
+
"table_schema": record["table_schema"],
|
|
205
|
+
"table_name": record["table_name"],
|
|
206
|
+
"table_type": record["table_type"],
|
|
207
|
+
}
|
|
208
|
+
for record in records
|
|
209
|
+
]
|
|
211
210
|
|
|
212
211
|
|
|
213
212
|
class MySQLSchemaIntrospector(BaseSchemaIntrospector):
|
|
@@ -353,24 +352,33 @@ class MySQLSchemaIntrospector(BaseSchemaIntrospector):
|
|
|
353
352
|
await cursor.execute(pk_query)
|
|
354
353
|
return await cursor.fetchall()
|
|
355
354
|
|
|
356
|
-
async def list_tables_info(self, connection) -> dict[str, Any]:
|
|
355
|
+
async def list_tables_info(self, connection) -> list[dict[str, Any]]:
|
|
357
356
|
"""Get list of tables with basic information for MySQL."""
|
|
358
357
|
pool = await connection.get_pool()
|
|
359
358
|
async with pool.acquire() as conn:
|
|
360
359
|
async with conn.cursor() as cursor:
|
|
361
|
-
# Get tables
|
|
360
|
+
# Get tables without row counts for better performance
|
|
362
361
|
tables_query = """
|
|
363
362
|
SELECT
|
|
364
363
|
t.table_schema,
|
|
365
364
|
t.table_name,
|
|
366
|
-
t.table_type
|
|
367
|
-
COALESCE(t.table_rows, 0) as row_count
|
|
365
|
+
t.table_type
|
|
368
366
|
FROM information_schema.tables t
|
|
369
367
|
WHERE t.table_schema NOT IN ('information_schema', 'performance_schema', 'mysql', 'sys')
|
|
370
368
|
ORDER BY t.table_schema, t.table_name;
|
|
371
369
|
"""
|
|
372
370
|
await cursor.execute(tables_query)
|
|
373
|
-
|
|
371
|
+
rows = await cursor.fetchall()
|
|
372
|
+
|
|
373
|
+
# Convert rows to dictionaries
|
|
374
|
+
return [
|
|
375
|
+
{
|
|
376
|
+
"table_schema": row["table_schema"],
|
|
377
|
+
"table_name": row["table_name"],
|
|
378
|
+
"table_type": row["table_type"],
|
|
379
|
+
}
|
|
380
|
+
for row in rows
|
|
381
|
+
]
|
|
374
382
|
|
|
375
383
|
|
|
376
384
|
class SQLiteSchemaIntrospector(BaseSchemaIntrospector):
|
|
@@ -496,9 +504,9 @@ class SQLiteSchemaIntrospector(BaseSchemaIntrospector):
|
|
|
496
504
|
|
|
497
505
|
return primary_keys
|
|
498
506
|
|
|
499
|
-
async def list_tables_info(self, connection) -> dict[str, Any]:
|
|
507
|
+
async def list_tables_info(self, connection) -> list[dict[str, Any]]:
|
|
500
508
|
"""Get list of tables with basic information for SQLite."""
|
|
501
|
-
#
|
|
509
|
+
# Get table names without row counts for better performance
|
|
502
510
|
tables_query = """
|
|
503
511
|
SELECT
|
|
504
512
|
'main' as table_schema,
|
|
@@ -512,34 +520,15 @@ class SQLiteSchemaIntrospector(BaseSchemaIntrospector):
|
|
|
512
520
|
|
|
513
521
|
tables = await self._execute_query(connection, tables_query)
|
|
514
522
|
|
|
515
|
-
#
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
count_query = f"SELECT COUNT(*) as count FROM [{table_name}]"
|
|
525
|
-
count_result = await self._execute_query(connection, count_query)
|
|
526
|
-
row_count = count_result[0]["count"] if count_result else 0
|
|
527
|
-
except Exception:
|
|
528
|
-
# If count fails (e.g., table locked), default to 0
|
|
529
|
-
row_count = 0
|
|
530
|
-
else:
|
|
531
|
-
# For views, we don't count rows as it could be expensive
|
|
532
|
-
row_count = 0
|
|
533
|
-
|
|
534
|
-
result.append(
|
|
535
|
-
{
|
|
536
|
-
"table_schema": table["table_schema"],
|
|
537
|
-
"table_name": table_name,
|
|
538
|
-
"table_type": table_type,
|
|
539
|
-
"row_count": row_count,
|
|
540
|
-
}
|
|
541
|
-
)
|
|
542
|
-
return result
|
|
523
|
+
# Convert to expected format
|
|
524
|
+
return [
|
|
525
|
+
{
|
|
526
|
+
"table_schema": table["table_schema"],
|
|
527
|
+
"table_name": table["table_name"],
|
|
528
|
+
"table_type": table["table_type"],
|
|
529
|
+
}
|
|
530
|
+
for table in tables
|
|
531
|
+
]
|
|
543
532
|
|
|
544
533
|
|
|
545
534
|
class SchemaManager:
|
|
@@ -682,7 +671,7 @@ class SchemaManager:
|
|
|
682
671
|
)
|
|
683
672
|
|
|
684
673
|
async def list_tables(self) -> dict[str, Any]:
|
|
685
|
-
"""Get a list of all tables with basic information
|
|
674
|
+
"""Get a list of all tables with basic information."""
|
|
686
675
|
# Check cache first
|
|
687
676
|
cache_key = "list_tables"
|
|
688
677
|
cached_data = self._get_cached_tables(cache_key)
|
|
@@ -702,7 +691,6 @@ class SchemaManager:
|
|
|
702
691
|
"name": table["table_name"],
|
|
703
692
|
"full_name": f"{table['table_schema']}.{table['table_name']}",
|
|
704
693
|
"type": table["table_type"],
|
|
705
|
-
"row_count": table["row_count"],
|
|
706
694
|
}
|
|
707
695
|
)
|
|
708
696
|
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|