sqlsaber 0.27.0__py3-none-any.whl → 0.28.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.
- sqlsaber/application/auth_setup.py +2 -2
- sqlsaber/application/db_setup.py +2 -3
- sqlsaber/application/model_selection.py +2 -2
- sqlsaber/cli/auth.py +2 -2
- sqlsaber/cli/commands.py +2 -2
- sqlsaber/cli/database.py +2 -2
- sqlsaber/cli/display.py +59 -40
- sqlsaber/cli/interactive.py +18 -27
- sqlsaber/cli/memory.py +2 -2
- sqlsaber/cli/models.py +2 -2
- sqlsaber/cli/onboarding.py +2 -2
- sqlsaber/cli/streaming.py +1 -1
- sqlsaber/cli/threads.py +35 -16
- sqlsaber/config/api_keys.py +2 -2
- sqlsaber/config/oauth_flow.py +3 -2
- sqlsaber/config/oauth_tokens.py +3 -5
- sqlsaber/database/base.py +6 -0
- sqlsaber/database/csv.py +5 -0
- sqlsaber/database/duckdb.py +5 -0
- sqlsaber/database/mysql.py +5 -0
- sqlsaber/database/postgresql.py +5 -0
- sqlsaber/database/sqlite.py +5 -0
- sqlsaber/theme/__init__.py +5 -0
- sqlsaber/theme/manager.py +219 -0
- sqlsaber/tools/sql_guard.py +225 -0
- sqlsaber/tools/sql_tools.py +10 -35
- {sqlsaber-0.27.0.dist-info → sqlsaber-0.28.0.dist-info}/METADATA +2 -1
- {sqlsaber-0.27.0.dist-info → sqlsaber-0.28.0.dist-info}/RECORD +31 -28
- {sqlsaber-0.27.0.dist-info → sqlsaber-0.28.0.dist-info}/WHEEL +0 -0
- {sqlsaber-0.27.0.dist-info → sqlsaber-0.28.0.dist-info}/entry_points.txt +0 -0
- {sqlsaber-0.27.0.dist-info → sqlsaber-0.28.0.dist-info}/licenses/LICENSE +0 -0
|
@@ -0,0 +1,225 @@
|
|
|
1
|
+
"""SQL query validation and security using sqlglot AST analysis."""
|
|
2
|
+
|
|
3
|
+
from dataclasses import dataclass
|
|
4
|
+
from typing import Optional
|
|
5
|
+
|
|
6
|
+
import sqlglot
|
|
7
|
+
from sqlglot import exp
|
|
8
|
+
from sqlglot.errors import ParseError
|
|
9
|
+
|
|
10
|
+
# Prohibited AST node types that indicate write/mutation operations
|
|
11
|
+
# Only include expression types that exist in sqlglot
|
|
12
|
+
PROHIBITED_NODES = {
|
|
13
|
+
# DML operations
|
|
14
|
+
exp.Insert,
|
|
15
|
+
exp.Update,
|
|
16
|
+
exp.Delete,
|
|
17
|
+
exp.Merge,
|
|
18
|
+
# DDL operations
|
|
19
|
+
exp.Create,
|
|
20
|
+
exp.Drop,
|
|
21
|
+
exp.Alter,
|
|
22
|
+
exp.TruncateTable,
|
|
23
|
+
exp.AlterRename,
|
|
24
|
+
# MySQL specific
|
|
25
|
+
exp.Replace,
|
|
26
|
+
# Transaction control
|
|
27
|
+
exp.Transaction,
|
|
28
|
+
# Analysis and maintenance
|
|
29
|
+
exp.Analyze,
|
|
30
|
+
# Data loading/copying
|
|
31
|
+
exp.Copy,
|
|
32
|
+
exp.LoadData,
|
|
33
|
+
# Session and configuration
|
|
34
|
+
exp.Set,
|
|
35
|
+
exp.Use,
|
|
36
|
+
exp.Pragma,
|
|
37
|
+
# Security
|
|
38
|
+
exp.Grant,
|
|
39
|
+
exp.Revoke,
|
|
40
|
+
# Database operations
|
|
41
|
+
exp.Attach,
|
|
42
|
+
exp.Detach,
|
|
43
|
+
# Locking and process control
|
|
44
|
+
exp.Lock,
|
|
45
|
+
exp.Kill,
|
|
46
|
+
# Commands
|
|
47
|
+
exp.Command,
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
try:
|
|
51
|
+
# Add optional types that may not exist in all sqlglot versions
|
|
52
|
+
PROHIBITED_NODES.add(exp.Vacuum)
|
|
53
|
+
except AttributeError:
|
|
54
|
+
pass
|
|
55
|
+
|
|
56
|
+
# Dangerous functions by dialect that can read files or execute commands
|
|
57
|
+
DANGEROUS_FUNCTIONS_BY_DIALECT = {
|
|
58
|
+
"postgres": {
|
|
59
|
+
"pg_read_file",
|
|
60
|
+
"pg_read_binary_file",
|
|
61
|
+
"pg_ls_dir",
|
|
62
|
+
"pg_stat_file",
|
|
63
|
+
"pg_logdir_ls",
|
|
64
|
+
"dblink",
|
|
65
|
+
"dblink_exec",
|
|
66
|
+
},
|
|
67
|
+
"mysql": {
|
|
68
|
+
"load_file",
|
|
69
|
+
"sys_eval",
|
|
70
|
+
"sys_exec",
|
|
71
|
+
},
|
|
72
|
+
"sqlite": {
|
|
73
|
+
"readfile",
|
|
74
|
+
"writefile",
|
|
75
|
+
},
|
|
76
|
+
"tsql": {
|
|
77
|
+
"xp_cmdshell",
|
|
78
|
+
},
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
@dataclass
|
|
83
|
+
class GuardResult:
|
|
84
|
+
"""Result of SQL query validation."""
|
|
85
|
+
|
|
86
|
+
allowed: bool
|
|
87
|
+
reason: Optional[str] = None
|
|
88
|
+
is_select: bool = False
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
def is_select_like(stmt: exp.Expression) -> bool:
|
|
92
|
+
"""Check if statement is a SELECT-like query.
|
|
93
|
+
|
|
94
|
+
Handles CTEs (WITH) and set operations (UNION/INTERSECT/EXCEPT).
|
|
95
|
+
"""
|
|
96
|
+
root = stmt
|
|
97
|
+
# WITH wraps another statement
|
|
98
|
+
if isinstance(root, exp.With):
|
|
99
|
+
root = root.this
|
|
100
|
+
return isinstance(root, (exp.Select, exp.Union, exp.Except, exp.Intersect))
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
def has_prohibited_nodes(stmt: exp.Expression) -> Optional[str]:
|
|
104
|
+
"""Walk AST to find any prohibited operations.
|
|
105
|
+
|
|
106
|
+
Checks for:
|
|
107
|
+
- Write operations (INSERT/UPDATE/DELETE/etc)
|
|
108
|
+
- DDL operations (CREATE/DROP/ALTER/etc)
|
|
109
|
+
- SELECT INTO
|
|
110
|
+
- Locking clauses (FOR UPDATE/FOR SHARE)
|
|
111
|
+
"""
|
|
112
|
+
for node in stmt.walk():
|
|
113
|
+
# Check prohibited node types
|
|
114
|
+
if isinstance(node, tuple(PROHIBITED_NODES)):
|
|
115
|
+
return f"Prohibited operation: {type(node).__name__}"
|
|
116
|
+
|
|
117
|
+
# Block SELECT INTO (Postgres-style table creation)
|
|
118
|
+
if isinstance(node, exp.Select) and node.args.get("into"):
|
|
119
|
+
return "SELECT INTO is not allowed"
|
|
120
|
+
|
|
121
|
+
# Block locking clauses (FOR UPDATE/FOR SHARE)
|
|
122
|
+
if isinstance(node, exp.Select):
|
|
123
|
+
locks = node.args.get("locks")
|
|
124
|
+
if locks:
|
|
125
|
+
return "SELECT with locking clause (FOR UPDATE/SHARE) is not allowed"
|
|
126
|
+
|
|
127
|
+
return None
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
def has_dangerous_functions(stmt: exp.Expression, dialect: str) -> Optional[str]:
|
|
131
|
+
"""Check for dangerous functions that can read files or execute commands."""
|
|
132
|
+
deny_set = DANGEROUS_FUNCTIONS_BY_DIALECT.get(dialect, set())
|
|
133
|
+
if not deny_set:
|
|
134
|
+
return None
|
|
135
|
+
|
|
136
|
+
deny_lower = {f.lower() for f in deny_set}
|
|
137
|
+
|
|
138
|
+
for fn in stmt.find_all(exp.Func):
|
|
139
|
+
name = fn.name
|
|
140
|
+
if name and name.lower() in deny_lower:
|
|
141
|
+
return f"Use of dangerous function '{name}' is not allowed"
|
|
142
|
+
|
|
143
|
+
return None
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
def validate_read_only(sql: str, dialect: str = "ansi") -> GuardResult:
|
|
147
|
+
"""Validate that SQL query is read-only using AST analysis.
|
|
148
|
+
|
|
149
|
+
Args:
|
|
150
|
+
sql: SQL query to validate
|
|
151
|
+
dialect: SQL dialect (postgres, mysql, sqlite, tsql, etc.)
|
|
152
|
+
|
|
153
|
+
Returns:
|
|
154
|
+
GuardResult with validation outcome
|
|
155
|
+
"""
|
|
156
|
+
try:
|
|
157
|
+
statements = sqlglot.parse(sql, read=dialect)
|
|
158
|
+
except ParseError as e:
|
|
159
|
+
return GuardResult(False, f"Unable to parse query safely: {e}")
|
|
160
|
+
except Exception as e:
|
|
161
|
+
return GuardResult(False, f"Error parsing query: {e}")
|
|
162
|
+
|
|
163
|
+
# Only allow single statements
|
|
164
|
+
if len(statements) != 1:
|
|
165
|
+
return GuardResult(
|
|
166
|
+
False,
|
|
167
|
+
f"Only single SELECT statements are allowed (got {len(statements)} statements)",
|
|
168
|
+
)
|
|
169
|
+
|
|
170
|
+
stmt = statements[0]
|
|
171
|
+
|
|
172
|
+
# Must be a SELECT-like statement
|
|
173
|
+
if not is_select_like(stmt):
|
|
174
|
+
return GuardResult(False, "Only SELECT-like statements are allowed")
|
|
175
|
+
|
|
176
|
+
# Check for prohibited operations in the AST
|
|
177
|
+
reason = has_prohibited_nodes(stmt)
|
|
178
|
+
if reason:
|
|
179
|
+
return GuardResult(False, reason)
|
|
180
|
+
|
|
181
|
+
# Check for dangerous functions
|
|
182
|
+
reason = has_dangerous_functions(stmt, dialect)
|
|
183
|
+
if reason:
|
|
184
|
+
return GuardResult(False, reason)
|
|
185
|
+
|
|
186
|
+
return GuardResult(True, None, is_select=True)
|
|
187
|
+
|
|
188
|
+
|
|
189
|
+
def add_limit(sql: str, dialect: str = "ansi", limit: int = 100) -> str:
|
|
190
|
+
"""Add LIMIT clause to query if not already present.
|
|
191
|
+
|
|
192
|
+
Args:
|
|
193
|
+
sql: SQL query
|
|
194
|
+
dialect: SQL dialect for proper rendering
|
|
195
|
+
limit: Maximum number of rows to return
|
|
196
|
+
|
|
197
|
+
Returns:
|
|
198
|
+
SQL with LIMIT clause added (or original if LIMIT already exists)
|
|
199
|
+
"""
|
|
200
|
+
try:
|
|
201
|
+
statements = sqlglot.parse(sql, read=dialect)
|
|
202
|
+
if len(statements) != 1:
|
|
203
|
+
return sql
|
|
204
|
+
|
|
205
|
+
stmt = statements[0]
|
|
206
|
+
|
|
207
|
+
# Check if LIMIT/TOP/FETCH already exists
|
|
208
|
+
has_limit = any(
|
|
209
|
+
isinstance(n, (exp.Limit, exp.Top, exp.Fetch)) for n in stmt.walk()
|
|
210
|
+
)
|
|
211
|
+
if has_limit:
|
|
212
|
+
return stmt.sql(dialect=dialect)
|
|
213
|
+
|
|
214
|
+
# Add LIMIT - sqlglot will render appropriately for dialect
|
|
215
|
+
# (LIMIT for most, TOP for SQL Server, FETCH FIRST for Oracle)
|
|
216
|
+
stmt = stmt.limit(limit)
|
|
217
|
+
return stmt.sql(dialect=dialect)
|
|
218
|
+
|
|
219
|
+
except Exception:
|
|
220
|
+
# If parsing/transformation fails, fall back to simple string append
|
|
221
|
+
# This maintains backward compatibility
|
|
222
|
+
sql_upper = sql.strip().upper()
|
|
223
|
+
if "LIMIT" not in sql_upper:
|
|
224
|
+
return f"{sql.rstrip(';')} LIMIT {limit};"
|
|
225
|
+
return sql
|
sqlsaber/tools/sql_tools.py
CHANGED
|
@@ -9,6 +9,7 @@ from sqlsaber.database.schema import SchemaManager
|
|
|
9
9
|
from .base import Tool
|
|
10
10
|
from .enums import ToolCategory, WorkflowPosition
|
|
11
11
|
from .registry import register_tool
|
|
12
|
+
from .sql_guard import add_limit, validate_read_only
|
|
12
13
|
|
|
13
14
|
|
|
14
15
|
class SQLTool(Tool):
|
|
@@ -207,13 +208,17 @@ class ExecuteSQLTool(SQLTool):
|
|
|
207
208
|
limit = kwargs.get("limit", self.DEFAULT_LIMIT)
|
|
208
209
|
|
|
209
210
|
try:
|
|
210
|
-
#
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
211
|
+
# Get the dialect for this database
|
|
212
|
+
dialect = self.db.sqlglot_dialect
|
|
213
|
+
|
|
214
|
+
# Security check using sqlglot AST analysis
|
|
215
|
+
validation_result = validate_read_only(query, dialect)
|
|
216
|
+
if not validation_result.allowed:
|
|
217
|
+
return json.dumps({"error": validation_result.reason})
|
|
214
218
|
|
|
215
219
|
# Add LIMIT if not present and it's a SELECT query
|
|
216
|
-
|
|
220
|
+
if validation_result.is_select and limit:
|
|
221
|
+
query = add_limit(query, dialect, limit)
|
|
217
222
|
|
|
218
223
|
# Execute the query
|
|
219
224
|
results = await self.db.execute_query(query)
|
|
@@ -249,33 +254,3 @@ class ExecuteSQLTool(SQLTool):
|
|
|
249
254
|
)
|
|
250
255
|
|
|
251
256
|
return json.dumps({"error": error_msg, "suggestions": suggestions})
|
|
252
|
-
|
|
253
|
-
def _validate_write_operation(self, query: str) -> str | None:
|
|
254
|
-
"""Validate if a write operation is allowed."""
|
|
255
|
-
query_upper = query.strip().upper()
|
|
256
|
-
|
|
257
|
-
# Check for write operations
|
|
258
|
-
write_keywords = [
|
|
259
|
-
"INSERT",
|
|
260
|
-
"UPDATE",
|
|
261
|
-
"DELETE",
|
|
262
|
-
"DROP",
|
|
263
|
-
"CREATE",
|
|
264
|
-
"ALTER",
|
|
265
|
-
"TRUNCATE",
|
|
266
|
-
]
|
|
267
|
-
is_write_query = any(query_upper.startswith(kw) for kw in write_keywords)
|
|
268
|
-
|
|
269
|
-
if is_write_query:
|
|
270
|
-
return (
|
|
271
|
-
"Write operations are not allowed. Only SELECT queries are permitted."
|
|
272
|
-
)
|
|
273
|
-
|
|
274
|
-
return None
|
|
275
|
-
|
|
276
|
-
def _add_limit_to_query(self, query: str, limit: int = 100) -> str:
|
|
277
|
-
"""Add LIMIT clause to SELECT queries if not present."""
|
|
278
|
-
query_upper = query.strip().upper()
|
|
279
|
-
if query_upper.startswith("SELECT") and "LIMIT" not in query_upper:
|
|
280
|
-
return f"{query.rstrip(';')} LIMIT {limit};"
|
|
281
|
-
return query
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: sqlsaber
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.28.0
|
|
4
4
|
Summary: SQLsaber - Open-source agentic SQL assistant
|
|
5
5
|
License-File: LICENSE
|
|
6
6
|
Requires-Python: >=3.12
|
|
@@ -17,6 +17,7 @@ Requires-Dist: prompt-toolkit>3.0.51
|
|
|
17
17
|
Requires-Dist: pydantic-ai
|
|
18
18
|
Requires-Dist: questionary>=2.1.0
|
|
19
19
|
Requires-Dist: rich>=13.7.0
|
|
20
|
+
Requires-Dist: sqlglot[rs]>=27.20.0
|
|
20
21
|
Description-Content-Type: text/markdown
|
|
21
22
|
|
|
22
23
|
# SQLsaber
|
|
@@ -5,44 +5,46 @@ sqlsaber/agents/base.py,sha256=40-MKEoz5rGrqVIylV1U2DaAUSPFcC75ohRin4E3-kk,2668
|
|
|
5
5
|
sqlsaber/agents/mcp.py,sha256=Pn8tdDRUEVLYQyEi5nHRp9MKNePwHVVoeNI-uqWcr0Y,757
|
|
6
6
|
sqlsaber/agents/pydantic_ai_agent.py,sha256=wBxKz0pjOkL-HI-TXV6B67bczZNgu7k26Rr3w5usR3o,10064
|
|
7
7
|
sqlsaber/application/__init__.py,sha256=KY_-d5nEdQyAwNOsK5r-f7Tb69c63XbuEkHPeLpJal8,84
|
|
8
|
-
sqlsaber/application/auth_setup.py,sha256=
|
|
9
|
-
sqlsaber/application/db_setup.py,sha256=
|
|
10
|
-
sqlsaber/application/model_selection.py,sha256=
|
|
8
|
+
sqlsaber/application/auth_setup.py,sha256=cbS2VTwL7mwm24UFRz84PhC13XMzC1F7JkN-ze7tApY,5104
|
|
9
|
+
sqlsaber/application/db_setup.py,sha256=qtMxCd_KO7GsD4W_iRBpDRvLriiyvOXPvZdcvm6KVDM,6849
|
|
10
|
+
sqlsaber/application/model_selection.py,sha256=xZI-nvUgYZikaTK38SCmEWvWSfRsDpFu2VthbVdI95g,3187
|
|
11
11
|
sqlsaber/application/prompts.py,sha256=4rMGcWpYJbNWPMzqVWseUMx0nwvXOkWS6GaTAJ5mhfc,3473
|
|
12
12
|
sqlsaber/cli/__init__.py,sha256=qVSLVJLLJYzoC6aj6y9MFrzZvAwc4_OgxU9DlkQnZ4M,86
|
|
13
|
-
sqlsaber/cli/auth.py,sha256=
|
|
14
|
-
sqlsaber/cli/commands.py,sha256
|
|
13
|
+
sqlsaber/cli/auth.py,sha256=ysDBXEFR8Jz7wYbIP6X7yWA2ivd8SDnUp_jUg_qYNWk,6088
|
|
14
|
+
sqlsaber/cli/commands.py,sha256=-rTxr-kW7j2rR8wAg0tATKoh284pMDPKVMpQKaJwtqk,8540
|
|
15
15
|
sqlsaber/cli/completers.py,sha256=g-hLDq5fiBx7gg8Bte1Lq8GU-ZxCYVs4dcPsmHPIcK4,6574
|
|
16
|
-
sqlsaber/cli/database.py,sha256=
|
|
17
|
-
sqlsaber/cli/display.py,sha256=
|
|
18
|
-
sqlsaber/cli/interactive.py,sha256=
|
|
19
|
-
sqlsaber/cli/memory.py,sha256=
|
|
20
|
-
sqlsaber/cli/models.py,sha256=
|
|
21
|
-
sqlsaber/cli/onboarding.py,sha256=
|
|
22
|
-
sqlsaber/cli/streaming.py,sha256=
|
|
23
|
-
sqlsaber/cli/threads.py,sha256=
|
|
16
|
+
sqlsaber/cli/database.py,sha256=hh8PdWnhaD0fO2jwvSSQyxsjwk-JyvmcY7f5tuHfnAQ,10663
|
|
17
|
+
sqlsaber/cli/display.py,sha256=WB5JCumhXadziDEX1EZHG3vN1Chol5FNAaTXHieqFK0,17892
|
|
18
|
+
sqlsaber/cli/interactive.py,sha256=u1gvdMZJkFWelZQ2urOS6-EH1uShKF4u_edfr_BzDNk,13479
|
|
19
|
+
sqlsaber/cli/memory.py,sha256=gKP-JJ0w1ya1YTM_Lk7Gw-7wL9ptyj6cZtg-uoW8K7A,7818
|
|
20
|
+
sqlsaber/cli/models.py,sha256=NozZbnisSjbPKo7PW7CltJMIkGcPqTDpDQEY-C_eLhk,8504
|
|
21
|
+
sqlsaber/cli/onboarding.py,sha256=l6FFWn8J1OVQUxr-xIAzKaFhAz8rFh6IEWwIyPWqR6U,11438
|
|
22
|
+
sqlsaber/cli/streaming.py,sha256=1XoZGPPMoTmBQVgp_Bqk483MR93j9oXxSV6Tx_-TpOg,6923
|
|
23
|
+
sqlsaber/cli/threads.py,sha256=5EV4ckRzKqhWeTKpTfQSNCBuqs3onsJURKT09g4E4XM,13634
|
|
24
24
|
sqlsaber/config/__init__.py,sha256=olwC45k8Nc61yK0WmPUk7XHdbsZH9HuUAbwnmKe3IgA,100
|
|
25
|
-
sqlsaber/config/api_keys.py,sha256=
|
|
25
|
+
sqlsaber/config/api_keys.py,sha256=bjogRmIuxNNGusyKXKi0ZpJWeS5Fyn53zrAD8hsoYx4,3671
|
|
26
26
|
sqlsaber/config/auth.py,sha256=b5qB2h1doXyO9Bn8z0CcL8LAR2jF431gGXBGKLgTmtQ,2756
|
|
27
27
|
sqlsaber/config/database.py,sha256=Yec6_0wdzq-ADblMNnbgvouYCimYOY_DWHT9oweaISc,11449
|
|
28
|
-
sqlsaber/config/oauth_flow.py,sha256=
|
|
29
|
-
sqlsaber/config/oauth_tokens.py,sha256=
|
|
28
|
+
sqlsaber/config/oauth_flow.py,sha256=VNbq4TZPk0hVJIcOh7JUO5qSxJnNzqDj0vwjCn-59Ok,10316
|
|
29
|
+
sqlsaber/config/oauth_tokens.py,sha256=SC-lXVcKCV7uiWtBiU2mxvx1z7ryW8tOSKHBApPsXtE,5931
|
|
30
30
|
sqlsaber/config/providers.py,sha256=JFjeJv1K5Q93zWSlWq3hAvgch1TlgoF0qFa0KJROkKY,2957
|
|
31
31
|
sqlsaber/config/settings.py,sha256=iB4CnGQ4hw8gxkaa9CVLB_JEy6Y9h9FQTAams5OCVyI,6421
|
|
32
32
|
sqlsaber/database/__init__.py,sha256=Gi9N_NOkD459WRWXDg3hSuGoBs3xWbMDRBvsTVmnGAg,2025
|
|
33
|
-
sqlsaber/database/base.py,sha256=
|
|
34
|
-
sqlsaber/database/csv.py,sha256=
|
|
35
|
-
sqlsaber/database/duckdb.py,sha256=
|
|
36
|
-
sqlsaber/database/mysql.py,sha256=
|
|
37
|
-
sqlsaber/database/postgresql.py,sha256=
|
|
33
|
+
sqlsaber/database/base.py,sha256=oaipLxlvoylX6oJCITPAWWqRqv09hRELqqEBufsmFic,3703
|
|
34
|
+
sqlsaber/database/csv.py,sha256=41wuP40FaGPfj28HMiD0I69uG0JbUxArpoTLC3MG2uc,4464
|
|
35
|
+
sqlsaber/database/duckdb.py,sha256=8HNKdx208aFK_YtwGjLz6LTne0xEmNevD-f9dRWlrFg,11244
|
|
36
|
+
sqlsaber/database/mysql.py,sha256=wMzDQqq4GFbfEdqXtv_sCb4Qbr9GSWqYAvOLeo5UryY,14472
|
|
37
|
+
sqlsaber/database/postgresql.py,sha256=fuf2Wl29NKXvD3mqsR08PDleNQ1PG-fNvWSxT6HDh2M,13223
|
|
38
38
|
sqlsaber/database/resolver.py,sha256=wSCcn__aCqwIfpt_LCjtW2Zgb8RpG5PlmwwZHli1q_U,3628
|
|
39
39
|
sqlsaber/database/schema.py,sha256=68PrNcA-5eR9PZB3i-TUQw5_E7QatwiDU2wv9GgXgM4,6928
|
|
40
|
-
sqlsaber/database/sqlite.py,sha256=
|
|
40
|
+
sqlsaber/database/sqlite.py,sha256=iReEIiSpkhhS1VzITd79ZWqSL3fHMyfe3DRCDpM0DvE,9421
|
|
41
41
|
sqlsaber/mcp/__init__.py,sha256=COdWq7wauPBp5Ew8tfZItFzbcLDSEkHBJSMhxzy8C9c,112
|
|
42
42
|
sqlsaber/mcp/mcp.py,sha256=tpNPHpkaCre1Xjp7c4DHXbTKeuYpDQ8qhmJZvAyr7Vk,3939
|
|
43
43
|
sqlsaber/memory/__init__.py,sha256=GiWkU6f6YYVV0EvvXDmFWe_CxarmDCql05t70MkTEWs,63
|
|
44
44
|
sqlsaber/memory/manager.py,sha256=p3fybMVfH-E4ApT1ZRZUnQIWSk9dkfUPCyfkmA0HALs,2739
|
|
45
45
|
sqlsaber/memory/storage.py,sha256=ne8szLlGj5NELheqLnI7zu21V8YS4rtpYGGC7tOmi-s,5745
|
|
46
|
+
sqlsaber/theme/__init__.py,sha256=qCICX1Cg4B6yCbZ1UrerxglWxcqldRFVSRrSs73na_8,188
|
|
47
|
+
sqlsaber/theme/manager.py,sha256=0DWuVXn7JoC8NvAl5FSqc61eagKFTx5YnoY8SoCTxGM,7236
|
|
46
48
|
sqlsaber/threads/__init__.py,sha256=Hh3dIG1tuC8fXprREUpslCIgPYz8_6o7aRLx4yNeO48,139
|
|
47
49
|
sqlsaber/threads/storage.py,sha256=rsUdxT4CR52D7xtGir9UlsFnBMk11jZeflzDrk2q4ME,11183
|
|
48
50
|
sqlsaber/tools/__init__.py,sha256=x3YdmX_7P0Qq_HtZHAgfIVKTLxYqKk6oc4tGsujQWsc,586
|
|
@@ -50,9 +52,10 @@ sqlsaber/tools/base.py,sha256=mHhvAj27BHmckyvuDLCPlAQdzABJyYxd9SJnaYAwwuA,1777
|
|
|
50
52
|
sqlsaber/tools/enums.py,sha256=CH32mL-0k9ZA18911xLpNtsgpV6tB85TktMj6uqGz54,411
|
|
51
53
|
sqlsaber/tools/instructions.py,sha256=X-x8maVkkyi16b6Tl0hcAFgjiYceZaSwyWTfmrvx8U8,9024
|
|
52
54
|
sqlsaber/tools/registry.py,sha256=HWOQMsNIdL4XZS6TeNUyrL-5KoSDH6PHsWd3X66o-18,3211
|
|
53
|
-
sqlsaber/tools/
|
|
54
|
-
sqlsaber
|
|
55
|
-
sqlsaber-0.
|
|
56
|
-
sqlsaber-0.
|
|
57
|
-
sqlsaber-0.
|
|
58
|
-
sqlsaber-0.
|
|
55
|
+
sqlsaber/tools/sql_guard.py,sha256=dTDwcZP-N4xPGzcr7MQtKUxKrlDzlc1irr9aH5a4wvk,6182
|
|
56
|
+
sqlsaber/tools/sql_tools.py,sha256=ujmAcfLkNaBrb5LWEgWcINQEQSX0LRPX3VK5Dag1Sj4,9178
|
|
57
|
+
sqlsaber-0.28.0.dist-info/METADATA,sha256=oY9Awl9jLkdeJLa1oeTPHPGH94KktJ_HmBVUVLQs-do,7174
|
|
58
|
+
sqlsaber-0.28.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
|
59
|
+
sqlsaber-0.28.0.dist-info/entry_points.txt,sha256=qEbOB7OffXPFgyJc7qEIJlMEX5RN9xdzLmWZa91zCQQ,162
|
|
60
|
+
sqlsaber-0.28.0.dist-info/licenses/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
|
|
61
|
+
sqlsaber-0.28.0.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|