sqlsaber 0.1.0__py3-none-any.whl
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/__init__.py +3 -0
- sqlsaber/__main__.py +4 -0
- sqlsaber/agents/__init__.py +9 -0
- sqlsaber/agents/anthropic.py +451 -0
- sqlsaber/agents/base.py +67 -0
- sqlsaber/agents/streaming.py +26 -0
- sqlsaber/cli/__init__.py +7 -0
- sqlsaber/cli/commands.py +132 -0
- sqlsaber/cli/database.py +275 -0
- sqlsaber/cli/display.py +207 -0
- sqlsaber/cli/interactive.py +93 -0
- sqlsaber/cli/memory.py +239 -0
- sqlsaber/cli/models.py +231 -0
- sqlsaber/cli/streaming.py +94 -0
- sqlsaber/config/__init__.py +7 -0
- sqlsaber/config/api_keys.py +102 -0
- sqlsaber/config/database.py +252 -0
- sqlsaber/config/settings.py +115 -0
- sqlsaber/database/__init__.py +9 -0
- sqlsaber/database/connection.py +187 -0
- sqlsaber/database/schema.py +678 -0
- sqlsaber/memory/__init__.py +1 -0
- sqlsaber/memory/manager.py +77 -0
- sqlsaber/memory/storage.py +176 -0
- sqlsaber/models/__init__.py +13 -0
- sqlsaber/models/events.py +28 -0
- sqlsaber/models/types.py +40 -0
- sqlsaber-0.1.0.dist-info/METADATA +168 -0
- sqlsaber-0.1.0.dist-info/RECORD +32 -0
- sqlsaber-0.1.0.dist-info/WHEEL +4 -0
- sqlsaber-0.1.0.dist-info/entry_points.txt +4 -0
- sqlsaber-0.1.0.dist-info/licenses/LICENSE +201 -0
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
"""Memory manager for handling database-specific context and memories."""
|
|
2
|
+
|
|
3
|
+
from typing import List, Optional
|
|
4
|
+
|
|
5
|
+
from sqlsaber.memory.storage import Memory, MemoryStorage
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class MemoryManager:
|
|
9
|
+
"""Manages database-specific memories and context."""
|
|
10
|
+
|
|
11
|
+
def __init__(self):
|
|
12
|
+
self.storage = MemoryStorage()
|
|
13
|
+
|
|
14
|
+
def add_memory(self, database_name: str, content: str) -> Memory:
|
|
15
|
+
"""Add a new memory for the specified database."""
|
|
16
|
+
return self.storage.add_memory(database_name, content)
|
|
17
|
+
|
|
18
|
+
def get_memories(self, database_name: str) -> List[Memory]:
|
|
19
|
+
"""Get all memories for the specified database."""
|
|
20
|
+
return self.storage.get_memories(database_name)
|
|
21
|
+
|
|
22
|
+
def remove_memory(self, database_name: str, memory_id: str) -> bool:
|
|
23
|
+
"""Remove a specific memory by ID."""
|
|
24
|
+
return self.storage.remove_memory(database_name, memory_id)
|
|
25
|
+
|
|
26
|
+
def clear_memories(self, database_name: str) -> int:
|
|
27
|
+
"""Clear all memories for the specified database."""
|
|
28
|
+
return self.storage.clear_memories(database_name)
|
|
29
|
+
|
|
30
|
+
def get_memory_by_id(self, database_name: str, memory_id: str) -> Optional[Memory]:
|
|
31
|
+
"""Get a specific memory by ID."""
|
|
32
|
+
return self.storage.get_memory_by_id(database_name, memory_id)
|
|
33
|
+
|
|
34
|
+
def has_memories(self, database_name: str) -> bool:
|
|
35
|
+
"""Check if database has any memories."""
|
|
36
|
+
return self.storage.has_memories(database_name)
|
|
37
|
+
|
|
38
|
+
def format_memories_for_prompt(self, database_name: str) -> str:
|
|
39
|
+
"""Format memories for inclusion in system prompt."""
|
|
40
|
+
memories = self.get_memories(database_name)
|
|
41
|
+
|
|
42
|
+
if not memories:
|
|
43
|
+
return ""
|
|
44
|
+
|
|
45
|
+
formatted_memories = []
|
|
46
|
+
for memory in memories:
|
|
47
|
+
formatted_memories.append(f"- {memory.content}")
|
|
48
|
+
|
|
49
|
+
return f"""
|
|
50
|
+
Previous context from user:
|
|
51
|
+
{chr(10).join(formatted_memories)}
|
|
52
|
+
|
|
53
|
+
Use this context to better understand the user's needs and provide more relevant responses.
|
|
54
|
+
"""
|
|
55
|
+
|
|
56
|
+
def get_memories_summary(self, database_name: str) -> dict:
|
|
57
|
+
"""Get a summary of memories for a database."""
|
|
58
|
+
memories = self.get_memories(database_name)
|
|
59
|
+
|
|
60
|
+
return {
|
|
61
|
+
"database": database_name,
|
|
62
|
+
"total_memories": len(memories),
|
|
63
|
+
"memories": [
|
|
64
|
+
{
|
|
65
|
+
"id": memory.id,
|
|
66
|
+
"content": memory.content[:100] + "..."
|
|
67
|
+
if len(memory.content) > 100
|
|
68
|
+
else memory.content,
|
|
69
|
+
"timestamp": memory.formatted_timestamp(),
|
|
70
|
+
}
|
|
71
|
+
for memory in memories
|
|
72
|
+
],
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
def list_databases_with_memories(self) -> List[str]:
|
|
76
|
+
"""List all databases that have memories."""
|
|
77
|
+
return self.storage.list_databases_with_memories()
|
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
"""Memory storage implementation for database-specific memories."""
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
import os
|
|
5
|
+
import platform
|
|
6
|
+
import stat
|
|
7
|
+
import time
|
|
8
|
+
import uuid
|
|
9
|
+
from dataclasses import dataclass
|
|
10
|
+
from pathlib import Path
|
|
11
|
+
from typing import Dict, List, Optional
|
|
12
|
+
|
|
13
|
+
import platformdirs
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
@dataclass
|
|
17
|
+
class Memory:
|
|
18
|
+
"""Represents a single memory entry."""
|
|
19
|
+
|
|
20
|
+
id: str
|
|
21
|
+
content: str
|
|
22
|
+
timestamp: float
|
|
23
|
+
|
|
24
|
+
def to_dict(self) -> Dict:
|
|
25
|
+
"""Convert memory to dictionary for JSON serialization."""
|
|
26
|
+
return {
|
|
27
|
+
"id": self.id,
|
|
28
|
+
"content": self.content,
|
|
29
|
+
"timestamp": self.timestamp,
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
@classmethod
|
|
33
|
+
def from_dict(cls, data: Dict) -> "Memory":
|
|
34
|
+
"""Create Memory from dictionary."""
|
|
35
|
+
return cls(
|
|
36
|
+
id=data["id"],
|
|
37
|
+
content=data["content"],
|
|
38
|
+
timestamp=data["timestamp"],
|
|
39
|
+
)
|
|
40
|
+
|
|
41
|
+
def formatted_timestamp(self) -> str:
|
|
42
|
+
"""Get human-readable timestamp."""
|
|
43
|
+
return time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(self.timestamp))
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
class MemoryStorage:
|
|
47
|
+
"""Handles storage and retrieval of database-specific memories."""
|
|
48
|
+
|
|
49
|
+
def __init__(self):
|
|
50
|
+
self.memory_dir = Path(platformdirs.user_config_dir("sqlsaber")) / "memories"
|
|
51
|
+
self._ensure_memory_dir()
|
|
52
|
+
|
|
53
|
+
def _ensure_memory_dir(self) -> None:
|
|
54
|
+
"""Ensure memory directory exists with proper permissions."""
|
|
55
|
+
self.memory_dir.mkdir(parents=True, exist_ok=True)
|
|
56
|
+
self._set_secure_permissions(self.memory_dir, is_directory=True)
|
|
57
|
+
|
|
58
|
+
def _set_secure_permissions(self, path: Path, is_directory: bool = False) -> None:
|
|
59
|
+
"""Set secure permissions cross-platform."""
|
|
60
|
+
try:
|
|
61
|
+
if platform.system() == "Windows":
|
|
62
|
+
# On Windows, rely on NTFS permissions and avoid chmod
|
|
63
|
+
return
|
|
64
|
+
else:
|
|
65
|
+
# Unix-like systems (Linux, macOS)
|
|
66
|
+
if is_directory:
|
|
67
|
+
os.chmod(
|
|
68
|
+
path, stat.S_IRWXU
|
|
69
|
+
) # 0o700 - owner read/write/execute only
|
|
70
|
+
else:
|
|
71
|
+
os.chmod(
|
|
72
|
+
path, stat.S_IRUSR | stat.S_IWUSR
|
|
73
|
+
) # 0o600 - owner read/write only
|
|
74
|
+
except (OSError, PermissionError):
|
|
75
|
+
# If we can't set permissions, continue anyway
|
|
76
|
+
pass
|
|
77
|
+
|
|
78
|
+
def _get_memory_file(self, database_name: str) -> Path:
|
|
79
|
+
"""Get the memory file path for a specific database."""
|
|
80
|
+
return self.memory_dir / f"{database_name}.json"
|
|
81
|
+
|
|
82
|
+
def _load_memories(self, database_name: str) -> List[Memory]:
|
|
83
|
+
"""Load memories for a specific database."""
|
|
84
|
+
memory_file = self._get_memory_file(database_name)
|
|
85
|
+
|
|
86
|
+
if not memory_file.exists():
|
|
87
|
+
return []
|
|
88
|
+
|
|
89
|
+
try:
|
|
90
|
+
with open(memory_file, "r") as f:
|
|
91
|
+
data = json.load(f)
|
|
92
|
+
return [
|
|
93
|
+
Memory.from_dict(memory_data)
|
|
94
|
+
for memory_data in data.get("memories", [])
|
|
95
|
+
]
|
|
96
|
+
except (json.JSONDecodeError, IOError, KeyError):
|
|
97
|
+
return []
|
|
98
|
+
|
|
99
|
+
def _save_memories(self, database_name: str, memories: List[Memory]) -> None:
|
|
100
|
+
"""Save memories for a specific database."""
|
|
101
|
+
memory_file = self._get_memory_file(database_name)
|
|
102
|
+
|
|
103
|
+
data = {
|
|
104
|
+
"database": database_name,
|
|
105
|
+
"memories": [memory.to_dict() for memory in memories],
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
with open(memory_file, "w") as f:
|
|
109
|
+
json.dump(data, f, indent=2)
|
|
110
|
+
|
|
111
|
+
# Set secure permissions
|
|
112
|
+
self._set_secure_permissions(memory_file, is_directory=False)
|
|
113
|
+
|
|
114
|
+
def add_memory(self, database_name: str, content: str) -> Memory:
|
|
115
|
+
"""Add a new memory for the specified database."""
|
|
116
|
+
memory = Memory(
|
|
117
|
+
id=str(uuid.uuid4()),
|
|
118
|
+
content=content.strip(),
|
|
119
|
+
timestamp=time.time(),
|
|
120
|
+
)
|
|
121
|
+
|
|
122
|
+
memories = self._load_memories(database_name)
|
|
123
|
+
memories.append(memory)
|
|
124
|
+
self._save_memories(database_name, memories)
|
|
125
|
+
|
|
126
|
+
return memory
|
|
127
|
+
|
|
128
|
+
def get_memories(self, database_name: str) -> List[Memory]:
|
|
129
|
+
"""Get all memories for the specified database."""
|
|
130
|
+
return self._load_memories(database_name)
|
|
131
|
+
|
|
132
|
+
def remove_memory(self, database_name: str, memory_id: str) -> bool:
|
|
133
|
+
"""Remove a specific memory by ID."""
|
|
134
|
+
memories = self._load_memories(database_name)
|
|
135
|
+
original_count = len(memories)
|
|
136
|
+
|
|
137
|
+
memories = [m for m in memories if m.id != memory_id]
|
|
138
|
+
|
|
139
|
+
if len(memories) < original_count:
|
|
140
|
+
self._save_memories(database_name, memories)
|
|
141
|
+
return True
|
|
142
|
+
|
|
143
|
+
return False
|
|
144
|
+
|
|
145
|
+
def clear_memories(self, database_name: str) -> int:
|
|
146
|
+
"""Clear all memories for the specified database."""
|
|
147
|
+
memories = self._load_memories(database_name)
|
|
148
|
+
count = len(memories)
|
|
149
|
+
|
|
150
|
+
if count > 0:
|
|
151
|
+
self._save_memories(database_name, [])
|
|
152
|
+
|
|
153
|
+
return count
|
|
154
|
+
|
|
155
|
+
def get_memory_by_id(self, database_name: str, memory_id: str) -> Optional[Memory]:
|
|
156
|
+
"""Get a specific memory by ID."""
|
|
157
|
+
memories = self._load_memories(database_name)
|
|
158
|
+
return next((m for m in memories if m.id == memory_id), None)
|
|
159
|
+
|
|
160
|
+
def has_memories(self, database_name: str) -> bool:
|
|
161
|
+
"""Check if database has any memories."""
|
|
162
|
+
return len(self._load_memories(database_name)) > 0
|
|
163
|
+
|
|
164
|
+
def list_databases_with_memories(self) -> List[str]:
|
|
165
|
+
"""List all databases that have memories."""
|
|
166
|
+
databases = []
|
|
167
|
+
|
|
168
|
+
if not self.memory_dir.exists():
|
|
169
|
+
return databases
|
|
170
|
+
|
|
171
|
+
for memory_file in self.memory_dir.glob("*.json"):
|
|
172
|
+
database_name = memory_file.stem
|
|
173
|
+
if self.has_memories(database_name):
|
|
174
|
+
databases.append(database_name)
|
|
175
|
+
|
|
176
|
+
return databases
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
"""Models module for SQLSaber."""
|
|
2
|
+
|
|
3
|
+
from .events import StreamEvent, SQLResponse
|
|
4
|
+
from .types import ColumnInfo, ForeignKeyInfo, SchemaInfo, ToolDefinition
|
|
5
|
+
|
|
6
|
+
__all__ = [
|
|
7
|
+
"StreamEvent",
|
|
8
|
+
"SQLResponse",
|
|
9
|
+
"ColumnInfo",
|
|
10
|
+
"ForeignKeyInfo",
|
|
11
|
+
"SchemaInfo",
|
|
12
|
+
"ToolDefinition",
|
|
13
|
+
]
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
"""Event models for streaming and responses."""
|
|
2
|
+
|
|
3
|
+
from typing import Any, Dict, List, Optional
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class StreamEvent:
|
|
7
|
+
"""Event emitted during streaming processing."""
|
|
8
|
+
|
|
9
|
+
def __init__(self, event_type: str, data: Any = None):
|
|
10
|
+
# 'tool_use', 'text', 'query_result', 'error', 'processing'
|
|
11
|
+
self.type = event_type
|
|
12
|
+
self.data = data
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class SQLResponse:
|
|
16
|
+
"""Response from the SQL agent."""
|
|
17
|
+
|
|
18
|
+
def __init__(
|
|
19
|
+
self,
|
|
20
|
+
query: Optional[str] = None,
|
|
21
|
+
explanation: str = "",
|
|
22
|
+
results: Optional[List[Dict[str, Any]]] = None,
|
|
23
|
+
error: Optional[str] = None,
|
|
24
|
+
):
|
|
25
|
+
self.query = query
|
|
26
|
+
self.explanation = explanation
|
|
27
|
+
self.results = results
|
|
28
|
+
self.error = error
|
sqlsaber/models/types.py
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
"""Type definitions for SQLSaber."""
|
|
2
|
+
|
|
3
|
+
from typing import Any, Dict, List, Optional, TypedDict
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class ColumnInfo(TypedDict):
|
|
7
|
+
"""Type definition for column information."""
|
|
8
|
+
|
|
9
|
+
data_type: str
|
|
10
|
+
nullable: bool
|
|
11
|
+
default: Optional[str]
|
|
12
|
+
max_length: Optional[int]
|
|
13
|
+
precision: Optional[int]
|
|
14
|
+
scale: Optional[int]
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class ForeignKeyInfo(TypedDict):
|
|
18
|
+
"""Type definition for foreign key information."""
|
|
19
|
+
|
|
20
|
+
column: str
|
|
21
|
+
references: Dict[str, str] # {"table": "schema.table", "column": "column_name"}
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class SchemaInfo(TypedDict):
|
|
25
|
+
"""Type definition for schema information."""
|
|
26
|
+
|
|
27
|
+
schema: str
|
|
28
|
+
name: str
|
|
29
|
+
type: str
|
|
30
|
+
columns: Dict[str, ColumnInfo]
|
|
31
|
+
primary_keys: List[str]
|
|
32
|
+
foreign_keys: List[ForeignKeyInfo]
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
class ToolDefinition(TypedDict):
|
|
36
|
+
"""Type definition for tool definition."""
|
|
37
|
+
|
|
38
|
+
name: str
|
|
39
|
+
description: str
|
|
40
|
+
input_schema: Dict[str, Any]
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: sqlsaber
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: SQLSaber - Agentic SQL assistant like Claude Code
|
|
5
|
+
License-File: LICENSE
|
|
6
|
+
Requires-Python: >=3.12
|
|
7
|
+
Requires-Dist: aiomysql>=0.2.0
|
|
8
|
+
Requires-Dist: aiosqlite>=0.21.0
|
|
9
|
+
Requires-Dist: anthropic>=0.54.0
|
|
10
|
+
Requires-Dist: asyncpg>=0.30.0
|
|
11
|
+
Requires-Dist: httpx>=0.28.1
|
|
12
|
+
Requires-Dist: keyring>=25.6.0
|
|
13
|
+
Requires-Dist: platformdirs>=4.0.0
|
|
14
|
+
Requires-Dist: questionary>=2.1.0
|
|
15
|
+
Requires-Dist: rich>=13.7.0
|
|
16
|
+
Requires-Dist: typer>=0.16.0
|
|
17
|
+
Description-Content-Type: text/markdown
|
|
18
|
+
|
|
19
|
+
# SQLSaber
|
|
20
|
+
|
|
21
|
+
> Use the agent Luke!
|
|
22
|
+
|
|
23
|
+
SQLSaber is an agentic SQL assistant. Think Claude Code but for SQL.
|
|
24
|
+
|
|
25
|
+
Ask your questions in natural language and it will gather the right context and answer your query by writing SQL and analyzing the results.
|
|
26
|
+
|
|
27
|
+
## Features
|
|
28
|
+
|
|
29
|
+
- Natural language to SQL conversion
|
|
30
|
+
- 🔍 Automatic database schema introspection
|
|
31
|
+
- 🛡️ Safe query execution (read-only by default)
|
|
32
|
+
- 🧠 Memory management
|
|
33
|
+
- 💬 Interactive REPL mode
|
|
34
|
+
- 🎨 Beautiful formatted output with syntax highlighting
|
|
35
|
+
- 🗄️ Support for PostgreSQL, SQLite, and MySQL
|
|
36
|
+
|
|
37
|
+
## Installation
|
|
38
|
+
|
|
39
|
+
```bash
|
|
40
|
+
uv tool install sqlsaber
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
or
|
|
44
|
+
|
|
45
|
+
```bash
|
|
46
|
+
pipx install sqlsaber
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
## Configuration
|
|
50
|
+
|
|
51
|
+
### Database Connection
|
|
52
|
+
|
|
53
|
+
Set your database connection URL:
|
|
54
|
+
|
|
55
|
+
```bash
|
|
56
|
+
saber db add DB_NAME
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
This will ask you some questions about your database connection
|
|
60
|
+
|
|
61
|
+
### AI Model Configuration
|
|
62
|
+
|
|
63
|
+
SQLSaber uses Sonnet-4 by default. You can change it using:
|
|
64
|
+
|
|
65
|
+
```bash
|
|
66
|
+
saber models set
|
|
67
|
+
|
|
68
|
+
# for more model settings run:
|
|
69
|
+
saber models --help
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
### Memory Management
|
|
73
|
+
|
|
74
|
+
You can add specific context about your database to the model using the memory feature. This is similar to how you add memory/context in Claude Code.
|
|
75
|
+
|
|
76
|
+
```bash
|
|
77
|
+
saber memory add 'always convert dates to string for easier formating'
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
View all memories
|
|
81
|
+
|
|
82
|
+
```bash
|
|
83
|
+
saber memory list
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
> You can also add memories in an interactive query session by starting with the `#` sign
|
|
87
|
+
|
|
88
|
+
## Usage
|
|
89
|
+
|
|
90
|
+
### Interactive Mode
|
|
91
|
+
|
|
92
|
+
Start an interactive session:
|
|
93
|
+
|
|
94
|
+
```bash
|
|
95
|
+
saber query
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
> You can also add memories in an interactive session by starting your message with the `#` sign
|
|
99
|
+
|
|
100
|
+
### Single Query
|
|
101
|
+
|
|
102
|
+
Execute a single natural language query:
|
|
103
|
+
|
|
104
|
+
```bash
|
|
105
|
+
saber query "show me all users created this month"
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
### Database Selection
|
|
109
|
+
|
|
110
|
+
Use a specific database connection:
|
|
111
|
+
|
|
112
|
+
```bash
|
|
113
|
+
# Use named database from config
|
|
114
|
+
saber query -d mydb "count all orders"
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
## Examples
|
|
118
|
+
|
|
119
|
+
```bash
|
|
120
|
+
# Show database schema
|
|
121
|
+
saber query "what tables are in my database?"
|
|
122
|
+
|
|
123
|
+
# Count records
|
|
124
|
+
saber query "how many active users do we have?"
|
|
125
|
+
|
|
126
|
+
# Complex queries with joins
|
|
127
|
+
saber query "show me orders with customer details for this week"
|
|
128
|
+
|
|
129
|
+
# Aggregations
|
|
130
|
+
saber query "what's the total revenue by product category?"
|
|
131
|
+
|
|
132
|
+
# Date filtering
|
|
133
|
+
saber query "list users who haven't logged in for 30 days"
|
|
134
|
+
|
|
135
|
+
# Data exploration
|
|
136
|
+
saber query "show me the distribution of customer ages"
|
|
137
|
+
|
|
138
|
+
# Business analytics
|
|
139
|
+
saber query "which products had the highest sales growth last quarter?"
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
## How It Works
|
|
143
|
+
|
|
144
|
+
SQLSaber uses an intelligent three-step process optimized for minimal token usage:
|
|
145
|
+
|
|
146
|
+
### 🔍 Discovery Phase
|
|
147
|
+
|
|
148
|
+
1. **List Tables Tool**: Quickly discovers available tables with row counts
|
|
149
|
+
2. **Pattern Matching**: Identifies relevant tables based on your query using SQL LIKE patterns
|
|
150
|
+
|
|
151
|
+
### 📋 Schema Analysis
|
|
152
|
+
|
|
153
|
+
3. **Smart Introspection**: Analyzes only the specific table structures needed for your query
|
|
154
|
+
4. **Selective Loading**: Fetches schema information only for relevant tables
|
|
155
|
+
|
|
156
|
+
### ⚡ Execution Phase
|
|
157
|
+
|
|
158
|
+
5. **SQL Generation**: Creates optimized SQL queries based on natural language input
|
|
159
|
+
6. **Safe Execution**: Runs queries with built-in protections against destructive operations
|
|
160
|
+
7. **Result Formatting**: Presents results with syntax highlighting and explanations
|
|
161
|
+
|
|
162
|
+
## Contributing
|
|
163
|
+
|
|
164
|
+
Contributions are welcome! Please feel free to open an issue to discuss your ideas or report bugs.
|
|
165
|
+
|
|
166
|
+
## License
|
|
167
|
+
|
|
168
|
+
This project is licensed under Apache-2.0 License - see the LICENSE file for details.
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
sqlsaber/__init__.py,sha256=QCFi8xTVMohelfi7zOV1-6oLCcGoiXoOcKQY-HNBCk8,66
|
|
2
|
+
sqlsaber/__main__.py,sha256=RIHxWeWh2QvLfah-2OkhI5IJxojWfy4fXpMnVEJYvxw,78
|
|
3
|
+
sqlsaber/agents/__init__.py,sha256=LWeSeEUE4BhkyAYFF3TE-fx8TtLud3oyEtyB8ojFJgo,167
|
|
4
|
+
sqlsaber/agents/anthropic.py,sha256=CPNshN68QxxdIvWS5YjEuiXd_8V8mmMTL9aC2Mqkvts,17863
|
|
5
|
+
sqlsaber/agents/base.py,sha256=UUSGhoJImATXrYS7yrLR2qjg1iFW4udOUdRaV3Ryk5s,2086
|
|
6
|
+
sqlsaber/agents/streaming.py,sha256=0bNzd_JhLlgQB40pf9FZFMvmU9Q7W6D9BmglA1rIGqw,850
|
|
7
|
+
sqlsaber/cli/__init__.py,sha256=qVSLVJLLJYzoC6aj6y9MFrzZvAwc4_OgxU9DlkQnZ4M,86
|
|
8
|
+
sqlsaber/cli/commands.py,sha256=Adrt_0LRgykb2FZ4F0TQpuBM8Z0qgfbggn0FexcVALI,4094
|
|
9
|
+
sqlsaber/cli/database.py,sha256=W-tJqmihKjZhoe5AGpQKe0txzLIgRVGikZHM_ELAbnQ,9138
|
|
10
|
+
sqlsaber/cli/display.py,sha256=5J4AgJADmMwKi9Aq5u6_MKRO1TA6unS4F4RUfml_sfU,7651
|
|
11
|
+
sqlsaber/cli/interactive.py,sha256=y92rdoM49SOSwEctm9ZcrEN220fhJ_DMHPSd_7KsORg,3701
|
|
12
|
+
sqlsaber/cli/memory.py,sha256=LW4ZF2V6Gw6hviUFGZ4ym9ostFCwucgBTIMZ3EANO-I,7671
|
|
13
|
+
sqlsaber/cli/models.py,sha256=3IcXeeU15IQvemSv-V-RQzVytJ3wuQ4YmWk89nTDcSE,7813
|
|
14
|
+
sqlsaber/cli/streaming.py,sha256=5QGAYTAvg9mzQLxDEVtdDH-TIbGfYYzMOLoOYPrHPu0,3788
|
|
15
|
+
sqlsaber/config/__init__.py,sha256=olwC45k8Nc61yK0WmPUk7XHdbsZH9HuUAbwnmKe3IgA,100
|
|
16
|
+
sqlsaber/config/api_keys.py,sha256=kLdoExF_My9ojmdhO5Ca7-ZeowsO0v1GVa_QT5jjUPo,3658
|
|
17
|
+
sqlsaber/config/database.py,sha256=FX4zwmOkW-lvIH--c8xRyoyyjYLjn3OQTkSruEw-aQY,8790
|
|
18
|
+
sqlsaber/config/settings.py,sha256=zjQ7nS3ybcCb88Ea0tmwJox5-q0ettChZw89ZqRVpX8,3975
|
|
19
|
+
sqlsaber/database/__init__.py,sha256=a_gtKRJnZVO8-fEZI7g3Z8YnGa6Nio-5Y50PgVp07ss,176
|
|
20
|
+
sqlsaber/database/connection.py,sha256=Z1iIRBIoPQcCfBliROLeebEQeI7ggu-hh_G1l-tzhIM,6672
|
|
21
|
+
sqlsaber/database/schema.py,sha256=gURfCFVE--UWIqD_0StqS2NMB9VIPpqczBEoS2GnKR4,27025
|
|
22
|
+
sqlsaber/memory/__init__.py,sha256=GiWkU6f6YYVV0EvvXDmFWe_CxarmDCql05t70MkTEWs,63
|
|
23
|
+
sqlsaber/memory/manager.py,sha256=ML2NEO5Z4Aw36sEI9eOvWVnjl-qT2VOTojViJAj7Seo,2777
|
|
24
|
+
sqlsaber/memory/storage.py,sha256=DvZBsSPaAfk_DqrNEn86uMD-TQsWUI6rQLfNw6PSCB8,5788
|
|
25
|
+
sqlsaber/models/__init__.py,sha256=RJ7p3WtuSwwpFQ1Iw4_DHV2zzCtHqIzsjJzxv8kUjUE,287
|
|
26
|
+
sqlsaber/models/events.py,sha256=55m41tDwMsFxnKKA5_VLJz8iV-V4Sq3LDfta4VoutJI,737
|
|
27
|
+
sqlsaber/models/types.py,sha256=3U_30n91EB3IglBTHipwiW4MqmmaA2qfshfraMZyPps,896
|
|
28
|
+
sqlsaber-0.1.0.dist-info/METADATA,sha256=37Xik234wv415wWsvIWr4XFjzHiQV39EnDAYyZsMgXA,3953
|
|
29
|
+
sqlsaber-0.1.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
|
30
|
+
sqlsaber-0.1.0.dist-info/entry_points.txt,sha256=POwcsEskUp7xQQWabrAi6Eawz4qc5eBlB3KzAiBq-Y0,124
|
|
31
|
+
sqlsaber-0.1.0.dist-info/licenses/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
|
|
32
|
+
sqlsaber-0.1.0.dist-info/RECORD,,
|