local-deep-research 0.6.0__py3-none-any.whl → 0.6.4__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.
- local_deep_research/__init__.py +6 -0
- local_deep_research/__version__.py +1 -1
- local_deep_research/setup_data_dir.py +3 -2
- local_deep_research/utilities/db_utils.py +11 -9
- local_deep_research/utilities/log_utils.py +1 -1
- local_deep_research/utilities/threading_utils.py +17 -4
- local_deep_research/web/api.py +5 -3
- local_deep_research/web/app.py +0 -74
- local_deep_research/web/app_factory.py +1 -11
- local_deep_research/web/database/migrations.py +699 -28
- local_deep_research/web/database/uuid_migration.py +2 -247
- local_deep_research/web/models/database.py +0 -79
- local_deep_research/web/routes/settings_routes.py +70 -73
- local_deep_research/web/services/settings_manager.py +13 -28
- local_deep_research/web/services/settings_service.py +6 -40
- local_deep_research/web/services/socket_service.py +1 -1
- local_deep_research/web/templates/pages/benchmark_results.html +146 -8
- local_deep_research/web_search_engines/rate_limiting/tracker.py +1 -3
- {local_deep_research-0.6.0.dist-info → local_deep_research-0.6.4.dist-info}/METADATA +20 -5
- {local_deep_research-0.6.0.dist-info → local_deep_research-0.6.4.dist-info}/RECORD +23 -26
- local_deep_research/migrate_db.py +0 -149
- local_deep_research/web/database/migrate_to_ldr_db.py +0 -297
- local_deep_research/web/database/schema_upgrade.py +0 -519
- {local_deep_research-0.6.0.dist-info → local_deep_research-0.6.4.dist-info}/WHEEL +0 -0
- {local_deep_research-0.6.0.dist-info → local_deep_research-0.6.4.dist-info}/entry_points.txt +0 -0
- {local_deep_research-0.6.0.dist-info → local_deep_research-0.6.4.dist-info}/licenses/LICENSE +0 -0
@@ -1,15 +1,18 @@
|
|
1
|
+
import sqlite3
|
2
|
+
import uuid
|
3
|
+
from pathlib import Path
|
4
|
+
|
1
5
|
from loguru import logger
|
2
|
-
from sqlalchemy import inspect
|
6
|
+
from sqlalchemy import inspect, text, create_engine
|
3
7
|
|
4
8
|
from ..services.settings_manager import SettingsManager
|
5
9
|
from .models import (
|
6
10
|
Base,
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
Research,
|
11
|
-
ResearchHistory,
|
11
|
+
ResearchStrategy,
|
12
|
+
RateLimitAttempt,
|
13
|
+
RateLimitEstimate,
|
12
14
|
)
|
15
|
+
from ...utilities.db_utils import DB_PATH, get_db_session
|
13
16
|
|
14
17
|
|
15
18
|
def import_default_settings_file(db_session):
|
@@ -33,40 +36,708 @@ def import_default_settings_file(db_session):
|
|
33
36
|
|
34
37
|
# Update the saved version.
|
35
38
|
settings_mgr.update_db_version()
|
39
|
+
except Exception:
|
40
|
+
logger.exception("Error importing settings from files")
|
41
|
+
|
42
|
+
|
43
|
+
def remove_research_log_table(engine):
|
44
|
+
"""
|
45
|
+
Remove the redundant research_log table if it exists
|
46
|
+
|
47
|
+
Args:
|
48
|
+
engine: SQLAlchemy engine
|
49
|
+
|
50
|
+
Returns:
|
51
|
+
bool: True if operation was successful, False otherwise
|
52
|
+
"""
|
53
|
+
try:
|
54
|
+
inspector = inspect(engine)
|
55
|
+
|
56
|
+
# Check if table exists
|
57
|
+
if inspector.has_table("research_log"):
|
58
|
+
# For SQLite, we need to use raw SQL for DROP TABLE
|
59
|
+
with engine.connect() as conn:
|
60
|
+
conn.execute("DROP TABLE research_log")
|
61
|
+
conn.commit()
|
62
|
+
logger.info("Successfully removed redundant 'research_log' table")
|
63
|
+
return True
|
64
|
+
else:
|
65
|
+
logger.info("Table 'research_log' does not exist, no action needed")
|
66
|
+
return True
|
67
|
+
except Exception:
|
68
|
+
logger.exception("Error removing research_log table")
|
69
|
+
return False
|
70
|
+
|
71
|
+
|
72
|
+
def create_research_strategy_table(engine):
|
73
|
+
"""
|
74
|
+
Create the research_strategies table if it doesn't exist
|
75
|
+
|
76
|
+
Args:
|
77
|
+
engine: SQLAlchemy engine
|
78
|
+
|
79
|
+
Returns:
|
80
|
+
bool: True if operation was successful, False otherwise
|
81
|
+
"""
|
82
|
+
try:
|
83
|
+
inspector = inspect(engine)
|
84
|
+
|
85
|
+
# Check if table exists
|
86
|
+
if not inspector.has_table("research_strategies"):
|
87
|
+
# Create the table using ORM
|
88
|
+
Base.metadata.create_all(
|
89
|
+
engine, tables=[ResearchStrategy.__table__]
|
90
|
+
)
|
91
|
+
logger.info("Successfully created 'research_strategies' table")
|
92
|
+
return True
|
93
|
+
else:
|
94
|
+
logger.info(
|
95
|
+
"Table 'research_strategies' already exists, no action needed"
|
96
|
+
)
|
97
|
+
return True
|
98
|
+
except Exception:
|
99
|
+
logger.exception("Error creating research_strategies table")
|
100
|
+
return False
|
101
|
+
|
102
|
+
|
103
|
+
def create_rate_limiting_tables(engine):
|
104
|
+
"""
|
105
|
+
Create rate limiting tables if they don't exist
|
106
|
+
|
107
|
+
Args:
|
108
|
+
engine: SQLAlchemy engine
|
109
|
+
|
110
|
+
Returns:
|
111
|
+
bool: True if operation was successful, False otherwise
|
112
|
+
"""
|
113
|
+
try:
|
114
|
+
inspector = inspect(engine)
|
115
|
+
|
116
|
+
tables_to_create = []
|
117
|
+
|
118
|
+
# Check if rate_limit_attempts table exists
|
119
|
+
if not inspector.has_table("rate_limit_attempts"):
|
120
|
+
tables_to_create.append(RateLimitAttempt.__table__)
|
121
|
+
logger.info("Need to create 'rate_limit_attempts' table")
|
122
|
+
|
123
|
+
# Check if rate_limit_estimates table exists
|
124
|
+
if not inspector.has_table("rate_limit_estimates"):
|
125
|
+
tables_to_create.append(RateLimitEstimate.__table__)
|
126
|
+
logger.info("Need to create 'rate_limit_estimates' table")
|
127
|
+
|
128
|
+
if tables_to_create:
|
129
|
+
# Create the tables using ORM
|
130
|
+
Base.metadata.create_all(engine, tables=tables_to_create)
|
131
|
+
logger.info(
|
132
|
+
f"Successfully created {len(tables_to_create)} rate limiting tables"
|
133
|
+
)
|
134
|
+
else:
|
135
|
+
logger.info("Rate limiting tables already exist, no action needed")
|
136
|
+
|
137
|
+
return True
|
138
|
+
except Exception:
|
139
|
+
logger.exception("Error creating rate limiting tables")
|
140
|
+
return False
|
141
|
+
|
142
|
+
|
143
|
+
def add_research_id_to_benchmark_results(engine):
|
144
|
+
"""
|
145
|
+
Add research_id column to benchmark_results table if it doesn't exist.
|
146
|
+
"""
|
147
|
+
try:
|
148
|
+
import sqlite3
|
149
|
+
|
150
|
+
# Get database path from engine
|
151
|
+
db_path = engine.url.database
|
152
|
+
|
153
|
+
logger.info("Checking if benchmark_results needs research_id column...")
|
154
|
+
|
155
|
+
conn = sqlite3.connect(db_path)
|
156
|
+
|
157
|
+
try:
|
158
|
+
cursor = conn.cursor()
|
159
|
+
|
160
|
+
# Check if table exists
|
161
|
+
cursor.execute(
|
162
|
+
"SELECT name FROM sqlite_master WHERE type='table' AND name='benchmark_results'"
|
163
|
+
)
|
164
|
+
if not cursor.fetchone():
|
165
|
+
logger.info("benchmark_results table does not exist, skipping")
|
166
|
+
return True
|
167
|
+
|
168
|
+
# Check if research_id column already exists
|
169
|
+
cursor.execute("PRAGMA table_info(benchmark_results)")
|
170
|
+
columns = cursor.fetchall()
|
171
|
+
has_research_id = any(col[1] == "research_id" for col in columns)
|
172
|
+
|
173
|
+
if has_research_id:
|
174
|
+
logger.info("benchmark_results already has research_id column")
|
175
|
+
return True
|
176
|
+
|
177
|
+
# Add research_id column
|
178
|
+
logger.info("Adding research_id column to benchmark_results table")
|
179
|
+
cursor.execute(
|
180
|
+
"ALTER TABLE benchmark_results ADD COLUMN research_id TEXT"
|
181
|
+
)
|
182
|
+
|
183
|
+
conn.commit()
|
184
|
+
logger.info(
|
185
|
+
"Successfully added research_id column to benchmark_results"
|
186
|
+
)
|
187
|
+
return True
|
188
|
+
|
189
|
+
finally:
|
190
|
+
conn.close()
|
191
|
+
|
192
|
+
except Exception:
|
193
|
+
logger.exception("Error adding research_id column to benchmark_results")
|
194
|
+
return False
|
195
|
+
|
196
|
+
|
197
|
+
def add_uuid_id_column_to_research_history(engine):
|
198
|
+
"""
|
199
|
+
Adds a new `uuid_id` string column to the `research_history` table if it
|
200
|
+
does not exist already.
|
201
|
+
"""
|
202
|
+
try:
|
203
|
+
import sqlite3
|
204
|
+
|
205
|
+
# Get database path from engine
|
206
|
+
db_path = engine.url.database
|
207
|
+
|
208
|
+
logger.info("Checking if research_history needs uuid_id column...")
|
209
|
+
|
210
|
+
conn = sqlite3.connect(db_path)
|
211
|
+
|
212
|
+
try:
|
213
|
+
cursor = conn.cursor()
|
214
|
+
|
215
|
+
# Check if table exists
|
216
|
+
cursor.execute(
|
217
|
+
"SELECT name FROM sqlite_master WHERE type='table' AND name='research_history'"
|
218
|
+
)
|
219
|
+
if not cursor.fetchone():
|
220
|
+
logger.info("research_history table does not exist, skipping")
|
221
|
+
return True
|
222
|
+
|
223
|
+
# Check if uuid_id column already exists
|
224
|
+
cursor.execute("PRAGMA table_info(research_history)")
|
225
|
+
columns = cursor.fetchall()
|
226
|
+
has_uuid_id = any(col[1] == "uuid_id" for col in columns)
|
227
|
+
|
228
|
+
if has_uuid_id:
|
229
|
+
logger.info("research_history already has uuid_id column")
|
230
|
+
return True
|
231
|
+
|
232
|
+
# Add uuid_id column
|
233
|
+
logger.info("Adding uuid_id column to research_history table")
|
234
|
+
cursor.execute(
|
235
|
+
"ALTER TABLE research_history ADD COLUMN uuid_id CHAR(36)"
|
236
|
+
)
|
237
|
+
|
238
|
+
conn.commit()
|
239
|
+
logger.info("Successfully added uuid_id column to research_history")
|
240
|
+
return True
|
241
|
+
|
242
|
+
finally:
|
243
|
+
conn.close()
|
244
|
+
|
245
|
+
except Exception:
|
246
|
+
logger.exception("Error adding uuid_id column to research_history")
|
247
|
+
return False
|
248
|
+
|
249
|
+
|
250
|
+
def convert_research_id_to_string_if_needed(engine):
|
251
|
+
"""
|
252
|
+
Convert research_id columns from Integer to String in all tables.
|
253
|
+
Preserves existing data by converting integer IDs to string format.
|
254
|
+
Only runs if integer research_id columns are detected.
|
255
|
+
"""
|
256
|
+
try:
|
257
|
+
import sqlite3
|
258
|
+
|
259
|
+
# Get database path from engine
|
260
|
+
db_path = engine.url.database
|
261
|
+
|
262
|
+
logger.info(
|
263
|
+
"Checking if research_id columns need conversion to string..."
|
264
|
+
)
|
265
|
+
|
266
|
+
conn = sqlite3.connect(db_path)
|
267
|
+
conn.execute("PRAGMA foreign_keys = OFF")
|
268
|
+
|
269
|
+
try:
|
270
|
+
cursor = conn.cursor()
|
271
|
+
|
272
|
+
# List of tables that might have research_id columns
|
273
|
+
tables_to_check = [
|
274
|
+
"token_usage",
|
275
|
+
"model_usage",
|
276
|
+
"search_calls",
|
277
|
+
"benchmark_results",
|
278
|
+
]
|
279
|
+
|
280
|
+
tables_needing_conversion = []
|
281
|
+
|
282
|
+
# Check which tables need conversion
|
283
|
+
for table_name in tables_to_check:
|
284
|
+
# Check if table exists
|
285
|
+
cursor.execute(
|
286
|
+
"SELECT name FROM sqlite_master WHERE type='table' AND name=?",
|
287
|
+
(table_name,),
|
288
|
+
)
|
289
|
+
if not cursor.fetchone():
|
290
|
+
continue
|
291
|
+
|
292
|
+
# Check if research_id column exists and is integer type
|
293
|
+
cursor.execute(f"PRAGMA table_info({table_name})")
|
294
|
+
columns = cursor.fetchall()
|
295
|
+
|
296
|
+
for col in columns:
|
297
|
+
col_name, col_type = col[1], col[2]
|
298
|
+
if col_name == "research_id" and (
|
299
|
+
"INTEGER" in col_type.upper()
|
300
|
+
or "INT" in col_type.upper()
|
301
|
+
):
|
302
|
+
tables_needing_conversion.append(table_name)
|
303
|
+
break
|
304
|
+
|
305
|
+
if not tables_needing_conversion:
|
306
|
+
logger.info(
|
307
|
+
"All research_id columns are already string type, no conversion needed"
|
308
|
+
)
|
309
|
+
return True
|
310
|
+
|
311
|
+
logger.info(
|
312
|
+
f"Converting research_id to string in tables: {tables_needing_conversion}"
|
313
|
+
)
|
314
|
+
|
315
|
+
# Convert each table
|
316
|
+
for table_name in tables_needing_conversion:
|
317
|
+
logger.info(f"Converting {table_name} table...")
|
318
|
+
|
319
|
+
# Get the current table schema
|
320
|
+
cursor.execute(
|
321
|
+
f"SELECT sql FROM sqlite_master WHERE type='table' AND name='{table_name}'"
|
322
|
+
)
|
323
|
+
create_sql = cursor.fetchone()[0]
|
324
|
+
|
325
|
+
# Create new table name
|
326
|
+
new_table_name = f"{table_name}_new"
|
327
|
+
|
328
|
+
# Modify the CREATE TABLE statement to change research_id to TEXT
|
329
|
+
new_create_sql = create_sql.replace(
|
330
|
+
f"CREATE TABLE {table_name}",
|
331
|
+
f"CREATE TABLE {new_table_name}",
|
332
|
+
)
|
333
|
+
new_create_sql = new_create_sql.replace(
|
334
|
+
"research_id INTEGER", "research_id TEXT"
|
335
|
+
)
|
336
|
+
new_create_sql = new_create_sql.replace(
|
337
|
+
"research_id INT", "research_id TEXT"
|
338
|
+
)
|
339
|
+
|
340
|
+
# Create the new table
|
341
|
+
cursor.execute(new_create_sql)
|
342
|
+
|
343
|
+
# Copy data from old table to new table, converting research_id to string
|
344
|
+
cursor.execute(f"SELECT * FROM {table_name}")
|
345
|
+
old_rows = cursor.fetchall()
|
346
|
+
|
347
|
+
if old_rows:
|
348
|
+
# Get column names
|
349
|
+
cursor.execute(f"PRAGMA table_info({table_name})")
|
350
|
+
columns = cursor.fetchall()
|
351
|
+
column_names = [col[1] for col in columns]
|
352
|
+
research_id_index = (
|
353
|
+
column_names.index("research_id")
|
354
|
+
if "research_id" in column_names
|
355
|
+
else -1
|
356
|
+
)
|
357
|
+
|
358
|
+
# Prepare insert statement
|
359
|
+
placeholders = ",".join(["?" for _ in column_names])
|
360
|
+
insert_sql = f"INSERT INTO {new_table_name} ({','.join(column_names)}) VALUES ({placeholders})"
|
361
|
+
|
362
|
+
# Convert rows and insert
|
363
|
+
converted_rows = []
|
364
|
+
for row in old_rows:
|
365
|
+
row_list = list(row)
|
366
|
+
# Convert research_id to string if it's not None
|
367
|
+
if (
|
368
|
+
research_id_index >= 0
|
369
|
+
and row_list[research_id_index] is not None
|
370
|
+
):
|
371
|
+
row_list[research_id_index] = str(
|
372
|
+
row_list[research_id_index]
|
373
|
+
)
|
374
|
+
converted_rows.append(tuple(row_list))
|
375
|
+
|
376
|
+
cursor.executemany(insert_sql, converted_rows)
|
377
|
+
logger.info(
|
378
|
+
f"Converted {len(converted_rows)} rows in {table_name}"
|
379
|
+
)
|
380
|
+
|
381
|
+
# Drop old table and rename new table
|
382
|
+
cursor.execute(f"DROP TABLE {table_name}")
|
383
|
+
cursor.execute(
|
384
|
+
f"ALTER TABLE {new_table_name} RENAME TO {table_name}"
|
385
|
+
)
|
386
|
+
|
387
|
+
logger.info(
|
388
|
+
f"Successfully converted {table_name} research_id to string"
|
389
|
+
)
|
390
|
+
|
391
|
+
# Commit all changes
|
392
|
+
conn.commit()
|
393
|
+
logger.info(
|
394
|
+
"All research_id columns converted to string successfully!"
|
395
|
+
)
|
396
|
+
return True
|
397
|
+
|
398
|
+
finally:
|
399
|
+
conn.execute("PRAGMA foreign_keys = ON")
|
400
|
+
conn.close()
|
401
|
+
|
402
|
+
except Exception:
|
403
|
+
logger.exception("Error converting research_id columns to string")
|
404
|
+
return False
|
405
|
+
|
406
|
+
|
407
|
+
def create_provider_models_table(engine):
|
408
|
+
"""Create provider_models table for caching available models"""
|
409
|
+
with engine.connect() as conn:
|
410
|
+
result = conn.execute(
|
411
|
+
text(
|
412
|
+
"SELECT name FROM sqlite_master WHERE type='table' AND name='provider_models'"
|
413
|
+
)
|
414
|
+
)
|
415
|
+
if result.fetchone():
|
416
|
+
logger.info(
|
417
|
+
"Table 'provider_models' already exists, no action needed"
|
418
|
+
)
|
419
|
+
return
|
420
|
+
|
421
|
+
logger.info("Creating 'provider_models' table...")
|
422
|
+
|
423
|
+
# Create the table
|
424
|
+
conn.execute(
|
425
|
+
text(
|
426
|
+
"""
|
427
|
+
CREATE TABLE provider_models (
|
428
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
429
|
+
provider VARCHAR(50) NOT NULL,
|
430
|
+
model_key VARCHAR(255) NOT NULL,
|
431
|
+
model_label VARCHAR(255) NOT NULL,
|
432
|
+
model_metadata JSON,
|
433
|
+
last_updated DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL,
|
434
|
+
UNIQUE(provider, model_key)
|
435
|
+
)
|
436
|
+
"""
|
437
|
+
)
|
438
|
+
)
|
439
|
+
|
440
|
+
# Create index on provider
|
441
|
+
conn.execute(
|
442
|
+
text(
|
443
|
+
"CREATE INDEX ix_provider_models_provider ON provider_models (provider)"
|
444
|
+
)
|
445
|
+
)
|
446
|
+
|
447
|
+
conn.commit()
|
448
|
+
logger.info("Table 'provider_models' created successfully")
|
449
|
+
|
450
|
+
|
451
|
+
def migrate_to_uuid():
|
452
|
+
"""
|
453
|
+
Migrate all research_id fields from integers to UUIDs.
|
454
|
+
|
455
|
+
Strategy:
|
456
|
+
1. Add new UUID columns alongside existing integer columns
|
457
|
+
2. Generate UUIDs for existing data (or keep as string versions of integers)
|
458
|
+
3. Update foreign key relationships
|
459
|
+
4. Drop old integer columns and rename UUID columns
|
460
|
+
"""
|
461
|
+
db_path = Path(DB_PATH)
|
462
|
+
if not db_path.exists():
|
463
|
+
logger.info("Database doesn't exist yet, migration not needed")
|
464
|
+
return
|
465
|
+
|
466
|
+
logger.info(f"Starting UUID migration on {db_path}")
|
467
|
+
|
468
|
+
conn = sqlite3.connect(db_path)
|
469
|
+
conn.execute(
|
470
|
+
"PRAGMA foreign_keys = OFF"
|
471
|
+
) # Disable FK constraints during migration
|
472
|
+
|
473
|
+
try:
|
474
|
+
cursor = conn.cursor()
|
475
|
+
|
476
|
+
# 1. Migrate research_history table (main research IDs)
|
477
|
+
logger.info("Migrating research_history table...")
|
478
|
+
|
479
|
+
# Check if the table exists and has the old structure
|
480
|
+
cursor.execute("PRAGMA table_info(research_history)")
|
481
|
+
columns = cursor.fetchall()
|
482
|
+
has_uuid_id = any(col[1] == "uuid_id" for col in columns)
|
483
|
+
|
484
|
+
if not has_uuid_id:
|
485
|
+
# Add UUID column
|
486
|
+
cursor.execute(
|
487
|
+
"ALTER TABLE research_history ADD COLUMN uuid_id TEXT"
|
488
|
+
)
|
489
|
+
|
490
|
+
# Generate UUIDs for existing records (convert integer ID to UUID format)
|
491
|
+
cursor.execute("SELECT id FROM research_history")
|
492
|
+
existing_ids = cursor.fetchall()
|
493
|
+
|
494
|
+
for (old_id,) in existing_ids:
|
495
|
+
# Generate a deterministic UUID based on the old ID
|
496
|
+
new_uuid = str(
|
497
|
+
uuid.uuid5(uuid.NAMESPACE_OID, f"research_{old_id}")
|
498
|
+
)
|
499
|
+
cursor.execute(
|
500
|
+
"UPDATE research_history SET uuid_id = ? WHERE id = ?",
|
501
|
+
(new_uuid, old_id),
|
502
|
+
)
|
503
|
+
|
504
|
+
logger.info(
|
505
|
+
f"Generated UUIDs for {len(existing_ids)} research records"
|
506
|
+
)
|
507
|
+
|
508
|
+
# 2. Migrate metrics tables
|
509
|
+
logger.info("Migrating metrics tables...")
|
510
|
+
|
511
|
+
# Token usage table
|
512
|
+
cursor.execute("PRAGMA table_info(token_usage)")
|
513
|
+
columns = cursor.fetchall()
|
514
|
+
has_uuid_research_id = any(
|
515
|
+
col[1] == "uuid_research_id" for col in columns
|
516
|
+
)
|
517
|
+
|
518
|
+
if not has_uuid_research_id:
|
519
|
+
cursor.execute(
|
520
|
+
"ALTER TABLE token_usage ADD COLUMN uuid_research_id TEXT"
|
521
|
+
)
|
522
|
+
|
523
|
+
# Convert existing research_ids to UUIDs (deterministic conversion)
|
524
|
+
cursor.execute(
|
525
|
+
"SELECT DISTINCT research_id FROM token_usage WHERE research_id IS NOT NULL"
|
526
|
+
)
|
527
|
+
research_ids = cursor.fetchall()
|
528
|
+
|
529
|
+
for (research_id,) in research_ids:
|
530
|
+
if research_id:
|
531
|
+
new_uuid = str(
|
532
|
+
uuid.uuid5(
|
533
|
+
uuid.NAMESPACE_OID, f"research_{research_id}"
|
534
|
+
)
|
535
|
+
)
|
536
|
+
cursor.execute(
|
537
|
+
"UPDATE token_usage SET uuid_research_id = ? WHERE research_id = ?",
|
538
|
+
(new_uuid, research_id),
|
539
|
+
)
|
540
|
+
|
541
|
+
logger.info(
|
542
|
+
f"Migrated {len(research_ids)} research IDs in token_usage"
|
543
|
+
)
|
544
|
+
|
545
|
+
# Model usage table
|
546
|
+
cursor.execute("PRAGMA table_info(model_usage)")
|
547
|
+
columns = cursor.fetchall()
|
548
|
+
has_uuid_research_id = any(
|
549
|
+
col[1] == "uuid_research_id" for col in columns
|
550
|
+
)
|
551
|
+
|
552
|
+
if not has_uuid_research_id:
|
553
|
+
cursor.execute(
|
554
|
+
"ALTER TABLE model_usage ADD COLUMN uuid_research_id TEXT"
|
555
|
+
)
|
556
|
+
|
557
|
+
cursor.execute(
|
558
|
+
"SELECT DISTINCT research_id FROM model_usage WHERE research_id IS NOT NULL"
|
559
|
+
)
|
560
|
+
research_ids = cursor.fetchall()
|
561
|
+
|
562
|
+
for (research_id,) in research_ids:
|
563
|
+
if research_id:
|
564
|
+
new_uuid = str(
|
565
|
+
uuid.uuid5(
|
566
|
+
uuid.NAMESPACE_OID, f"research_{research_id}"
|
567
|
+
)
|
568
|
+
)
|
569
|
+
cursor.execute(
|
570
|
+
"UPDATE model_usage SET uuid_research_id = ? WHERE research_id = ?",
|
571
|
+
(new_uuid, research_id),
|
572
|
+
)
|
573
|
+
|
574
|
+
logger.info(
|
575
|
+
f"Migrated {len(research_ids)} research IDs in model_usage"
|
576
|
+
)
|
577
|
+
|
578
|
+
# Search calls table
|
579
|
+
cursor.execute("PRAGMA table_info(search_calls)")
|
580
|
+
columns = cursor.fetchall()
|
581
|
+
has_uuid_research_id = any(
|
582
|
+
col[1] == "uuid_research_id" for col in columns
|
583
|
+
)
|
584
|
+
|
585
|
+
if not has_uuid_research_id:
|
586
|
+
cursor.execute(
|
587
|
+
"ALTER TABLE search_calls ADD COLUMN uuid_research_id TEXT"
|
588
|
+
)
|
589
|
+
|
590
|
+
cursor.execute(
|
591
|
+
"SELECT DISTINCT research_id FROM search_calls WHERE research_id IS NOT NULL"
|
592
|
+
)
|
593
|
+
research_ids = cursor.fetchall()
|
594
|
+
|
595
|
+
for (research_id,) in research_ids:
|
596
|
+
if research_id:
|
597
|
+
new_uuid = str(
|
598
|
+
uuid.uuid5(
|
599
|
+
uuid.NAMESPACE_OID, f"research_{research_id}"
|
600
|
+
)
|
601
|
+
)
|
602
|
+
cursor.execute(
|
603
|
+
"UPDATE search_calls SET uuid_research_id = ? WHERE research_id = ?",
|
604
|
+
(new_uuid, research_id),
|
605
|
+
)
|
606
|
+
|
607
|
+
logger.info(
|
608
|
+
f"Migrated {len(research_ids)} research IDs in search_calls"
|
609
|
+
)
|
610
|
+
|
611
|
+
# 3. Migrate benchmark tables
|
612
|
+
logger.info("Migrating benchmark tables...")
|
613
|
+
|
614
|
+
# Check if benchmark_results table exists
|
615
|
+
cursor.execute(
|
616
|
+
"SELECT name FROM sqlite_master WHERE type='table' AND name='benchmark_results'"
|
617
|
+
)
|
618
|
+
if cursor.fetchone():
|
619
|
+
cursor.execute("PRAGMA table_info(benchmark_results)")
|
620
|
+
columns = cursor.fetchall()
|
621
|
+
has_uuid_research_id = any(
|
622
|
+
col[1] == "uuid_research_id" for col in columns
|
623
|
+
)
|
624
|
+
|
625
|
+
if not has_uuid_research_id:
|
626
|
+
cursor.execute(
|
627
|
+
"ALTER TABLE benchmark_results ADD COLUMN uuid_research_id TEXT"
|
628
|
+
)
|
629
|
+
|
630
|
+
cursor.execute(
|
631
|
+
"SELECT DISTINCT research_id FROM benchmark_results WHERE research_id IS NOT NULL"
|
632
|
+
)
|
633
|
+
research_ids = cursor.fetchall()
|
634
|
+
|
635
|
+
for (research_id,) in research_ids:
|
636
|
+
if research_id:
|
637
|
+
new_uuid = str(
|
638
|
+
uuid.uuid5(
|
639
|
+
uuid.NAMESPACE_OID, f"research_{research_id}"
|
640
|
+
)
|
641
|
+
)
|
642
|
+
cursor.execute(
|
643
|
+
"UPDATE benchmark_results SET uuid_research_id = ? WHERE research_id = ?",
|
644
|
+
(new_uuid, research_id),
|
645
|
+
)
|
646
|
+
|
647
|
+
logger.info(
|
648
|
+
f"Migrated {len(research_ids)} research IDs in benchmark_results"
|
649
|
+
)
|
650
|
+
|
651
|
+
# Commit all changes
|
652
|
+
conn.commit()
|
653
|
+
logger.info("UUID migration completed successfully!")
|
654
|
+
|
655
|
+
# Note: We're keeping both old and new columns for now
|
656
|
+
# The application will use the new UUID columns
|
657
|
+
# Old columns can be dropped in a future migration once everything is stable
|
658
|
+
|
36
659
|
except Exception as e:
|
37
|
-
logger.error("Error
|
660
|
+
logger.error(f"Error during UUID migration: {e}")
|
661
|
+
conn.rollback()
|
662
|
+
raise
|
663
|
+
finally:
|
664
|
+
conn.execute("PRAGMA foreign_keys = ON") # Re-enable FK constraints
|
665
|
+
conn.close()
|
666
|
+
|
667
|
+
|
668
|
+
def cleanup_old_columns():
|
669
|
+
"""
|
670
|
+
Cleanup migration - drops old integer columns after UUID migration is stable.
|
671
|
+
Run this only after confirming the UUID migration is working correctly.
|
672
|
+
"""
|
673
|
+
logger.warning(
|
674
|
+
"This will permanently remove old integer research_id columns!"
|
675
|
+
)
|
676
|
+
logger.warning("Make sure to backup your database before running this!")
|
677
|
+
|
678
|
+
conn = sqlite3.connect(DB_PATH)
|
679
|
+
conn.execute("PRAGMA foreign_keys = OFF")
|
680
|
+
|
681
|
+
try:
|
682
|
+
# For SQLite, we need to recreate tables to drop columns
|
683
|
+
# This is complex, so we'll leave old columns for now
|
684
|
+
# They can be cleaned up manually if needed
|
685
|
+
|
686
|
+
logger.info("Cleanup deferred - old columns remain for safety")
|
38
687
|
|
688
|
+
finally:
|
689
|
+
conn.execute("PRAGMA foreign_keys = ON")
|
690
|
+
conn.close()
|
39
691
|
|
40
|
-
|
692
|
+
|
693
|
+
def ensure_database_initialized(engine=None, db_session=None):
|
41
694
|
"""
|
42
|
-
|
695
|
+
Initialize the database and run any necessary database migrations
|
43
696
|
|
44
697
|
Args:
|
45
698
|
engine: SQLAlchemy engine
|
46
699
|
db_session: Optional SQLAlchemy session
|
47
700
|
"""
|
701
|
+
logger.info("Initializing database at {}.", DB_PATH)
|
702
|
+
|
703
|
+
if engine is None:
|
704
|
+
# Create SQLAlchemy engine
|
705
|
+
engine = create_engine(f"sqlite:///{DB_PATH}")
|
706
|
+
if db_session is None:
|
707
|
+
db_session = get_db_session()
|
708
|
+
|
48
709
|
# Create all tables if they don't exist
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
710
|
+
Base.metadata.create_all(engine)
|
711
|
+
|
712
|
+
# Import existing settings from files
|
713
|
+
import_default_settings_file(db_session)
|
53
714
|
|
54
|
-
|
55
|
-
|
56
|
-
|
715
|
+
try:
|
716
|
+
# 1. Remove the redundant research_log table
|
717
|
+
remove_research_log_table(engine)
|
57
718
|
|
58
|
-
|
59
|
-
|
60
|
-
Base.metadata.create_all(engine, tables=[ResearchLog.__table__])
|
719
|
+
# 2. Create research_strategies table
|
720
|
+
create_research_strategy_table(engine)
|
61
721
|
|
62
|
-
|
63
|
-
|
64
|
-
Base.metadata.create_all(engine, tables=[Research.__table__])
|
722
|
+
# 4. Create rate limiting tables
|
723
|
+
create_rate_limiting_tables(engine)
|
65
724
|
|
66
|
-
|
67
|
-
|
68
|
-
Base.metadata.create_all(engine, tables=[ResearchHistory.__table__])
|
725
|
+
# 5. Add research_id column to benchmark_results if missing
|
726
|
+
add_research_id_to_benchmark_results(engine)
|
69
727
|
|
70
|
-
|
71
|
-
|
72
|
-
|
728
|
+
# 6. Convert research_id columns from integer to string
|
729
|
+
convert_research_id_to_string_if_needed(engine)
|
730
|
+
|
731
|
+
# 7. Add uuid_id column to research_history if missing
|
732
|
+
add_uuid_id_column_to_research_history(engine)
|
733
|
+
|
734
|
+
# 8. Create provider_models table for caching
|
735
|
+
create_provider_models_table(engine)
|
736
|
+
|
737
|
+
logger.info("Schema upgrades completed successfully")
|
738
|
+
return True
|
739
|
+
except Exception:
|
740
|
+
logger.exception("Error during schema upgrades")
|
741
|
+
return False
|
742
|
+
finally:
|
743
|
+
db_session.close()
|