JatsTheAIGen commited on
Commit
35d9168
Β·
1 Parent(s): ca444e2

cache key error when user id changes -fixed task 1 31_10_2025 v2

Browse files
Files changed (3) hide show
  1. migrate_database.py +408 -0
  2. sessions_v2.db +0 -0
  3. update_database_schema.py +245 -0
migrate_database.py ADDED
@@ -0,0 +1,408 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python
2
+ """
3
+ Database Migration Script - Fresh Start
4
+ Creates a new database with enhanced schema to prevent context loops.
5
+ Optionally migrates data from existing database.
6
+ """
7
+
8
+ import sqlite3
9
+ import os
10
+ import sys
11
+ import json
12
+ import shutil
13
+ from datetime import datetime
14
+ import hashlib
15
+
16
+ class DatabaseMigration:
17
+ def __init__(self, old_db_path='sessions.db', new_db_path='sessions_v2.db'):
18
+ self.old_db_path = old_db_path
19
+ self.new_db_path = new_db_path
20
+ self.schema_file = 'database_schema.sql'
21
+
22
+ def backup_existing_database(self):
23
+ """Create a backup of the existing database"""
24
+ if os.path.exists(self.old_db_path):
25
+ backup_path = f"{self.old_db_path}.backup_{datetime.now().strftime('%Y%m%d_%H%M%S')}"
26
+ shutil.copy2(self.old_db_path, backup_path)
27
+ print(f"βœ… Created backup: {backup_path}")
28
+ return backup_path
29
+ else:
30
+ print(f"ℹ️ No existing database found at {self.old_db_path}")
31
+ return None
32
+
33
+ def create_fresh_database(self):
34
+ """Create a new database with the updated schema"""
35
+ print(f"\nπŸ”¨ Creating fresh database: {self.new_db_path}")
36
+
37
+ # Remove existing new database if it exists
38
+ if os.path.exists(self.new_db_path):
39
+ os.remove(self.new_db_path)
40
+ print(f" Removed existing {self.new_db_path}")
41
+
42
+ # Read schema from file or use embedded schema
43
+ if os.path.exists(self.schema_file):
44
+ print(f" Loading schema from {self.schema_file}")
45
+ with open(self.schema_file, 'r') as f:
46
+ schema_sql = f.read()
47
+ else:
48
+ print(" Using embedded schema")
49
+ schema_sql = self.get_embedded_schema()
50
+
51
+ # Create new database
52
+ conn = sqlite3.connect(self.new_db_path)
53
+ cursor = conn.cursor()
54
+
55
+ # Execute schema
56
+ try:
57
+ cursor.executescript(schema_sql)
58
+ conn.commit()
59
+ print("βœ… Database created successfully")
60
+
61
+ # Verify tables were created
62
+ cursor.execute("SELECT name FROM sqlite_master WHERE type='table' ORDER BY name")
63
+ tables = cursor.fetchall()
64
+ print(f" Created {len(tables)} tables: {', '.join([t[0] for t in tables])}")
65
+
66
+ # Verify indexes were created
67
+ cursor.execute("SELECT name FROM sqlite_master WHERE type='index' AND name NOT LIKE 'sqlite_%' ORDER BY name")
68
+ indexes = cursor.fetchall()
69
+ print(f" Created {len(indexes)} indexes")
70
+
71
+ except Exception as e:
72
+ print(f"❌ Error creating database: {e}")
73
+ conn.close()
74
+ return False
75
+
76
+ conn.close()
77
+ return True
78
+
79
+ def migrate_data(self):
80
+ """Migrate data from old database to new one"""
81
+ if not os.path.exists(self.old_db_path):
82
+ print("ℹ️ No old database to migrate from")
83
+ return True
84
+
85
+ print(f"\nπŸ“¦ Migrating data from {self.old_db_path} to {self.new_db_path}")
86
+
87
+ old_conn = sqlite3.connect(self.old_db_path)
88
+ old_conn.row_factory = sqlite3.Row
89
+ new_conn = sqlite3.connect(self.new_db_path)
90
+
91
+ try:
92
+ # Check what tables exist in old database
93
+ old_cursor = old_conn.cursor()
94
+ old_cursor.execute("SELECT name FROM sqlite_master WHERE type='table'")
95
+ old_tables = [row[0] for row in old_cursor.fetchall()]
96
+ print(f" Old database tables: {', '.join(old_tables)}")
97
+
98
+ migrated_counts = {}
99
+
100
+ # Migrate sessions
101
+ if 'sessions' in old_tables:
102
+ old_cursor.execute("SELECT * FROM sessions")
103
+ sessions = old_cursor.fetchall()
104
+
105
+ new_cursor = new_conn.cursor()
106
+ for session in sessions:
107
+ # Extract user_id if it exists, otherwise use default
108
+ user_id = session['user_id'] if 'user_id' in session.keys() else 'Test_Any'
109
+
110
+ new_cursor.execute("""
111
+ INSERT OR IGNORE INTO sessions
112
+ (session_id, user_id, created_at, last_activity, context_data, user_metadata)
113
+ VALUES (?, ?, ?, ?, ?, ?)
114
+ """, (
115
+ session['session_id'],
116
+ user_id,
117
+ session['created_at'] if 'created_at' in session.keys() else datetime.now().isoformat(),
118
+ session['last_activity'] if 'last_activity' in session.keys() else datetime.now().isoformat(),
119
+ session['context_data'] if 'context_data' in session.keys() else '{}',
120
+ session['user_metadata'] if 'user_metadata' in session.keys() else '{}'
121
+ ))
122
+
123
+ migrated_counts['sessions'] = len(sessions)
124
+ print(f" βœ… Migrated {len(sessions)} sessions")
125
+
126
+ # Migrate interactions with deduplication
127
+ if 'interactions' in old_tables:
128
+ old_cursor.execute("SELECT * FROM interactions")
129
+ interactions = old_cursor.fetchall()
130
+
131
+ new_cursor = new_conn.cursor()
132
+ for interaction in interactions:
133
+ # Generate interaction hash for deduplication
134
+ hash_content = f"{interaction['session_id']}:{interaction['user_input']}:{interaction['created_at']}"
135
+ interaction_hash = hashlib.sha256(hash_content.encode()).hexdigest()
136
+
137
+ # Check for required fields
138
+ interaction_id = interaction['interaction_id'] if 'interaction_id' in interaction.keys() else interaction['id'] if 'id' in interaction.keys() else str(hash(hash_content))
139
+
140
+ new_cursor.execute("""
141
+ INSERT OR IGNORE INTO interactions
142
+ (interaction_id, interaction_hash, session_id, user_input, final_response,
143
+ processing_time, created_at)
144
+ VALUES (?, ?, ?, ?, ?, ?, ?)
145
+ """, (
146
+ str(interaction_id),
147
+ interaction_hash,
148
+ interaction['session_id'],
149
+ interaction['user_input'],
150
+ interaction['final_response'] if 'final_response' in interaction.keys() else None,
151
+ interaction['processing_time'] if 'processing_time' in interaction.keys() else None,
152
+ interaction['created_at'] if 'created_at' in interaction.keys() else datetime.now().isoformat()
153
+ ))
154
+
155
+ migrated_counts['interactions'] = len(interactions)
156
+ print(f" βœ… Migrated {len(interactions)} interactions")
157
+
158
+ # Migrate user_contexts if they exist
159
+ if 'user_contexts' in old_tables:
160
+ old_cursor.execute("SELECT * FROM user_contexts")
161
+ user_contexts = old_cursor.fetchall()
162
+
163
+ new_cursor = new_conn.cursor()
164
+ for context in user_contexts:
165
+ new_cursor.execute("""
166
+ INSERT OR REPLACE INTO user_contexts
167
+ (user_id, persona_summary, updated_at)
168
+ VALUES (?, ?, ?)
169
+ """, (
170
+ context['user_id'],
171
+ context['persona_summary'] if 'persona_summary' in context.keys() else None,
172
+ context['updated_at'] if 'updated_at' in context.keys() else datetime.now().isoformat()
173
+ ))
174
+
175
+ migrated_counts['user_contexts'] = len(user_contexts)
176
+ print(f" βœ… Migrated {len(user_contexts)} user contexts")
177
+
178
+ # Migrate interaction_contexts if they exist
179
+ if 'interaction_contexts' in old_tables:
180
+ old_cursor.execute("SELECT * FROM interaction_contexts")
181
+ int_contexts = old_cursor.fetchall()
182
+
183
+ new_cursor = new_conn.cursor()
184
+ for context in int_contexts:
185
+ new_cursor.execute("""
186
+ INSERT OR IGNORE INTO interaction_contexts
187
+ (interaction_id, session_id, user_input, system_response,
188
+ interaction_summary, created_at)
189
+ VALUES (?, ?, ?, ?, ?, ?)
190
+ """, (
191
+ context['interaction_id'],
192
+ context['session_id'],
193
+ context['user_input'] if 'user_input' in context.keys() else None,
194
+ context['system_response'] if 'system_response' in context.keys() else None,
195
+ context['interaction_summary'] if 'interaction_summary' in context.keys() else None,
196
+ context['created_at'] if 'created_at' in context.keys() else datetime.now().isoformat()
197
+ ))
198
+
199
+ migrated_counts['interaction_contexts'] = len(int_contexts)
200
+ print(f" βœ… Migrated {len(int_contexts)} interaction contexts")
201
+
202
+ new_conn.commit()
203
+
204
+ # Print summary
205
+ print("\nπŸ“Š Migration Summary:")
206
+ for table, count in migrated_counts.items():
207
+ print(f" {table}: {count} records")
208
+
209
+ except Exception as e:
210
+ print(f"❌ Error during migration: {e}")
211
+ new_conn.rollback()
212
+ return False
213
+ finally:
214
+ old_conn.close()
215
+ new_conn.close()
216
+
217
+ return True
218
+
219
+ def verify_new_database(self):
220
+ """Verify the new database is working correctly"""
221
+ print(f"\nπŸ” Verifying new database: {self.new_db_path}")
222
+
223
+ conn = sqlite3.connect(self.new_db_path)
224
+ cursor = conn.cursor()
225
+
226
+ try:
227
+ # Check tables
228
+ cursor.execute("SELECT COUNT(*) FROM sqlite_master WHERE type='table'")
229
+ table_count = cursor.fetchone()[0]
230
+ print(f" Tables: {table_count}")
231
+
232
+ # Check indexes
233
+ cursor.execute("SELECT COUNT(*) FROM sqlite_master WHERE type='index' AND name NOT LIKE 'sqlite_%'")
234
+ index_count = cursor.fetchone()[0]
235
+ print(f" Indexes: {index_count}")
236
+
237
+ # Check triggers
238
+ cursor.execute("SELECT COUNT(*) FROM sqlite_master WHERE type='trigger'")
239
+ trigger_count = cursor.fetchone()[0]
240
+ print(f" Triggers: {trigger_count}")
241
+
242
+ # Check views
243
+ cursor.execute("SELECT COUNT(*) FROM sqlite_master WHERE type='view'")
244
+ view_count = cursor.fetchone()[0]
245
+ print(f" Views: {view_count}")
246
+
247
+ # Check data
248
+ cursor.execute("SELECT COUNT(*) FROM sessions")
249
+ session_count = cursor.fetchone()[0]
250
+ print(f" Sessions: {session_count}")
251
+
252
+ cursor.execute("SELECT COUNT(*) FROM interactions")
253
+ interaction_count = cursor.fetchone()[0]
254
+ print(f" Interactions: {interaction_count}")
255
+
256
+ cursor.execute("SELECT COUNT(*) FROM user_contexts")
257
+ user_count = cursor.fetchone()[0]
258
+ print(f" Users: {user_count}")
259
+
260
+ # Test a view
261
+ cursor.execute("SELECT COUNT(*) FROM active_sessions")
262
+ active_count = cursor.fetchone()[0]
263
+ print(f" Active sessions (view): {active_count}")
264
+
265
+ print("\nβœ… Database verification complete!")
266
+
267
+ except Exception as e:
268
+ print(f"❌ Verification error: {e}")
269
+ return False
270
+ finally:
271
+ conn.close()
272
+
273
+ return True
274
+
275
+ def replace_old_database(self):
276
+ """Replace the old database with the new one"""
277
+ print(f"\nπŸ”„ Replacing old database...")
278
+
279
+ # Move old database to .old
280
+ if os.path.exists(self.old_db_path):
281
+ old_backup = f"{self.old_db_path}.old"
282
+ if os.path.exists(old_backup):
283
+ os.remove(old_backup)
284
+ os.rename(self.old_db_path, old_backup)
285
+ print(f" Moved {self.old_db_path} to {old_backup}")
286
+
287
+ # Move new database to production name
288
+ os.rename(self.new_db_path, self.old_db_path)
289
+ print(f" Moved {self.new_db_path} to {self.old_db_path}")
290
+
291
+ print("βœ… Database replacement complete!")
292
+ return True
293
+
294
+ def get_embedded_schema(self):
295
+ """Return the embedded schema if file is not available"""
296
+ return """
297
+ -- Embedded minimal schema (use database_schema.sql for full version)
298
+ PRAGMA journal_mode=WAL;
299
+ PRAGMA foreign_keys=ON;
300
+
301
+ CREATE TABLE sessions (
302
+ session_id TEXT PRIMARY KEY,
303
+ user_id TEXT NOT NULL DEFAULT 'Test_Any',
304
+ created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
305
+ last_activity TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
306
+ context_data TEXT DEFAULT '{}',
307
+ user_metadata TEXT DEFAULT '{}'
308
+ );
309
+
310
+ CREATE TABLE interactions (
311
+ interaction_id TEXT PRIMARY KEY,
312
+ interaction_hash TEXT UNIQUE,
313
+ session_id TEXT NOT NULL,
314
+ user_input TEXT NOT NULL,
315
+ final_response TEXT,
316
+ processing_time REAL,
317
+ created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
318
+ FOREIGN KEY(session_id) REFERENCES sessions(session_id)
319
+ );
320
+
321
+ CREATE TABLE user_contexts (
322
+ user_id TEXT PRIMARY KEY,
323
+ persona_summary TEXT,
324
+ updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
325
+ );
326
+
327
+ CREATE TABLE interaction_contexts (
328
+ interaction_id TEXT PRIMARY KEY,
329
+ session_id TEXT NOT NULL,
330
+ user_input TEXT,
331
+ system_response TEXT,
332
+ interaction_summary TEXT,
333
+ needs_refresh INTEGER DEFAULT 0,
334
+ created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
335
+ FOREIGN KEY(session_id) REFERENCES sessions(session_id)
336
+ );
337
+
338
+ CREATE TABLE user_change_log (
339
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
340
+ session_id TEXT NOT NULL,
341
+ old_user_id TEXT,
342
+ new_user_id TEXT NOT NULL,
343
+ timestamp TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
344
+ FOREIGN KEY(session_id) REFERENCES sessions(session_id)
345
+ );
346
+
347
+ -- Basic indexes
348
+ CREATE INDEX idx_sessions_user_id ON sessions(user_id);
349
+ CREATE INDEX idx_interactions_session_id ON interactions(session_id);
350
+ CREATE INDEX idx_interactions_hash ON interactions(interaction_hash);
351
+ """
352
+
353
+ def main():
354
+ """Main migration process"""
355
+ print("=" * 60)
356
+ print("DATABASE MIGRATION TOOL")
357
+ print("This will create a fresh database with loop prevention fixes")
358
+ print("=" * 60)
359
+
360
+ # Parse arguments
361
+ migrate_data = True
362
+ replace_db = False
363
+
364
+ if len(sys.argv) > 1:
365
+ if '--no-migration' in sys.argv:
366
+ migrate_data = False
367
+ print("ℹ️ Data migration disabled")
368
+ if '--replace' in sys.argv:
369
+ replace_db = True
370
+ print("ℹ️ Will replace existing database")
371
+
372
+ migration = DatabaseMigration()
373
+
374
+ # Step 1: Backup
375
+ backup_path = migration.backup_existing_database()
376
+
377
+ # Step 2: Create fresh database
378
+ if not migration.create_fresh_database():
379
+ print("❌ Failed to create new database")
380
+ return 1
381
+
382
+ # Step 3: Migrate data (optional)
383
+ if migrate_data and backup_path:
384
+ if not migration.migrate_data():
385
+ print("⚠️ Data migration had issues, but continuing...")
386
+
387
+ # Step 4: Verify
388
+ if not migration.verify_new_database():
389
+ print("❌ Database verification failed")
390
+ return 1
391
+
392
+ # Step 5: Replace (optional)
393
+ if replace_db:
394
+ if not migration.replace_old_database():
395
+ print("❌ Failed to replace database")
396
+ return 1
397
+ else:
398
+ print(f"\nβœ… New database created at: {migration.new_db_path}")
399
+ print(f"To use it, either:")
400
+ print(f" 1. Run with --replace flag to replace the old database")
401
+ print(f" 2. Manually rename {migration.new_db_path} to sessions.db")
402
+ print(f" 3. Update your application to use {migration.new_db_path}")
403
+
404
+ print("\nπŸŽ‰ Migration complete!")
405
+ return 0
406
+
407
+ if __name__ == "__main__":
408
+ sys.exit(main())
sessions_v2.db ADDED
Binary file (28.7 kB). View file
 
update_database_schema.py ADDED
@@ -0,0 +1,245 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python
2
+ """
3
+ Database Schema Update Script
4
+ Updates the sessions.db database with necessary schema changes for the context loop fix.
5
+ """
6
+
7
+ import sqlite3
8
+ import os
9
+ import sys
10
+ from datetime import datetime
11
+
12
+ def update_database_schema(db_path='sessions.db'):
13
+ """
14
+ Update database schema with loop prevention improvements
15
+
16
+ Args:
17
+ db_path: Path to the SQLite database file (default: sessions.db)
18
+ """
19
+
20
+ # Check if database file exists
21
+ if not os.path.exists(db_path):
22
+ print(f"❌ Error: Database file '{db_path}' not found!")
23
+ print(f"Current directory: {os.getcwd()}")
24
+ print(f"Files in current directory: {os.listdir('.')}")
25
+ return False
26
+
27
+ # Verify it's a valid SQLite database
28
+ try:
29
+ # Test connection
30
+ test_conn = sqlite3.connect(db_path)
31
+ test_cursor = test_conn.cursor()
32
+ test_cursor.execute("SELECT name FROM sqlite_master WHERE type='table' LIMIT 1")
33
+ test_conn.close()
34
+ except sqlite3.DatabaseError as e:
35
+ print(f"❌ Error: '{db_path}' is not a valid SQLite database!")
36
+ print(f"Error details: {e}")
37
+ return False
38
+
39
+ print(f"βœ… Found database: {db_path}")
40
+ print(f"πŸ“ File size: {os.path.getsize(db_path):,} bytes")
41
+ print("=" * 50)
42
+
43
+ applied_count = 0
44
+ skipped_count = 0
45
+ failed_count = 0
46
+
47
+ try:
48
+ conn = sqlite3.connect(db_path)
49
+ cursor = conn.cursor()
50
+
51
+ # First, check existing schema
52
+ print("Checking existing schema...")
53
+ cursor.execute("SELECT name FROM sqlite_master WHERE type='table'")
54
+ existing_tables = [row[0] for row in cursor.fetchall()]
55
+ print(f"Existing tables: {', '.join(existing_tables)}")
56
+ print("-" * 50)
57
+
58
+ # Enable WAL mode for better concurrency
59
+ try:
60
+ cursor.execute('PRAGMA journal_mode=WAL')
61
+ print("βœ“ Enabled WAL mode for better concurrency")
62
+ applied_count += 1
63
+ except sqlite3.OperationalError as e:
64
+ print(f"βœ— Failed to enable WAL mode: {e}")
65
+ failed_count += 1
66
+
67
+ # Schema updates to apply
68
+ schema_updates = [
69
+ # Add interaction_hash for deduplication
70
+ {
71
+ 'name': 'interaction_hash column',
72
+ 'check_sql': "PRAGMA table_info(interactions)",
73
+ 'check_field': 'interaction_hash',
74
+ 'update_sql': "ALTER TABLE interactions ADD COLUMN interaction_hash TEXT UNIQUE",
75
+ 'table_required': 'interactions'
76
+ },
77
+
78
+ # Add needs_refresh flag for cache invalidation
79
+ {
80
+ 'name': 'needs_refresh column',
81
+ 'check_sql': "PRAGMA table_info(interaction_contexts)",
82
+ 'check_field': 'needs_refresh',
83
+ 'update_sql': "ALTER TABLE interaction_contexts ADD COLUMN needs_refresh INTEGER DEFAULT 0",
84
+ 'table_required': 'interaction_contexts'
85
+ },
86
+
87
+ # Create user change log table
88
+ {
89
+ 'name': 'user_change_log table',
90
+ 'check_sql': "SELECT name FROM sqlite_master WHERE type='table' AND name='user_change_log'",
91
+ 'check_exists': False,
92
+ 'update_sql': """CREATE TABLE IF NOT EXISTS user_change_log (
93
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
94
+ session_id TEXT,
95
+ old_user_id TEXT,
96
+ new_user_id TEXT,
97
+ timestamp TIMESTAMP,
98
+ FOREIGN KEY(session_id) REFERENCES sessions(session_id)
99
+ )"""
100
+ }
101
+ ]
102
+
103
+ # Apply schema updates
104
+ for update in schema_updates:
105
+ try:
106
+ # Check if table exists (if required)
107
+ if 'table_required' in update:
108
+ if update['table_required'] not in existing_tables:
109
+ print(f"⚠ Skipping {update['name']} - table '{update['table_required']}' doesn't exist")
110
+ skipped_count += 1
111
+ continue
112
+
113
+ # Check if update is needed
114
+ needs_update = True
115
+ if 'check_field' in update:
116
+ cursor.execute(update['check_sql'])
117
+ columns = [row[1] for row in cursor.fetchall()]
118
+ if update['check_field'] in columns:
119
+ print(f"⚠ {update['name']} already exists")
120
+ skipped_count += 1
121
+ needs_update = False
122
+ elif 'check_exists' in update and not update['check_exists']:
123
+ cursor.execute(update['check_sql'])
124
+ if cursor.fetchone():
125
+ print(f"⚠ {update['name']} already exists")
126
+ skipped_count += 1
127
+ needs_update = False
128
+
129
+ # Apply update if needed
130
+ if needs_update:
131
+ cursor.execute(update['update_sql'])
132
+ print(f"βœ“ Applied: {update['name']}")
133
+ applied_count += 1
134
+
135
+ except sqlite3.OperationalError as e:
136
+ error_msg = str(e).lower()
137
+ if "duplicate column" in error_msg or "already exists" in error_msg:
138
+ print(f"⚠ {update['name']} already exists")
139
+ skipped_count += 1
140
+ else:
141
+ print(f"βœ— Failed: {update['name']} - {e}")
142
+ failed_count += 1
143
+
144
+ print("-" * 50)
145
+
146
+ # Create indexes for better performance
147
+ indexes = [
148
+ ("idx_sessions_user_id", "CREATE INDEX IF NOT EXISTS idx_sessions_user_id ON sessions(user_id)", "sessions"),
149
+ ("idx_sessions_last_activity", "CREATE INDEX IF NOT EXISTS idx_sessions_last_activity ON sessions(last_activity)", "sessions"),
150
+ ("idx_interaction_hash", "CREATE INDEX IF NOT EXISTS idx_interaction_hash ON interactions(interaction_hash)", "interactions"),
151
+ ("idx_interaction_contexts_session_id", "CREATE INDEX IF NOT EXISTS idx_interaction_contexts_session_id ON interaction_contexts(session_id)", "interaction_contexts"),
152
+ ("idx_interaction_contexts_needs_refresh", "CREATE INDEX IF NOT EXISTS idx_interaction_contexts_needs_refresh ON interaction_contexts(needs_refresh)", "interaction_contexts"),
153
+ ("idx_user_change_log_session_id", "CREATE INDEX IF NOT EXISTS idx_user_change_log_session_id ON user_change_log(session_id)", "user_change_log")
154
+ ]
155
+
156
+ print("Creating performance indexes...")
157
+ for index_name, index_sql, required_table in indexes:
158
+ try:
159
+ # Check if required table exists
160
+ if required_table not in existing_tables and required_table != "user_change_log":
161
+ print(f"⚠ Skipping {index_name} - table '{required_table}' doesn't exist")
162
+ skipped_count += 1
163
+ continue
164
+
165
+ cursor.execute(index_sql)
166
+ print(f"βœ“ Created index: {index_name}")
167
+ applied_count += 1
168
+ except sqlite3.OperationalError as e:
169
+ if "already exists" in str(e).lower():
170
+ print(f"⚠ Index {index_name} already exists")
171
+ skipped_count += 1
172
+ else:
173
+ print(f"βœ— Failed: {index_name} - {e}")
174
+ failed_count += 1
175
+
176
+ # Run ANALYZE to optimize query planner
177
+ try:
178
+ cursor.execute("ANALYZE")
179
+ print("βœ“ Database statistics updated (ANALYZE)")
180
+ applied_count += 1
181
+ except Exception as e:
182
+ print(f"βœ— Failed to run ANALYZE: {e}")
183
+ failed_count += 1
184
+
185
+ # Commit all changes
186
+ conn.commit()
187
+
188
+ # Get final schema info
189
+ print("-" * 50)
190
+ print("Final schema check:")
191
+ cursor.execute("SELECT name, type FROM sqlite_master WHERE type IN ('table', 'index') ORDER BY type, name")
192
+ for name, obj_type in cursor.fetchall():
193
+ print(f" {obj_type.capitalize()}: {name}")
194
+
195
+ conn.close()
196
+
197
+ except Exception as e:
198
+ print(f"❌ Unexpected error: {e}")
199
+ if conn:
200
+ conn.rollback()
201
+ conn.close()
202
+ return False
203
+
204
+ print("=" * 50)
205
+ print("Database schema update complete!")
206
+ print(f" Applied: {applied_count}")
207
+ print(f" Skipped: {skipped_count} (already exists)")
208
+ print(f" Failed: {failed_count}")
209
+ print("=" * 50)
210
+
211
+ return failed_count == 0
212
+
213
+ def main():
214
+ """Main entry point"""
215
+ # Check command line arguments
216
+ if len(sys.argv) > 1:
217
+ db_path = sys.argv[1]
218
+ # Check if user accidentally passed the SQL schema file
219
+ if db_path.endswith('.sql'):
220
+ print("❌ Error: You provided a .sql file, but this script needs the SQLite database file!")
221
+ print("Usage: python update_database_schema.py [database_file]")
222
+ print("Example: python update_database_schema.py sessions.db")
223
+ print("\nLooking for SQLite database files in current directory...")
224
+ db_files = [f for f in os.listdir('.') if f.endswith('.db')]
225
+ if db_files:
226
+ print(f"Found: {', '.join(db_files)}")
227
+ if 'sessions.db' in db_files:
228
+ print("\nWould you like to use 'sessions.db'? Run:")
229
+ print("python update_database_schema.py sessions.db")
230
+ else:
231
+ print("No .db files found in current directory.")
232
+ return 1
233
+ else:
234
+ # Default to sessions.db
235
+ db_path = 'sessions.db'
236
+
237
+ print(f"Database Schema Update Script")
238
+ print(f"Target database: {db_path}")
239
+ print("=" * 50)
240
+
241
+ success = update_database_schema(db_path)
242
+ return 0 if success else 1
243
+
244
+ if __name__ == "__main__":
245
+ sys.exit(main())