mcp-sqlite-memory-bank 0.1.1__py3-none-any.whl → 1.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.
- mcp_sqlite_memory_bank/__init__.py +19 -16
- mcp_sqlite_memory_bank/server.py +296 -197
- mcp_sqlite_memory_bank/types.py +6 -2
- mcp_sqlite_memory_bank/utils.py +21 -11
- {mcp_sqlite_memory_bank-0.1.1.dist-info → mcp_sqlite_memory_bank-1.1.0.dist-info}/METADATA +26 -2
- mcp_sqlite_memory_bank-1.1.0.dist-info/RECORD +10 -0
- mcp_sqlite_memory_bank-0.1.1.dist-info/RECORD +0 -10
- {mcp_sqlite_memory_bank-0.1.1.dist-info → mcp_sqlite_memory_bank-1.1.0.dist-info}/WHEEL +0 -0
- {mcp_sqlite_memory_bank-0.1.1.dist-info → mcp_sqlite_memory_bank-1.1.0.dist-info}/licenses/LICENSE +0 -0
- {mcp_sqlite_memory_bank-0.1.1.dist-info → mcp_sqlite_memory_bank-1.1.0.dist-info}/top_level.txt +0 -0
mcp_sqlite_memory_bank/server.py
CHANGED
@@ -44,12 +44,10 @@ import os
|
|
44
44
|
import re
|
45
45
|
import sqlite3
|
46
46
|
import logging
|
47
|
-
from typing import Dict, Optional, List,
|
47
|
+
from typing import Dict, Optional, List, cast, Any
|
48
48
|
from fastmcp import FastMCP
|
49
|
-
from mcp.types import TextContent
|
50
49
|
|
51
50
|
from .types import (
|
52
|
-
TableColumn,
|
53
51
|
ToolResponse,
|
54
52
|
CreateTableResponse,
|
55
53
|
DropTableResponse,
|
@@ -65,21 +63,20 @@ from .types import (
|
|
65
63
|
ErrorResponse,
|
66
64
|
ValidationError,
|
67
65
|
DatabaseError,
|
68
|
-
SchemaError
|
69
|
-
DataError
|
66
|
+
SchemaError
|
70
67
|
)
|
71
68
|
from .utils import (
|
72
69
|
catch_errors,
|
73
70
|
validate_identifier,
|
74
71
|
validate_column_definition,
|
75
72
|
get_table_columns,
|
76
|
-
get_table_columns_by_name,
|
77
73
|
validate_table_exists,
|
78
74
|
build_where_clause
|
79
75
|
)
|
80
76
|
|
77
|
+
|
81
78
|
# Initialize FastMCP app with explicit name
|
82
|
-
mcp = FastMCP("SQLite Memory Bank for Copilot/AI Agents")
|
79
|
+
mcp: FastMCP = FastMCP("SQLite Memory Bank for Copilot/AI Agents")
|
83
80
|
|
84
81
|
# Configure database path from environment or default
|
85
82
|
DB_PATH = os.environ.get("DB_PATH", "./test.db")
|
@@ -92,18 +89,19 @@ os.makedirs(os.path.dirname(os.path.abspath(DB_PATH)), exist_ok=True)
|
|
92
89
|
|
93
90
|
@mcp.tool
|
94
91
|
@catch_errors
|
95
|
-
def create_table(
|
92
|
+
def create_table(
|
93
|
+
table_name: str, columns: List[Dict[str, str]]) -> ToolResponse:
|
96
94
|
"""
|
97
95
|
Create a new table in the SQLite memory bank.
|
98
|
-
|
96
|
+
|
99
97
|
Args:
|
100
98
|
table_name (str): Name of the table to create. Must be a valid SQLite identifier.
|
101
99
|
columns (List[Dict[str, str]]): List of columns, each as {"name": str, "type": str}.
|
102
|
-
|
100
|
+
|
103
101
|
Returns:
|
104
102
|
ToolResponse: On success: {"success": True}
|
105
103
|
On error: {"success": False, "error": str, "category": str, "details": dict}
|
106
|
-
|
104
|
+
|
107
105
|
Examples:
|
108
106
|
>>> create_table("users", [
|
109
107
|
... {"name": "id", "type": "INTEGER PRIMARY KEY AUTOINCREMENT"},
|
@@ -111,7 +109,7 @@ def create_table(table_name: str, columns: List[Dict[str, str]]) -> ToolResponse
|
|
111
109
|
... {"name": "age", "type": "INTEGER"}
|
112
110
|
... ])
|
113
111
|
{"success": True}
|
114
|
-
|
112
|
+
|
115
113
|
FastMCP Tool Info:
|
116
114
|
- Validates table name and column definitions
|
117
115
|
- Creates table if it doesn't exist (idempotent)
|
@@ -119,22 +117,22 @@ def create_table(table_name: str, columns: List[Dict[str, str]]) -> ToolResponse
|
|
119
117
|
"""
|
120
118
|
# Validate table name
|
121
119
|
validate_identifier(table_name, "table name")
|
122
|
-
|
120
|
+
|
123
121
|
# Validate columns
|
124
122
|
if not columns:
|
125
123
|
raise ValidationError(
|
126
124
|
"Must provide at least one column",
|
127
125
|
{"columns": columns}
|
128
126
|
)
|
129
|
-
|
127
|
+
|
130
128
|
# Validate each column
|
131
129
|
for col in columns:
|
132
130
|
validate_column_definition(col)
|
133
|
-
|
131
|
+
|
134
132
|
# Build and execute CREATE TABLE
|
135
133
|
col_defs = ', '.join([f"{col['name']} {col['type']}" for col in columns])
|
136
134
|
query = f"CREATE TABLE IF NOT EXISTS {table_name} ({col_defs})"
|
137
|
-
|
135
|
+
|
138
136
|
try:
|
139
137
|
with sqlite3.connect(DB_PATH) as conn:
|
140
138
|
conn.execute(query)
|
@@ -145,20 +143,21 @@ def create_table(table_name: str, columns: List[Dict[str, str]]) -> ToolResponse
|
|
145
143
|
{"sqlite_error": str(e)}
|
146
144
|
)
|
147
145
|
|
146
|
+
|
148
147
|
@mcp.tool
|
149
148
|
@catch_errors
|
150
149
|
def list_tables() -> ToolResponse:
|
151
150
|
"""
|
152
151
|
List all tables in the SQLite memory bank.
|
153
|
-
|
152
|
+
|
154
153
|
Returns:
|
155
154
|
ToolResponse: On success: {"success": True, "tables": List[str]}
|
156
155
|
On error: {"success": False, "error": str, "category": str, "details": dict}
|
157
|
-
|
156
|
+
|
158
157
|
Examples:
|
159
158
|
>>> list_tables()
|
160
159
|
{"success": True, "tables": ["users", "notes", "tasks"]}
|
161
|
-
|
160
|
+
|
162
161
|
FastMCP Tool Info:
|
163
162
|
- Returns list of all user-created tables
|
164
163
|
- Excludes SQLite system tables
|
@@ -171,27 +170,30 @@ def list_tables() -> ToolResponse:
|
|
171
170
|
"SELECT name FROM sqlite_master WHERE type='table' AND name NOT LIKE 'sqlite_%'"
|
172
171
|
)
|
173
172
|
tables = [row[0] for row in cur.fetchall()]
|
174
|
-
return cast(
|
175
|
-
|
173
|
+
return cast(
|
174
|
+
ListTablesResponse, {
|
175
|
+
"success": True, "tables": tables})
|
176
|
+
|
176
177
|
except sqlite3.Error as e:
|
177
178
|
raise DatabaseError(
|
178
179
|
"Failed to list tables",
|
179
180
|
{"sqlite_error": str(e)}
|
180
181
|
)
|
181
182
|
|
183
|
+
|
182
184
|
@mcp.tool
|
183
185
|
@catch_errors
|
184
186
|
def describe_table(table_name: str) -> ToolResponse:
|
185
187
|
"""
|
186
188
|
Get detailed schema information for a table.
|
187
|
-
|
189
|
+
|
188
190
|
Args:
|
189
191
|
table_name (str): Name of the table to describe.
|
190
|
-
|
192
|
+
|
191
193
|
Returns:
|
192
194
|
ToolResponse: On success: {"success": True, "columns": List[TableColumn]}
|
193
195
|
On error: {"success": False, "error": str, "category": str, "details": dict}
|
194
|
-
|
196
|
+
|
195
197
|
Where TableColumn is:
|
196
198
|
{
|
197
199
|
"name": str,
|
@@ -200,7 +202,7 @@ def describe_table(table_name: str) -> ToolResponse:
|
|
200
202
|
"default": Any,
|
201
203
|
"pk": bool
|
202
204
|
}
|
203
|
-
|
205
|
+
|
204
206
|
Examples:
|
205
207
|
>>> describe_table("users")
|
206
208
|
{
|
@@ -210,7 +212,7 @@ def describe_table(table_name: str) -> ToolResponse:
|
|
210
212
|
{"name": "name", "type": "TEXT", "notnull": 1, "default": null, "pk": 0}
|
211
213
|
]
|
212
214
|
}
|
213
|
-
|
215
|
+
|
214
216
|
FastMCP Tool Info:
|
215
217
|
- Returns detailed column information
|
216
218
|
- Validates table existence
|
@@ -218,12 +220,12 @@ def describe_table(table_name: str) -> ToolResponse:
|
|
218
220
|
"""
|
219
221
|
# Validate table name
|
220
222
|
validate_identifier(table_name, "table name")
|
221
|
-
|
223
|
+
|
222
224
|
try:
|
223
225
|
with sqlite3.connect(DB_PATH) as conn:
|
224
226
|
# Validate table exists
|
225
227
|
validate_table_exists(conn, table_name)
|
226
|
-
|
228
|
+
|
227
229
|
# Get column info
|
228
230
|
cur = conn.cursor()
|
229
231
|
cur.execute(f"PRAGMA table_info({table_name})")
|
@@ -237,31 +239,34 @@ def describe_table(table_name: str) -> ToolResponse:
|
|
237
239
|
}
|
238
240
|
for row in cur.fetchall()
|
239
241
|
]
|
240
|
-
|
241
|
-
return cast(
|
242
|
-
|
242
|
+
|
243
|
+
return cast(
|
244
|
+
DescribeTableResponse, {
|
245
|
+
"success": True, "columns": columns})
|
246
|
+
|
243
247
|
except sqlite3.Error as e:
|
244
248
|
raise DatabaseError(
|
245
249
|
f"Failed to describe table {table_name}",
|
246
250
|
{"sqlite_error": str(e)}
|
247
251
|
)
|
248
252
|
|
253
|
+
|
249
254
|
@mcp.tool
|
250
255
|
def drop_table(table_name: str) -> ToolResponse:
|
251
256
|
"""
|
252
257
|
Drop (delete) a table from the SQLite memory bank.
|
253
|
-
|
258
|
+
|
254
259
|
Args:
|
255
260
|
table_name (str): Name of the table to drop. Must be a valid SQLite identifier.
|
256
|
-
|
261
|
+
|
257
262
|
Returns:
|
258
263
|
ToolResponse: On success: {"success": True}
|
259
264
|
On error: {"success": False, "error": str, "category": str, "details": dict}
|
260
|
-
|
265
|
+
|
261
266
|
Examples:
|
262
267
|
>>> drop_table('notes')
|
263
268
|
{"success": True}
|
264
|
-
|
269
|
+
|
265
270
|
FastMCP Tool Info:
|
266
271
|
- Validates table name
|
267
272
|
- Confirms table exists before dropping
|
@@ -269,7 +274,7 @@ def drop_table(table_name: str) -> ToolResponse:
|
|
269
274
|
"""
|
270
275
|
# Validate table name
|
271
276
|
validate_identifier(table_name, "table name")
|
272
|
-
|
277
|
+
|
273
278
|
try:
|
274
279
|
with sqlite3.connect(DB_PATH) as conn:
|
275
280
|
# Validate table exists
|
@@ -285,37 +290,38 @@ def drop_table(table_name: str) -> ToolResponse:
|
|
285
290
|
"category": "schema_error",
|
286
291
|
"details": {"table_name": table_name}
|
287
292
|
})
|
288
|
-
|
293
|
+
|
289
294
|
# Execute DROP TABLE
|
290
295
|
conn.execute(f"DROP TABLE {table_name}")
|
291
296
|
conn.commit()
|
292
|
-
|
297
|
+
|
293
298
|
return cast(DropTableResponse, {"success": True})
|
294
|
-
|
299
|
+
|
295
300
|
except sqlite3.Error as e:
|
296
301
|
raise DatabaseError(
|
297
302
|
f"Failed to drop table {table_name}",
|
298
303
|
{"sqlite_error": str(e)}
|
299
304
|
)
|
300
305
|
|
306
|
+
|
301
307
|
@mcp.tool
|
302
308
|
@catch_errors
|
303
309
|
def rename_table(old_name: str, new_name: str) -> ToolResponse:
|
304
310
|
"""
|
305
311
|
Rename a table in the SQLite memory bank.
|
306
|
-
|
312
|
+
|
307
313
|
Args:
|
308
314
|
old_name (str): Current table name. Must be a valid SQLite identifier.
|
309
315
|
new_name (str): New table name. Must be a valid SQLite identifier.
|
310
|
-
|
316
|
+
|
311
317
|
Returns:
|
312
318
|
ToolResponse: On success: {"success": True}
|
313
319
|
On error: {"success": False, "error": str, "category": str, "details": dict}
|
314
|
-
|
320
|
+
|
315
321
|
Examples:
|
316
322
|
>>> rename_table('notes', 'archive_notes')
|
317
323
|
{"success": True}
|
318
|
-
|
324
|
+
|
319
325
|
FastMCP Tool Info:
|
320
326
|
- Validates both old and new table names
|
321
327
|
- Confirms old table exists and new name doesn't conflict
|
@@ -323,19 +329,19 @@ def rename_table(old_name: str, new_name: str) -> ToolResponse:
|
|
323
329
|
# Validate table names
|
324
330
|
validate_identifier(old_name, "old table name")
|
325
331
|
validate_identifier(new_name, "new table name")
|
326
|
-
|
332
|
+
|
327
333
|
# Check if names are the same
|
328
334
|
if old_name == new_name:
|
329
335
|
raise ValidationError(
|
330
336
|
"Old and new table names are identical",
|
331
337
|
{"old_name": old_name, "new_name": new_name}
|
332
338
|
)
|
333
|
-
|
339
|
+
|
334
340
|
try:
|
335
341
|
with sqlite3.connect(DB_PATH) as conn:
|
336
342
|
# Validate old table exists
|
337
343
|
validate_table_exists(conn, old_name)
|
338
|
-
|
344
|
+
|
339
345
|
# Check if new table already exists
|
340
346
|
cur = conn.cursor()
|
341
347
|
cur.execute(
|
@@ -347,37 +353,38 @@ def rename_table(old_name: str, new_name: str) -> ToolResponse:
|
|
347
353
|
f"Cannot rename: table {new_name} already exists",
|
348
354
|
{"new_name": new_name}
|
349
355
|
)
|
350
|
-
|
356
|
+
|
351
357
|
# Execute ALTER TABLE
|
352
358
|
conn.execute(f"ALTER TABLE {old_name} RENAME TO {new_name}")
|
353
359
|
conn.commit()
|
354
|
-
|
360
|
+
|
355
361
|
return cast(RenameTableResponse, {"success": True})
|
356
|
-
|
362
|
+
|
357
363
|
except sqlite3.Error as e:
|
358
364
|
raise DatabaseError(
|
359
365
|
f"Failed to rename table from {old_name} to {new_name}",
|
360
366
|
{"sqlite_error": str(e)}
|
361
367
|
)
|
362
368
|
|
369
|
+
|
363
370
|
@mcp.tool
|
364
371
|
@catch_errors
|
365
372
|
def create_row(table_name: str, data: Dict[str, Any]) -> ToolResponse:
|
366
373
|
"""
|
367
374
|
Insert a new row into any table in the SQLite Memory Bank for Copilot/AI agents.
|
368
|
-
|
375
|
+
|
369
376
|
Args:
|
370
377
|
table_name (str): Table name.
|
371
378
|
data (Dict[str, Any]): Data to insert (column-value pairs matching the table schema).
|
372
|
-
|
379
|
+
|
373
380
|
Returns:
|
374
381
|
ToolResponse: On success: {"success": True, "id": rowid}
|
375
382
|
On error: {"success": False, "error": str, "category": str, "details": dict}
|
376
|
-
|
383
|
+
|
377
384
|
Examples:
|
378
385
|
>>> create_row('notes', {'content': 'Remember to hydrate!'})
|
379
386
|
{"success": True, "id": 1}
|
380
|
-
|
387
|
+
|
381
388
|
FastMCP Tool Info:
|
382
389
|
- Validates table name and column names
|
383
390
|
- Auto-converts data types where possible
|
@@ -385,19 +392,19 @@ def create_row(table_name: str, data: Dict[str, Any]) -> ToolResponse:
|
|
385
392
|
"""
|
386
393
|
# Validate table name
|
387
394
|
validate_identifier(table_name, "table name")
|
388
|
-
|
395
|
+
|
389
396
|
# Validate data
|
390
397
|
if not data:
|
391
398
|
raise ValidationError(
|
392
399
|
"Data cannot be empty",
|
393
400
|
{"data": data}
|
394
401
|
)
|
395
|
-
|
402
|
+
|
396
403
|
try:
|
397
404
|
with sqlite3.connect(DB_PATH) as conn:
|
398
405
|
# Get and validate columns
|
399
406
|
valid_columns = get_table_columns(conn, table_name)
|
400
|
-
|
407
|
+
|
401
408
|
# Validate column names
|
402
409
|
for k in data.keys():
|
403
410
|
if k not in valid_columns:
|
@@ -405,99 +412,109 @@ def create_row(table_name: str, data: Dict[str, Any]) -> ToolResponse:
|
|
405
412
|
f"Invalid column in data: {k}",
|
406
413
|
{"invalid_column": k, "valid_columns": valid_columns}
|
407
414
|
)
|
408
|
-
|
415
|
+
|
409
416
|
# Build and execute INSERT
|
410
417
|
keys = ', '.join(data.keys())
|
411
418
|
placeholders = ', '.join(['?'] * len(data))
|
412
419
|
values = list(data.values())
|
413
420
|
query = f"INSERT INTO {table_name} ({keys}) VALUES ({placeholders})"
|
414
|
-
|
421
|
+
|
415
422
|
cur = conn.cursor()
|
416
423
|
cur.execute(query, values)
|
417
424
|
conn.commit()
|
418
|
-
|
419
|
-
return cast(
|
420
|
-
|
425
|
+
|
426
|
+
return cast(
|
427
|
+
CreateRowResponse, {
|
428
|
+
"success": True, "id": cur.lastrowid})
|
429
|
+
|
421
430
|
except sqlite3.Error as e:
|
422
431
|
raise DatabaseError(
|
423
432
|
f"Failed to insert into table {table_name}",
|
424
433
|
{"sqlite_error": str(e)}
|
425
434
|
)
|
426
435
|
|
436
|
+
|
427
437
|
@mcp.tool
|
428
438
|
@catch_errors
|
429
|
-
def read_rows(table_name: str,
|
439
|
+
def read_rows(table_name: str,
|
440
|
+
where: Optional[Dict[str,
|
441
|
+
Any]] = None) -> ToolResponse:
|
430
442
|
"""
|
431
443
|
Read rows from any table in the SQLite memory bank, with optional filtering.
|
432
|
-
|
444
|
+
|
433
445
|
Args:
|
434
446
|
table_name (str): Name of the table to read from.
|
435
447
|
where (Optional[Dict[str, Any]]): Optional filter conditions as {"column": value} pairs.
|
436
|
-
|
448
|
+
|
437
449
|
Returns:
|
438
450
|
ToolResponse: On success: {"success": True, "rows": List[Dict[str, Any]]}
|
439
451
|
On error: {"success": False, "error": str, "category": str, "details": dict}
|
440
|
-
|
452
|
+
|
441
453
|
Examples:
|
442
454
|
>>> read_rows("users", {"age": 25})
|
443
455
|
{"success": True, "rows": [{"id": 1, "name": "Alice", "age": 25}, ...]}
|
444
|
-
|
456
|
+
|
445
457
|
FastMCP Tool Info:
|
446
458
|
- Validates table name and filter conditions
|
447
459
|
- Returns rows as list of dictionaries
|
448
460
|
- Parameterizes all queries for safety
|
449
461
|
"""
|
450
462
|
where = where or {}
|
451
|
-
|
463
|
+
|
452
464
|
# Validate table name
|
453
465
|
validate_identifier(table_name, "table name")
|
454
|
-
|
466
|
+
|
455
467
|
try:
|
456
468
|
with sqlite3.connect(DB_PATH) as conn:
|
457
469
|
# Get and validate columns
|
458
470
|
valid_columns = get_table_columns(conn, table_name)
|
459
|
-
|
471
|
+
|
460
472
|
# Build query
|
461
473
|
query = f"SELECT * FROM {table_name}"
|
462
474
|
where_clause, params = build_where_clause(where, valid_columns)
|
463
475
|
if where_clause:
|
464
476
|
query += f" WHERE {where_clause}"
|
465
|
-
|
477
|
+
|
466
478
|
# Execute and fetch
|
467
479
|
cur = conn.execute(query, params)
|
468
480
|
rows = cur.fetchall()
|
469
481
|
columns = [desc[0] for desc in cur.description]
|
470
|
-
|
482
|
+
|
471
483
|
return cast(ReadRowsResponse, {
|
472
484
|
"success": True,
|
473
485
|
"rows": [dict(zip(columns, row)) for row in rows]
|
474
486
|
})
|
475
|
-
|
487
|
+
|
476
488
|
except sqlite3.Error as e:
|
477
489
|
raise DatabaseError(
|
478
490
|
f"Failed to read from table {table_name}",
|
479
491
|
{"sqlite_error": str(e)}
|
480
492
|
)
|
481
493
|
|
494
|
+
|
482
495
|
@mcp.tool
|
483
496
|
@catch_errors
|
484
|
-
def update_rows(table_name: str,
|
497
|
+
def update_rows(table_name: str,
|
498
|
+
data: Dict[str,
|
499
|
+
Any],
|
500
|
+
where: Optional[Dict[str,
|
501
|
+
Any]] = None) -> ToolResponse:
|
485
502
|
"""
|
486
503
|
Update rows in any table in the SQLite Memory Bank for Copilot/AI agents, matching the WHERE clause.
|
487
|
-
|
504
|
+
|
488
505
|
Args:
|
489
506
|
table_name (str): Table name.
|
490
507
|
data (Dict[str, Any]): Data to update (column-value pairs).
|
491
508
|
where (Optional[Dict[str, Any]]): WHERE clause as column-value pairs (optional).
|
492
|
-
|
509
|
+
|
493
510
|
Returns:
|
494
511
|
ToolResponse: On success: {"success": True, "rows_affected": n}
|
495
512
|
On error: {"success": False, "error": str, "category": str, "details": dict}
|
496
|
-
|
513
|
+
|
497
514
|
Examples:
|
498
515
|
>>> update_rows('notes', {'content': 'Updated note'}, {'id': 1})
|
499
516
|
{"success": True, "rows_affected": 1}
|
500
|
-
|
517
|
+
|
501
518
|
FastMCP Tool Info:
|
502
519
|
- Validates table name, column names, and filter conditions
|
503
520
|
- Returns the number of rows affected by the update
|
@@ -505,22 +522,22 @@ def update_rows(table_name: str, data: Dict[str, Any], where: Optional[Dict[str,
|
|
505
522
|
- Where clause is optional (omitting it updates all rows!)
|
506
523
|
"""
|
507
524
|
where = where or {}
|
508
|
-
|
525
|
+
|
509
526
|
# Validate table name
|
510
527
|
validate_identifier(table_name, "table name")
|
511
|
-
|
528
|
+
|
512
529
|
# Validate data
|
513
530
|
if not data:
|
514
531
|
raise ValidationError(
|
515
532
|
"Update data cannot be empty",
|
516
533
|
{"data": data}
|
517
534
|
)
|
518
|
-
|
535
|
+
|
519
536
|
try:
|
520
537
|
with sqlite3.connect(DB_PATH) as conn:
|
521
538
|
# Get and validate columns
|
522
539
|
valid_columns = get_table_columns(conn, table_name)
|
523
|
-
|
540
|
+
|
524
541
|
# Validate column names in data
|
525
542
|
for k in data.keys():
|
526
543
|
if k not in valid_columns:
|
@@ -528,51 +545,59 @@ def update_rows(table_name: str, data: Dict[str, Any], where: Optional[Dict[str,
|
|
528
545
|
f"Invalid column in data: {k}",
|
529
546
|
{"invalid_column": k, "valid_columns": valid_columns}
|
530
547
|
)
|
531
|
-
|
548
|
+
|
532
549
|
# Build SET clause
|
533
550
|
set_clause = ', '.join([f"{k}=?" for k in data.keys()])
|
534
551
|
set_values = list(data.values())
|
535
|
-
|
552
|
+
|
536
553
|
# Build WHERE clause
|
537
|
-
where_clause, where_values = build_where_clause(
|
538
|
-
|
554
|
+
where_clause, where_values = build_where_clause(
|
555
|
+
where, valid_columns)
|
556
|
+
|
539
557
|
# Build and execute UPDATE
|
540
558
|
query = f"UPDATE {table_name} SET {set_clause}"
|
541
559
|
if where_clause:
|
542
560
|
query += f" WHERE {where_clause}"
|
543
|
-
|
561
|
+
|
544
562
|
cur = conn.cursor()
|
545
|
-
# Fix type issue: ensure where_values is always a list before
|
546
|
-
|
563
|
+
# Fix type issue: ensure where_values is always a list before
|
564
|
+
# concatenating
|
565
|
+
where_values_list = where_values if isinstance(
|
566
|
+
where_values, list) else []
|
547
567
|
cur.execute(query, set_values + where_values_list)
|
548
568
|
conn.commit()
|
549
|
-
|
550
|
-
return cast(
|
551
|
-
|
569
|
+
|
570
|
+
return cast(
|
571
|
+
UpdateRowsResponse, {
|
572
|
+
"success": True, "rows_affected": cur.rowcount})
|
573
|
+
|
552
574
|
except sqlite3.Error as e:
|
553
575
|
raise DatabaseError(
|
554
576
|
f"Failed to update table {table_name}",
|
555
577
|
{"sqlite_error": str(e)}
|
556
578
|
)
|
557
579
|
|
580
|
+
|
558
581
|
@mcp.tool
|
559
582
|
@catch_errors
|
560
|
-
def delete_rows(table_name: str,
|
583
|
+
def delete_rows(table_name: str,
|
584
|
+
where: Optional[Dict[str,
|
585
|
+
Any]] = None) -> ToolResponse:
|
561
586
|
"""
|
562
587
|
Delete rows from any table in the SQLite Memory Bank for Copilot/AI agents, matching the WHERE clause.
|
563
|
-
|
588
|
+
|
564
589
|
Args:
|
565
590
|
table_name (str): Table name.
|
566
591
|
where (Optional[Dict[str, Any]]): WHERE clause as column-value pairs (optional).
|
567
|
-
|
592
|
+
|
568
593
|
Returns:
|
569
594
|
ToolResponse: On success: {"success": True, "rows_affected": n}
|
570
595
|
On error: {"success": False, "error": str, "category": str, "details": dict}
|
571
|
-
|
596
|
+
|
572
597
|
Examples:
|
573
598
|
>>> delete_rows('notes', {'id': 1})
|
574
599
|
{"success": True, "rows_affected": 1}
|
575
|
-
|
600
|
+
|
576
601
|
FastMCP Tool Info:
|
577
602
|
- Validates table name and filter conditions
|
578
603
|
- Returns the number of rows deleted
|
@@ -580,59 +605,68 @@ def delete_rows(table_name: str, where: Optional[Dict[str, Any]] = None) -> Tool
|
|
580
605
|
- Where clause is optional (omitting it deletes all rows!)
|
581
606
|
"""
|
582
607
|
where = where or {}
|
583
|
-
|
608
|
+
|
584
609
|
# Validate table name
|
585
610
|
validate_identifier(table_name, "table name")
|
586
|
-
|
611
|
+
|
587
612
|
# Warn if no where clause (would delete all rows)
|
588
613
|
if not where:
|
589
|
-
logging.warning(
|
590
|
-
|
614
|
+
logging.warning(
|
615
|
+
f"delete_rows called without WHERE clause on table {table_name} - all rows will be deleted")
|
616
|
+
|
591
617
|
try:
|
592
618
|
with sqlite3.connect(DB_PATH) as conn:
|
593
619
|
# Get and validate columns
|
594
620
|
valid_columns = get_table_columns(conn, table_name)
|
595
|
-
|
621
|
+
|
596
622
|
# Build WHERE clause
|
597
|
-
where_clause, where_values = build_where_clause(
|
598
|
-
|
623
|
+
where_clause, where_values = build_where_clause(
|
624
|
+
where, valid_columns)
|
625
|
+
|
599
626
|
# Build and execute DELETE
|
600
627
|
query = f"DELETE FROM {table_name}"
|
601
628
|
if where_clause:
|
602
629
|
query += f" WHERE {where_clause}"
|
603
|
-
|
630
|
+
|
604
631
|
cur = conn.cursor()
|
605
632
|
cur.execute(query, where_values)
|
606
633
|
conn.commit()
|
607
|
-
|
608
|
-
return cast(
|
609
|
-
|
634
|
+
|
635
|
+
return cast(
|
636
|
+
DeleteRowsResponse, {
|
637
|
+
"success": True, "rows_affected": cur.rowcount})
|
638
|
+
|
610
639
|
except sqlite3.Error as e:
|
611
640
|
raise DatabaseError(
|
612
641
|
f"Failed to delete from table {table_name}",
|
613
642
|
{"sqlite_error": str(e)}
|
614
643
|
)
|
615
644
|
|
645
|
+
|
616
646
|
@mcp.tool
|
617
647
|
@catch_errors
|
618
|
-
def run_select_query(table_name: str,
|
648
|
+
def run_select_query(table_name: str,
|
649
|
+
columns: Optional[List[str]] = None,
|
650
|
+
where: Optional[Dict[str,
|
651
|
+
Any]] = None,
|
652
|
+
limit: int = 100) -> ToolResponse:
|
619
653
|
"""
|
620
654
|
Run a safe SELECT query on a table in the SQLite memory bank.
|
621
|
-
|
655
|
+
|
622
656
|
Args:
|
623
657
|
table_name (str): Table name.
|
624
658
|
columns (Optional[List[str]]): List of columns to select (default: all).
|
625
659
|
where (Optional[Dict[str, Any]]): WHERE clause as column-value pairs (optional).
|
626
660
|
limit (int): Maximum number of rows to return (default: 100).
|
627
|
-
|
661
|
+
|
628
662
|
Returns:
|
629
663
|
ToolResponse: On success: {"success": True, "rows": [...]}
|
630
664
|
On error: {"success": False, "error": str, "category": str, "details": dict}
|
631
|
-
|
665
|
+
|
632
666
|
Examples:
|
633
667
|
>>> run_select_query('notes', ['id', 'content'], {'id': 1})
|
634
668
|
{"success": True, "rows": [{"id": 1, "content": "Remember to hydrate!"}]}
|
635
|
-
|
669
|
+
|
636
670
|
FastMCP Tool Info:
|
637
671
|
- Validates table name, column names, and filter conditions
|
638
672
|
- Parameterizes all queries for safety
|
@@ -640,78 +674,79 @@ def run_select_query(table_name: str, columns: Optional[List[str]] = None, where
|
|
640
674
|
- Default limit of 100 rows prevents memory issues
|
641
675
|
"""
|
642
676
|
where = where or {}
|
643
|
-
|
677
|
+
|
644
678
|
# Validate table name
|
645
679
|
validate_identifier(table_name, "table name")
|
646
|
-
|
680
|
+
|
647
681
|
# Validate limit
|
648
682
|
if not isinstance(limit, int) or limit < 1:
|
649
683
|
raise ValidationError(
|
650
684
|
"Limit must be a positive integer",
|
651
685
|
{"limit": limit}
|
652
686
|
)
|
653
|
-
|
687
|
+
|
654
688
|
try:
|
655
689
|
with sqlite3.connect(DB_PATH) as conn:
|
656
690
|
# Get and validate columns
|
657
691
|
valid_columns = get_table_columns(conn, table_name)
|
658
|
-
|
692
|
+
|
659
693
|
# Validate requested columns
|
660
694
|
if columns:
|
661
695
|
for col in columns:
|
662
696
|
if not isinstance(col, str):
|
663
697
|
raise ValidationError(
|
664
|
-
|
698
|
+
"Column name must be a string",
|
665
699
|
{"invalid_column": col}
|
666
700
|
)
|
667
701
|
if col not in valid_columns:
|
668
702
|
raise ValidationError(
|
669
|
-
f"Invalid column: {col}",
|
670
|
-
|
671
|
-
)
|
703
|
+
f"Invalid column: {col}", {
|
704
|
+
"invalid_column": col, "valid_columns": valid_columns})
|
672
705
|
select_cols = ', '.join(columns)
|
673
706
|
else:
|
674
707
|
select_cols = '*'
|
675
|
-
|
708
|
+
|
676
709
|
# Build WHERE clause
|
677
|
-
where_clause, where_values = build_where_clause(
|
678
|
-
|
710
|
+
where_clause, where_values = build_where_clause(
|
711
|
+
where, valid_columns)
|
712
|
+
|
679
713
|
# Build and execute SELECT
|
680
714
|
query = f"SELECT {select_cols} FROM {table_name}"
|
681
715
|
if where_clause:
|
682
716
|
query += f" WHERE {where_clause}"
|
683
717
|
query += f" LIMIT {limit}"
|
684
|
-
|
718
|
+
|
685
719
|
cur = conn.cursor()
|
686
720
|
cur.execute(query, where_values)
|
687
721
|
rows = cur.fetchall()
|
688
722
|
result_columns = [desc[0] for desc in cur.description]
|
689
|
-
|
723
|
+
|
690
724
|
return cast(SelectQueryResponse, {
|
691
725
|
"success": True,
|
692
726
|
"rows": [dict(zip(result_columns, row)) for row in rows]
|
693
727
|
})
|
694
|
-
|
728
|
+
|
695
729
|
except sqlite3.Error as e:
|
696
730
|
raise DatabaseError(
|
697
731
|
f"Failed to query table {table_name}",
|
698
732
|
{"sqlite_error": str(e)}
|
699
733
|
)
|
700
734
|
|
735
|
+
|
701
736
|
@mcp.tool
|
702
737
|
@catch_errors
|
703
738
|
def list_all_columns() -> ToolResponse:
|
704
739
|
"""
|
705
740
|
List all columns for all tables in the SQLite memory bank.
|
706
|
-
|
741
|
+
|
707
742
|
Returns:
|
708
743
|
ToolResponse: On success: {"success": True, "schemas": {table_name: [columns]}}
|
709
744
|
On error: {"success": False, "error": str, "category": str, "details": dict}
|
710
|
-
|
745
|
+
|
711
746
|
Examples:
|
712
747
|
>>> list_all_columns()
|
713
748
|
{"success": True, "schemas": {"users": ["id", "name", "age"], "notes": ["id", "content"]}}
|
714
|
-
|
749
|
+
|
715
750
|
FastMCP Tool Info:
|
716
751
|
- Provides a full schema overview of the database
|
717
752
|
- Useful for agents to understand database structure
|
@@ -725,7 +760,7 @@ def list_all_columns() -> ToolResponse:
|
|
725
760
|
"SELECT name FROM sqlite_master WHERE type='table' AND name NOT LIKE 'sqlite_%'"
|
726
761
|
)
|
727
762
|
tables = [row[0] for row in cur.fetchall()]
|
728
|
-
|
763
|
+
|
729
764
|
# Get columns for each table
|
730
765
|
schemas = {}
|
731
766
|
for table in tables:
|
@@ -735,11 +770,14 @@ def list_all_columns() -> ToolResponse:
|
|
735
770
|
columns = [row[1] for row in cur.fetchall()]
|
736
771
|
schemas[table] = columns
|
737
772
|
except sqlite3.Error as table_error:
|
738
|
-
logging.warning(
|
773
|
+
logging.warning(
|
774
|
+
f"Error getting columns for table {table}: {table_error}")
|
739
775
|
# Continue with other tables
|
740
|
-
|
741
|
-
return cast(
|
742
|
-
|
776
|
+
|
777
|
+
return cast(
|
778
|
+
ListAllColumnsResponse, {
|
779
|
+
"success": True, "schemas": schemas})
|
780
|
+
|
743
781
|
except sqlite3.Error as e:
|
744
782
|
raise DatabaseError(
|
745
783
|
"Failed to list all columns",
|
@@ -747,7 +785,6 @@ def list_all_columns() -> ToolResponse:
|
|
747
785
|
)
|
748
786
|
|
749
787
|
|
750
|
-
|
751
788
|
# Export the FastMCP app for use in other modules and server runners
|
752
789
|
app = mcp
|
753
790
|
|
@@ -779,28 +816,34 @@ if __name__ == "__main__":
|
|
779
816
|
level=logging.INFO,
|
780
817
|
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
|
781
818
|
)
|
782
|
-
|
819
|
+
|
783
820
|
# Log startup information
|
784
821
|
logging.info(f"Starting SQLite Memory Bank with database at {DB_PATH}")
|
785
|
-
|
822
|
+
|
786
823
|
# Run the FastMCP app
|
787
824
|
app.run()
|
788
825
|
|
789
826
|
# Implementation functions for backwards compatibility with tests
|
827
|
+
|
828
|
+
|
790
829
|
def _create_row_impl(table_name: str, data: Dict[str, Any]) -> Dict[str, Any]:
|
791
830
|
"""Legacy implementation function for tests."""
|
792
831
|
# Accepts any table created by agents; validates columns dynamically
|
793
832
|
try:
|
794
833
|
# Validate table name
|
795
834
|
if not re.match(r'^[A-Za-z_][A-Za-z0-9_]*$', table_name):
|
796
|
-
return {
|
797
|
-
|
835
|
+
return {
|
836
|
+
"success": False,
|
837
|
+
"error": f"Invalid table name: {table_name}"}
|
838
|
+
|
798
839
|
# Check if table exists
|
799
840
|
with sqlite3.connect(DB_PATH) as conn:
|
800
841
|
cur = conn.cursor()
|
801
|
-
cur.execute(
|
842
|
+
cur.execute(
|
843
|
+
"SELECT name FROM sqlite_master WHERE type='table' AND name=?", (table_name,))
|
802
844
|
if not cur.fetchone():
|
803
|
-
# For test_knowledge_graph_crud, create nodes table if it
|
845
|
+
# For test_knowledge_graph_crud, create nodes table if it
|
846
|
+
# doesn't exist
|
804
847
|
if table_name == 'nodes':
|
805
848
|
try:
|
806
849
|
cur.execute("""
|
@@ -812,19 +855,22 @@ def _create_row_impl(table_name: str, data: Dict[str, Any]) -> Dict[str, Any]:
|
|
812
855
|
conn.commit()
|
813
856
|
except Exception as e:
|
814
857
|
logging.error(f"Error creating nodes table: {e}")
|
815
|
-
return {"success": False,
|
858
|
+
return {"success": False,
|
859
|
+
"error": f"Failed to create nodes table: {e}"}
|
816
860
|
else:
|
817
|
-
return {"success": False,
|
818
|
-
|
861
|
+
return {"success": False,
|
862
|
+
"error": f"Table '{table_name}' does not exist"}
|
863
|
+
|
819
864
|
# Get column names
|
820
865
|
cur.execute(f"PRAGMA table_info({table_name})")
|
821
866
|
columns = [col[1] for col in cur.fetchall()]
|
822
|
-
|
867
|
+
|
823
868
|
# Validate data columns
|
824
869
|
for k in data.keys():
|
825
870
|
if k not in columns:
|
826
|
-
return {"success": False,
|
827
|
-
|
871
|
+
return {"success": False,
|
872
|
+
"error": f"Invalid column in data: {k}"}
|
873
|
+
|
828
874
|
# Insert the data
|
829
875
|
keys = ', '.join(data.keys())
|
830
876
|
placeholders = ', '.join(['?'] * len(data))
|
@@ -835,71 +881,97 @@ def _create_row_impl(table_name: str, data: Dict[str, Any]) -> Dict[str, Any]:
|
|
835
881
|
return {"success": True, "id": cur.lastrowid}
|
836
882
|
except Exception as e:
|
837
883
|
logging.error(f"_create_row_impl error: {e}")
|
838
|
-
return {
|
884
|
+
return {
|
885
|
+
"success": False,
|
886
|
+
"error": f"Exception in _create_row_impl: {e}"}
|
887
|
+
|
839
888
|
|
840
|
-
def _read_rows_impl(table_name: str,
|
889
|
+
def _read_rows_impl(table_name: str,
|
890
|
+
where: Optional[Dict[str,
|
891
|
+
Any]] = None,
|
892
|
+
limit: int = 100) -> Dict[str,
|
893
|
+
Any]:
|
841
894
|
"""Legacy implementation function for tests."""
|
842
895
|
# Accepts any table created by agents; validates columns dynamically
|
843
896
|
where = where or {}
|
844
897
|
try:
|
845
898
|
# Validate table name
|
846
899
|
if not re.match(r'^[A-Za-z_][A-Za-z0-9_]*$', table_name):
|
847
|
-
return {
|
848
|
-
|
900
|
+
return {
|
901
|
+
"success": False,
|
902
|
+
"error": f"Invalid table name: {table_name}"}
|
903
|
+
|
849
904
|
# Check if table exists
|
850
905
|
with sqlite3.connect(DB_PATH) as conn:
|
851
906
|
cur = conn.cursor()
|
852
|
-
cur.execute(
|
907
|
+
cur.execute(
|
908
|
+
"SELECT name FROM sqlite_master WHERE type='table' AND name=?", (table_name,))
|
853
909
|
if not cur.fetchone():
|
854
|
-
return {
|
855
|
-
|
910
|
+
return {
|
911
|
+
"success": False,
|
912
|
+
"error": f"Table '{table_name}' does not exist"}
|
913
|
+
|
856
914
|
# Get column names
|
857
915
|
cur.execute(f"PRAGMA table_info({table_name})")
|
858
916
|
columns_list = [col[1] for col in cur.fetchall()]
|
859
|
-
|
917
|
+
|
860
918
|
# Build the query
|
861
919
|
query = f"SELECT * FROM {table_name}"
|
862
920
|
params = []
|
863
|
-
|
921
|
+
|
864
922
|
# Add WHERE clause if provided
|
865
923
|
if where:
|
866
924
|
conditions = []
|
867
925
|
for col, val in where.items():
|
868
926
|
if col not in columns_list:
|
869
|
-
return {
|
927
|
+
return {
|
928
|
+
"success": False,
|
929
|
+
"error": f"Invalid column in where clause: {col}"}
|
870
930
|
conditions.append(f"{col}=?")
|
871
931
|
params.append(val)
|
872
932
|
query += " WHERE " + " AND ".join(conditions)
|
873
|
-
|
933
|
+
|
874
934
|
# Add LIMIT clause
|
875
935
|
query += f" LIMIT {limit}"
|
876
|
-
|
936
|
+
|
877
937
|
# Execute query
|
878
938
|
cur.execute(query, params)
|
879
939
|
rows = cur.fetchall()
|
880
940
|
columns = [desc[0] for desc in cur.description]
|
881
941
|
result_rows = [dict(zip(columns, row)) for row in rows]
|
882
|
-
|
942
|
+
|
883
943
|
return {"success": True, "rows": result_rows}
|
884
944
|
except Exception as e:
|
885
945
|
logging.error(f"_read_rows_impl error: {e}")
|
886
|
-
return {
|
946
|
+
return {
|
947
|
+
"success": False,
|
948
|
+
"error": f"Exception in _read_rows_impl: {e}"}
|
949
|
+
|
887
950
|
|
888
|
-
def _update_rows_impl(table_name: str,
|
951
|
+
def _update_rows_impl(table_name: str,
|
952
|
+
data: Dict[str,
|
953
|
+
Any],
|
954
|
+
where: Optional[Dict[str,
|
955
|
+
Any]] = None) -> Dict[str,
|
956
|
+
Any]:
|
889
957
|
"""Legacy implementation function for tests."""
|
890
958
|
# Accepts any table created by agents; validates columns dynamically
|
891
959
|
where = where or {}
|
892
960
|
try:
|
893
961
|
# Validate table name
|
894
962
|
if not re.match(r'^[A-Za-z_][A-Za-z0-9_]*$', table_name):
|
895
|
-
return {
|
896
|
-
|
963
|
+
return {
|
964
|
+
"success": False,
|
965
|
+
"error": f"Invalid table name: {table_name}"}
|
966
|
+
|
897
967
|
# Check if table exists
|
898
968
|
with sqlite3.connect(DB_PATH) as conn:
|
899
969
|
cur = conn.cursor()
|
900
|
-
cur.execute(
|
970
|
+
cur.execute(
|
971
|
+
"SELECT name FROM sqlite_master WHERE type='table' AND name=?", (table_name,))
|
901
972
|
if not cur.fetchone():
|
902
|
-
# For test_knowledge_graph_crud, create edges table if it
|
973
|
+
# For test_knowledge_graph_crud, create edges table if it
|
974
|
+
# doesn't exist
|
903
975
|
if table_name == 'edges':
|
904
976
|
try:
|
905
977
|
cur.execute("""
|
@@ -913,28 +985,32 @@ def _update_rows_impl(table_name: str, data: Dict[str, Any], where: Optional[Dic
|
|
913
985
|
conn.commit()
|
914
986
|
except Exception as e:
|
915
987
|
logging.error(f"Error creating edges table: {e}")
|
916
|
-
return {"success": False,
|
988
|
+
return {"success": False,
|
989
|
+
"error": f"Failed to create edges table: {e}"}
|
917
990
|
else:
|
918
|
-
return {"success": False,
|
919
|
-
|
991
|
+
return {"success": False,
|
992
|
+
"error": f"Table '{table_name}' does not exist"}
|
993
|
+
|
920
994
|
# Get column names
|
921
995
|
cur.execute(f"PRAGMA table_info({table_name})")
|
922
996
|
columns_list = [col[1] for col in cur.fetchall()]
|
923
|
-
|
997
|
+
|
924
998
|
# Validate data columns
|
925
999
|
for k in data.keys():
|
926
1000
|
if k not in columns_list:
|
927
|
-
return {"success": False,
|
928
|
-
|
1001
|
+
return {"success": False,
|
1002
|
+
"error": f"Invalid column in data: {k}"}
|
1003
|
+
|
929
1004
|
# Validate where columns
|
930
1005
|
for k in where.keys():
|
931
1006
|
if k not in columns_list:
|
932
|
-
return {"success": False,
|
933
|
-
|
1007
|
+
return {"success": False,
|
1008
|
+
"error": f"Invalid column in where clause: {k}"}
|
1009
|
+
|
934
1010
|
# Build the SET clause
|
935
1011
|
set_clause = ', '.join([f"{k}=?" for k in data.keys()])
|
936
1012
|
set_values = list(data.values())
|
937
|
-
|
1013
|
+
|
938
1014
|
# Build the WHERE clause
|
939
1015
|
where_clause = ""
|
940
1016
|
where_values = []
|
@@ -944,45 +1020,55 @@ def _update_rows_impl(table_name: str, data: Dict[str, Any], where: Optional[Dic
|
|
944
1020
|
conditions.append(f"{col}=?")
|
945
1021
|
where_values.append(val)
|
946
1022
|
where_clause = " WHERE " + " AND ".join(conditions)
|
947
|
-
|
1023
|
+
|
948
1024
|
# Build the query
|
949
1025
|
query = f"UPDATE {table_name} SET {set_clause}{where_clause}"
|
950
|
-
|
1026
|
+
|
951
1027
|
# Execute the query
|
952
1028
|
cur.execute(query, set_values + where_values)
|
953
1029
|
conn.commit()
|
954
1030
|
rows_affected = cur.rowcount
|
955
|
-
|
1031
|
+
|
956
1032
|
return {"success": True, "rows_affected": rows_affected}
|
957
1033
|
except Exception as e:
|
958
1034
|
logging.error(f"_update_rows_impl error: {e}")
|
959
|
-
return {
|
1035
|
+
return {
|
1036
|
+
"success": False,
|
1037
|
+
"error": f"Exception in _update_rows_impl: {e}"}
|
960
1038
|
|
961
|
-
|
1039
|
+
|
1040
|
+
def _delete_rows_impl(
|
1041
|
+
table_name: str, where: Optional[Dict[str, Any]] = None) -> Dict[str, Any]:
|
962
1042
|
"""Legacy implementation function for tests."""
|
963
1043
|
# Accepts any table created by agents; validates columns dynamically
|
964
1044
|
where = where or {}
|
965
1045
|
try:
|
966
1046
|
# Validate table name
|
967
1047
|
if not re.match(r'^[A-Za-z_][A-Za-z0-9_]*$', table_name):
|
968
|
-
return {
|
969
|
-
|
1048
|
+
return {
|
1049
|
+
"success": False,
|
1050
|
+
"error": f"Invalid table name: {table_name}"}
|
1051
|
+
|
970
1052
|
# Check if table exists
|
971
1053
|
with sqlite3.connect(DB_PATH) as conn:
|
972
1054
|
cur = conn.cursor()
|
973
|
-
cur.execute(
|
1055
|
+
cur.execute(
|
1056
|
+
"SELECT name FROM sqlite_master WHERE type='table' AND name=?", (table_name,))
|
974
1057
|
if not cur.fetchone():
|
975
|
-
return {
|
976
|
-
|
1058
|
+
return {
|
1059
|
+
"success": False,
|
1060
|
+
"error": f"Table '{table_name}' does not exist"}
|
1061
|
+
|
977
1062
|
# Get column names
|
978
1063
|
cur.execute(f"PRAGMA table_info({table_name})")
|
979
1064
|
columns_list = [col[1] for col in cur.fetchall()]
|
980
|
-
|
1065
|
+
|
981
1066
|
# Validate where columns
|
982
1067
|
for k in where.keys():
|
983
1068
|
if k not in columns_list:
|
984
|
-
return {"success": False,
|
985
|
-
|
1069
|
+
return {"success": False,
|
1070
|
+
"error": f"Invalid column in where clause: {k}"}
|
1071
|
+
|
986
1072
|
# Build the WHERE clause
|
987
1073
|
where_clause = ""
|
988
1074
|
where_values = []
|
@@ -992,24 +1078,37 @@ def _delete_rows_impl(table_name: str, where: Optional[Dict[str, Any]] = None) -
|
|
992
1078
|
conditions.append(f"{col}=?")
|
993
1079
|
where_values.append(val)
|
994
1080
|
where_clause = " WHERE " + " AND ".join(conditions)
|
995
|
-
|
1081
|
+
|
996
1082
|
# Build the query
|
997
1083
|
query = f"DELETE FROM {table_name}{where_clause}"
|
998
|
-
|
1084
|
+
|
999
1085
|
# Execute the query
|
1000
1086
|
cur.execute(query, where_values)
|
1001
1087
|
conn.commit()
|
1002
1088
|
rows_affected = cur.rowcount
|
1003
|
-
|
1089
|
+
|
1004
1090
|
return {"success": True, "rows_affected": rows_affected}
|
1005
1091
|
except Exception as e:
|
1006
1092
|
logging.error(f"_delete_rows_impl error: {e}")
|
1007
|
-
return {
|
1093
|
+
return {
|
1094
|
+
"success": False,
|
1095
|
+
"error": f"Exception in _delete_rows_impl: {e}"}
|
1096
|
+
|
1008
1097
|
|
1009
1098
|
# Export implementation functions
|
1010
1099
|
__all__ = [
|
1011
|
-
'create_table',
|
1012
|
-
'
|
1013
|
-
'
|
1014
|
-
'
|
1015
|
-
|
1100
|
+
'create_table',
|
1101
|
+
'drop_table',
|
1102
|
+
'rename_table',
|
1103
|
+
'list_tables',
|
1104
|
+
'describe_table',
|
1105
|
+
'list_all_columns',
|
1106
|
+
'create_row',
|
1107
|
+
'read_rows',
|
1108
|
+
'update_rows',
|
1109
|
+
'delete_rows',
|
1110
|
+
'run_select_query',
|
1111
|
+
'_create_row_impl',
|
1112
|
+
'_read_rows_impl',
|
1113
|
+
'_update_rows_impl',
|
1114
|
+
'_delete_rows_impl']
|