sqlsaber 0.27.0__py3-none-any.whl → 0.29.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.

@@ -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
@@ -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
- # Security check - only allow SELECT queries unless write is enabled
211
- write_error = self._validate_write_operation(query)
212
- if write_error:
213
- return json.dumps({"error": write_error})
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
- query = self._add_limit_to_query(query, limit)
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.27.0
3
+ Version: 0.29.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
@@ -0,0 +1,62 @@
1
+ sqlsaber/__init__.py,sha256=HjS8ULtP4MGpnTL7njVY45NKV9Fi4e_yeYuY-hyXWQc,73
2
+ sqlsaber/__main__.py,sha256=RIHxWeWh2QvLfah-2OkhI5IJxojWfy4fXpMnVEJYvxw,78
3
+ sqlsaber/agents/__init__.py,sha256=qYI6rLY4q5AbF47vXH5RVoM08-yQjymBSaePh4lFIW4,116
4
+ sqlsaber/agents/base.py,sha256=40-MKEoz5rGrqVIylV1U2DaAUSPFcC75ohRin4E3-kk,2668
5
+ sqlsaber/agents/mcp.py,sha256=Pn8tdDRUEVLYQyEi5nHRp9MKNePwHVVoeNI-uqWcr0Y,757
6
+ sqlsaber/agents/pydantic_ai_agent.py,sha256=wBxKz0pjOkL-HI-TXV6B67bczZNgu7k26Rr3w5usR3o,10064
7
+ sqlsaber/application/__init__.py,sha256=KY_-d5nEdQyAwNOsK5r-f7Tb69c63XbuEkHPeLpJal8,84
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
+ sqlsaber/application/prompts.py,sha256=4rMGcWpYJbNWPMzqVWseUMx0nwvXOkWS6GaTAJ5mhfc,3473
12
+ sqlsaber/cli/__init__.py,sha256=qVSLVJLLJYzoC6aj6y9MFrzZvAwc4_OgxU9DlkQnZ4M,86
13
+ sqlsaber/cli/auth.py,sha256=ysDBXEFR8Jz7wYbIP6X7yWA2ivd8SDnUp_jUg_qYNWk,6088
14
+ sqlsaber/cli/commands.py,sha256=LiHLMAZF9fNgQXShtF67dzDvp-w4LlSbmeZIa47ntC0,8634
15
+ sqlsaber/cli/completers.py,sha256=g-hLDq5fiBx7gg8Bte1Lq8GU-ZxCYVs4dcPsmHPIcK4,6574
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/theme.py,sha256=hP0kmsMLCtqaT7b5wB1dk1hW1hV94oP4BHdz8S6887A,4243
24
+ sqlsaber/cli/threads.py,sha256=5EV4ckRzKqhWeTKpTfQSNCBuqs3onsJURKT09g4E4XM,13634
25
+ sqlsaber/config/__init__.py,sha256=olwC45k8Nc61yK0WmPUk7XHdbsZH9HuUAbwnmKe3IgA,100
26
+ sqlsaber/config/api_keys.py,sha256=bjogRmIuxNNGusyKXKi0ZpJWeS5Fyn53zrAD8hsoYx4,3671
27
+ sqlsaber/config/auth.py,sha256=b5qB2h1doXyO9Bn8z0CcL8LAR2jF431gGXBGKLgTmtQ,2756
28
+ sqlsaber/config/database.py,sha256=Yec6_0wdzq-ADblMNnbgvouYCimYOY_DWHT9oweaISc,11449
29
+ sqlsaber/config/oauth_flow.py,sha256=VNbq4TZPk0hVJIcOh7JUO5qSxJnNzqDj0vwjCn-59Ok,10316
30
+ sqlsaber/config/oauth_tokens.py,sha256=SC-lXVcKCV7uiWtBiU2mxvx1z7ryW8tOSKHBApPsXtE,5931
31
+ sqlsaber/config/providers.py,sha256=JFjeJv1K5Q93zWSlWq3hAvgch1TlgoF0qFa0KJROkKY,2957
32
+ sqlsaber/config/settings.py,sha256=iB4CnGQ4hw8gxkaa9CVLB_JEy6Y9h9FQTAams5OCVyI,6421
33
+ sqlsaber/database/__init__.py,sha256=Gi9N_NOkD459WRWXDg3hSuGoBs3xWbMDRBvsTVmnGAg,2025
34
+ sqlsaber/database/base.py,sha256=oaipLxlvoylX6oJCITPAWWqRqv09hRELqqEBufsmFic,3703
35
+ sqlsaber/database/csv.py,sha256=41wuP40FaGPfj28HMiD0I69uG0JbUxArpoTLC3MG2uc,4464
36
+ sqlsaber/database/duckdb.py,sha256=8HNKdx208aFK_YtwGjLz6LTne0xEmNevD-f9dRWlrFg,11244
37
+ sqlsaber/database/mysql.py,sha256=wMzDQqq4GFbfEdqXtv_sCb4Qbr9GSWqYAvOLeo5UryY,14472
38
+ sqlsaber/database/postgresql.py,sha256=fuf2Wl29NKXvD3mqsR08PDleNQ1PG-fNvWSxT6HDh2M,13223
39
+ sqlsaber/database/resolver.py,sha256=wSCcn__aCqwIfpt_LCjtW2Zgb8RpG5PlmwwZHli1q_U,3628
40
+ sqlsaber/database/schema.py,sha256=68PrNcA-5eR9PZB3i-TUQw5_E7QatwiDU2wv9GgXgM4,6928
41
+ sqlsaber/database/sqlite.py,sha256=iReEIiSpkhhS1VzITd79ZWqSL3fHMyfe3DRCDpM0DvE,9421
42
+ sqlsaber/mcp/__init__.py,sha256=COdWq7wauPBp5Ew8tfZItFzbcLDSEkHBJSMhxzy8C9c,112
43
+ sqlsaber/mcp/mcp.py,sha256=tpNPHpkaCre1Xjp7c4DHXbTKeuYpDQ8qhmJZvAyr7Vk,3939
44
+ sqlsaber/memory/__init__.py,sha256=GiWkU6f6YYVV0EvvXDmFWe_CxarmDCql05t70MkTEWs,63
45
+ sqlsaber/memory/manager.py,sha256=p3fybMVfH-E4ApT1ZRZUnQIWSk9dkfUPCyfkmA0HALs,2739
46
+ sqlsaber/memory/storage.py,sha256=ne8szLlGj5NELheqLnI7zu21V8YS4rtpYGGC7tOmi-s,5745
47
+ sqlsaber/theme/__init__.py,sha256=qCICX1Cg4B6yCbZ1UrerxglWxcqldRFVSRrSs73na_8,188
48
+ sqlsaber/theme/manager.py,sha256=7QXoqPvl-aKfvqTRduWYwnHsySu66Gg8a3-QQkVM5Ss,6551
49
+ sqlsaber/threads/__init__.py,sha256=Hh3dIG1tuC8fXprREUpslCIgPYz8_6o7aRLx4yNeO48,139
50
+ sqlsaber/threads/storage.py,sha256=rsUdxT4CR52D7xtGir9UlsFnBMk11jZeflzDrk2q4ME,11183
51
+ sqlsaber/tools/__init__.py,sha256=x3YdmX_7P0Qq_HtZHAgfIVKTLxYqKk6oc4tGsujQWsc,586
52
+ sqlsaber/tools/base.py,sha256=mHhvAj27BHmckyvuDLCPlAQdzABJyYxd9SJnaYAwwuA,1777
53
+ sqlsaber/tools/enums.py,sha256=CH32mL-0k9ZA18911xLpNtsgpV6tB85TktMj6uqGz54,411
54
+ sqlsaber/tools/instructions.py,sha256=X-x8maVkkyi16b6Tl0hcAFgjiYceZaSwyWTfmrvx8U8,9024
55
+ sqlsaber/tools/registry.py,sha256=HWOQMsNIdL4XZS6TeNUyrL-5KoSDH6PHsWd3X66o-18,3211
56
+ sqlsaber/tools/sql_guard.py,sha256=dTDwcZP-N4xPGzcr7MQtKUxKrlDzlc1irr9aH5a4wvk,6182
57
+ sqlsaber/tools/sql_tools.py,sha256=ujmAcfLkNaBrb5LWEgWcINQEQSX0LRPX3VK5Dag1Sj4,9178
58
+ sqlsaber-0.29.0.dist-info/METADATA,sha256=5Ocm_GD3MIdPLji_VpknTjiQ0ndDd2waD14dr6_IOjg,7174
59
+ sqlsaber-0.29.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
60
+ sqlsaber-0.29.0.dist-info/entry_points.txt,sha256=qEbOB7OffXPFgyJc7qEIJlMEX5RN9xdzLmWZa91zCQQ,162
61
+ sqlsaber-0.29.0.dist-info/licenses/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
62
+ sqlsaber-0.29.0.dist-info/RECORD,,
@@ -1,58 +0,0 @@
1
- sqlsaber/__init__.py,sha256=HjS8ULtP4MGpnTL7njVY45NKV9Fi4e_yeYuY-hyXWQc,73
2
- sqlsaber/__main__.py,sha256=RIHxWeWh2QvLfah-2OkhI5IJxojWfy4fXpMnVEJYvxw,78
3
- sqlsaber/agents/__init__.py,sha256=qYI6rLY4q5AbF47vXH5RVoM08-yQjymBSaePh4lFIW4,116
4
- sqlsaber/agents/base.py,sha256=40-MKEoz5rGrqVIylV1U2DaAUSPFcC75ohRin4E3-kk,2668
5
- sqlsaber/agents/mcp.py,sha256=Pn8tdDRUEVLYQyEi5nHRp9MKNePwHVVoeNI-uqWcr0Y,757
6
- sqlsaber/agents/pydantic_ai_agent.py,sha256=wBxKz0pjOkL-HI-TXV6B67bczZNgu7k26Rr3w5usR3o,10064
7
- sqlsaber/application/__init__.py,sha256=KY_-d5nEdQyAwNOsK5r-f7Tb69c63XbuEkHPeLpJal8,84
8
- sqlsaber/application/auth_setup.py,sha256=aIZ4vyfdjO5zCcMy4k4FmqjeGgruypPIRacaOJDuVRY,5080
9
- sqlsaber/application/db_setup.py,sha256=VipoXACDVs8auYs7XeuNYrUWjVHpxqxWphx9yUl1_2I,6826
10
- sqlsaber/application/model_selection.py,sha256=eRWga3vRpoyyTmw0BsVDRvUtyUnxvP8plAQ2Nv7tN3o,3163
11
- sqlsaber/application/prompts.py,sha256=4rMGcWpYJbNWPMzqVWseUMx0nwvXOkWS6GaTAJ5mhfc,3473
12
- sqlsaber/cli/__init__.py,sha256=qVSLVJLLJYzoC6aj6y9MFrzZvAwc4_OgxU9DlkQnZ4M,86
13
- sqlsaber/cli/auth.py,sha256=Hz7nuHdX1iMr2UR1VVtu4EAvBYYIhsgthluqpiOXH2A,6064
14
- sqlsaber/cli/commands.py,sha256=R63HF4P0JZwbGlI3TimkEUg3OUaFXYkVvLw1VIy0UFI,8516
15
- sqlsaber/cli/completers.py,sha256=g-hLDq5fiBx7gg8Bte1Lq8GU-ZxCYVs4dcPsmHPIcK4,6574
16
- sqlsaber/cli/database.py,sha256=6SdxNuP8w-e2nbV937q4hxfAIwKu1MqoirogD7USaMU,10639
17
- sqlsaber/cli/display.py,sha256=JQBSdLC_a3nkmJ4QfLMhhMPCYY-otS2eXF1XrfEe19E,17045
18
- sqlsaber/cli/interactive.py,sha256=RG4DJN9lprfKi60eomxVt-w3gTsWoqbfEcfO36iibXM,13694
19
- sqlsaber/cli/memory.py,sha256=OufHFJFwV0_GGn7LvKRTJikkWhV1IwNIUDOxFPHXOaQ,7794
20
- sqlsaber/cli/models.py,sha256=v_wrJWV5NXiM3zGLSMyNJf4YhgozK6hnf76Ua8HvysE,8480
21
- sqlsaber/cli/onboarding.py,sha256=5p-rHG2eGM-7t9yg_kNDrXpY13jr__dlFII3K69VtKc,11414
22
- sqlsaber/cli/streaming.py,sha256=YViLCxUv-7WN5TCphLYtAR02HXvuHYuPttGGDZKDUKU,6921
23
- sqlsaber/cli/threads.py,sha256=zVlbOuD3GjjEVNebXwANKeKt4I_Lunf6itiBUL0TaKA,12877
24
- sqlsaber/config/__init__.py,sha256=olwC45k8Nc61yK0WmPUk7XHdbsZH9HuUAbwnmKe3IgA,100
25
- sqlsaber/config/api_keys.py,sha256=7X63B-ow66GZWzYYgc_7f1T26F7gRhfyYQM4RpdkCpI,3647
26
- sqlsaber/config/auth.py,sha256=b5qB2h1doXyO9Bn8z0CcL8LAR2jF431gGXBGKLgTmtQ,2756
27
- sqlsaber/config/database.py,sha256=Yec6_0wdzq-ADblMNnbgvouYCimYOY_DWHT9oweaISc,11449
28
- sqlsaber/config/oauth_flow.py,sha256=A3bSXaBLzuAfXV2ZPA94m9NV33c2MyL6M4ii9oEkswQ,10291
29
- sqlsaber/config/oauth_tokens.py,sha256=C9z35hyx-PvSAYdC1LNf3rg9_wsEIY56hkEczelbad0,6015
30
- sqlsaber/config/providers.py,sha256=JFjeJv1K5Q93zWSlWq3hAvgch1TlgoF0qFa0KJROkKY,2957
31
- sqlsaber/config/settings.py,sha256=iB4CnGQ4hw8gxkaa9CVLB_JEy6Y9h9FQTAams5OCVyI,6421
32
- sqlsaber/database/__init__.py,sha256=Gi9N_NOkD459WRWXDg3hSuGoBs3xWbMDRBvsTVmnGAg,2025
33
- sqlsaber/database/base.py,sha256=yxYcfeNhRPbO5jFRVZH7eRUGj_up-y3p1ZX_obZXi0w,3552
34
- sqlsaber/database/csv.py,sha256=45eH9mAkBtwSu1Rc_vvG1Z40L4xvfHWSb8OMG15TbCA,4340
35
- sqlsaber/database/duckdb.py,sha256=v6gFUhih5NMbHHpUv7By2nXyl9aqdPtLt0zhqS4-OKE,11120
36
- sqlsaber/database/mysql.py,sha256=5qd9gnSCP3umtBJcQDTzzJfMzwqYCJhWlbOeJZ9_-6c,14349
37
- sqlsaber/database/postgresql.py,sha256=R8I3Y-w0P9qPe47-lmae0X17syIwI8saxEG3etx6Rqc,13097
38
- sqlsaber/database/resolver.py,sha256=wSCcn__aCqwIfpt_LCjtW2Zgb8RpG5PlmwwZHli1q_U,3628
39
- sqlsaber/database/schema.py,sha256=68PrNcA-5eR9PZB3i-TUQw5_E7QatwiDU2wv9GgXgM4,6928
40
- sqlsaber/database/sqlite.py,sha256=zdNj5i4mLJK21sWgftAHDHVihRUWevn__tVF9_nnLfQ,9297
41
- sqlsaber/mcp/__init__.py,sha256=COdWq7wauPBp5Ew8tfZItFzbcLDSEkHBJSMhxzy8C9c,112
42
- sqlsaber/mcp/mcp.py,sha256=tpNPHpkaCre1Xjp7c4DHXbTKeuYpDQ8qhmJZvAyr7Vk,3939
43
- sqlsaber/memory/__init__.py,sha256=GiWkU6f6YYVV0EvvXDmFWe_CxarmDCql05t70MkTEWs,63
44
- sqlsaber/memory/manager.py,sha256=p3fybMVfH-E4ApT1ZRZUnQIWSk9dkfUPCyfkmA0HALs,2739
45
- sqlsaber/memory/storage.py,sha256=ne8szLlGj5NELheqLnI7zu21V8YS4rtpYGGC7tOmi-s,5745
46
- sqlsaber/threads/__init__.py,sha256=Hh3dIG1tuC8fXprREUpslCIgPYz8_6o7aRLx4yNeO48,139
47
- sqlsaber/threads/storage.py,sha256=rsUdxT4CR52D7xtGir9UlsFnBMk11jZeflzDrk2q4ME,11183
48
- sqlsaber/tools/__init__.py,sha256=x3YdmX_7P0Qq_HtZHAgfIVKTLxYqKk6oc4tGsujQWsc,586
49
- sqlsaber/tools/base.py,sha256=mHhvAj27BHmckyvuDLCPlAQdzABJyYxd9SJnaYAwwuA,1777
50
- sqlsaber/tools/enums.py,sha256=CH32mL-0k9ZA18911xLpNtsgpV6tB85TktMj6uqGz54,411
51
- sqlsaber/tools/instructions.py,sha256=X-x8maVkkyi16b6Tl0hcAFgjiYceZaSwyWTfmrvx8U8,9024
52
- sqlsaber/tools/registry.py,sha256=HWOQMsNIdL4XZS6TeNUyrL-5KoSDH6PHsWd3X66o-18,3211
53
- sqlsaber/tools/sql_tools.py,sha256=2xLD_pkd0t8wKndQAKIr4c9UpWzVWeHbAFpkwo5j4kY,9954
54
- sqlsaber-0.27.0.dist-info/METADATA,sha256=-QcrEttuC3vwUmNdtjHzA_laIfTkcMGoghmSCvNn3nM,7138
55
- sqlsaber-0.27.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
56
- sqlsaber-0.27.0.dist-info/entry_points.txt,sha256=qEbOB7OffXPFgyJc7qEIJlMEX5RN9xdzLmWZa91zCQQ,162
57
- sqlsaber-0.27.0.dist-info/licenses/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
58
- sqlsaber-0.27.0.dist-info/RECORD,,