Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
125 changes: 70 additions & 55 deletions memori/core/database.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
from datetime import datetime, timedelta
from pathlib import Path
from typing import Any
from traceback import format_exc

from loguru import logger

Expand Down Expand Up @@ -95,6 +96,9 @@ def initialize_schema(self):
logger.warning(
f"Schema execution issue: {e}, falling back to statement-by-statement"
)
logger.warning(
f"Schema execution error details: {format_exc()}"
)
# Fallback to statement-by-statement execution
self._execute_schema_statements(conn, schema_sql)

Expand All @@ -104,6 +108,9 @@ def initialize_schema(self):

except Exception as e:
logger.error(f"Failed to initialize schema: {e}")
logger.error(f"Schema initialization error details: {format_exc()}")
logger.info("Falling back to basic schema creation")

# Fallback to basic schema
self._create_basic_schema()

Expand Down Expand Up @@ -151,76 +158,84 @@ def _execute_schema_statements(self, conn: sqlite3.Connection, schema_sql: str):
try:
conn.execute(statement)
except sqlite3.Error as e:
logger.debug(f"Schema statement warning: {e}")
logger.debug(f"Schema statement error: {e}")
logger.debug(f"Schema statement error details: {format_exc()}")

conn.commit()

def _create_basic_schema(self):
"""Create basic schema if SQL file is not available"""
with self._get_connection() as conn:
cursor = conn.cursor()
try:
with self._get_connection() as conn:
cursor = conn.cursor()

# Basic tables for fallback
cursor.execute(
# Basic tables for fallback
cursor.execute(
"""
CREATE TABLE IF NOT EXISTS chat_history (
chat_id TEXT PRIMARY KEY,
user_input TEXT NOT NULL,
ai_output TEXT NOT NULL,
model TEXT NOT NULL,
timestamp TIMESTAMP NOT NULL,
session_id TEXT NOT NULL,
namespace TEXT NOT NULL DEFAULT 'default',
tokens_used INTEGER DEFAULT 0,
metadata TEXT DEFAULT '{}'
)
"""
CREATE TABLE IF NOT EXISTS chat_history (
chat_id TEXT PRIMARY KEY,
user_input TEXT NOT NULL,
ai_output TEXT NOT NULL,
model TEXT NOT NULL,
timestamp TIMESTAMP NOT NULL,
session_id TEXT NOT NULL,
namespace TEXT NOT NULL DEFAULT 'default',
tokens_used INTEGER DEFAULT 0,
metadata TEXT DEFAULT '{}'
)
"""
)

cursor.execute(
cursor.execute(
"""
CREATE TABLE IF NOT EXISTS short_term_memory (
memory_id TEXT PRIMARY KEY,
chat_id TEXT,
processed_data TEXT NOT NULL,
importance_score REAL NOT NULL DEFAULT 0.5,
category_primary TEXT NOT NULL,
retention_type TEXT NOT NULL DEFAULT 'short_term',
namespace TEXT NOT NULL DEFAULT 'default',
created_at TIMESTAMP NOT NULL,
expires_at TIMESTAMP,
access_count INTEGER DEFAULT 0,
last_accessed TIMESTAMP,
searchable_content TEXT NOT NULL,
summary TEXT NOT NULL
)
"""
CREATE TABLE IF NOT EXISTS short_term_memory (
memory_id TEXT PRIMARY KEY,
chat_id TEXT,
processed_data TEXT NOT NULL,
importance_score REAL NOT NULL DEFAULT 0.5,
category_primary TEXT NOT NULL,
retention_type TEXT NOT NULL DEFAULT 'short_term',
namespace TEXT NOT NULL DEFAULT 'default',
created_at TIMESTAMP NOT NULL,
expires_at TIMESTAMP,
access_count INTEGER DEFAULT 0,
last_accessed TIMESTAMP,
searchable_content TEXT NOT NULL,
summary TEXT NOT NULL
)
"""
)

cursor.execute(
cursor.execute(
"""
CREATE TABLE IF NOT EXISTS long_term_memory (
memory_id TEXT PRIMARY KEY,
original_chat_id TEXT,
processed_data TEXT NOT NULL,
importance_score REAL NOT NULL DEFAULT 0.5,
category_primary TEXT NOT NULL,
retention_type TEXT NOT NULL DEFAULT 'long_term',
namespace TEXT NOT NULL DEFAULT 'default',
created_at TIMESTAMP NOT NULL,
access_count INTEGER DEFAULT 0,
last_accessed TIMESTAMP,
searchable_content TEXT NOT NULL,
summary TEXT NOT NULL,
novelty_score REAL DEFAULT 0.5,
relevance_score REAL DEFAULT 0.5,
actionability_score REAL DEFAULT 0.5
)
"""
CREATE TABLE IF NOT EXISTS long_term_memory (
memory_id TEXT PRIMARY KEY,
original_chat_id TEXT,
processed_data TEXT NOT NULL,
importance_score REAL NOT NULL DEFAULT 0.5,
category_primary TEXT NOT NULL,
retention_type TEXT NOT NULL DEFAULT 'long_term',
namespace TEXT NOT NULL DEFAULT 'default',
created_at TIMESTAMP NOT NULL,
access_count INTEGER DEFAULT 0,
last_accessed TIMESTAMP,
searchable_content TEXT NOT NULL,
summary TEXT NOT NULL,
novelty_score REAL DEFAULT 0.5,
relevance_score REAL DEFAULT 0.5,
actionability_score REAL DEFAULT 0.5
)
"""
)

conn.commit()
logger.info("Basic database schema created")
conn.commit()
logger.info("Basic database schema created")
except Exception as e:
error_message = f"Failed to create basic schema: {e}"

logger.error(error_message)
logger.error(f"Basic schema creation error details: {format_exc()}")
raise DatabaseError(error_message)

def store_chat_history(
self,
Expand Down
98 changes: 98 additions & 0 deletions tests/schema_exception_test.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
#!/usr/bin/env python3
"""
A very simple test for schema initialization exception
logging and fallback to the basic schema
"""

from loguru import logger
import sys
import tempfile
from pathlib import Path

# Add the memori package to the Python path
sys.path.insert(0, str(Path(__file__).parent.parent))


def test_schema_exception_logging():
"""Test that schema initialization exceptions are logged with traceback and fallback to the basic schema"""
print("🧪 Testing schema exception logging and fallback to the basic schema...")

temp_path = None

try:
from memori.core.database import DatabaseManager

# Create temp database
with tempfile.NamedTemporaryFile(suffix=".db", delete=False) as f:
temp_path = f.name

# Create a list to store the log messages
captured_logs = []

# Create a handler that captures the log messages
def capture_log(message):
captured_logs.append(message.strip())

# Add the custom handler to the logger
logger.add(capture_log, level="TRACE")

# Test with invalid template (this will trigger the fallback)
db_manager = DatabaseManager(
database_connect=f"sqlite:///{temp_path}", template="basic"
)

# Initialize schema (it should fail and log errors then fallback to the basic schema)
db_manager.initialize_schema()

# Verify it worked by checking tables exist
with db_manager._get_connection() as conn:
cursor = conn.cursor()
cursor.execute("SELECT name FROM sqlite_master WHERE type='table'")
tables = [row[0] for row in cursor.fetchall()]

assert "chat_history" in tables, "Fallback schema creation did NOT work"

assert len(captured_logs) > 0, "No logs captured"

assert any(
"Schema execution issue" in log for log in captured_logs
), "Schema execution issue log not found"
assert any(
"Schema execution error details: Traceback" in log for log in captured_logs
), "Schema execution error details: Traceback log not found"

assert any(
"Schema statement error" in log for log in captured_logs
), "Schema statement error log not found"
assert any(
"Schema statement error details: Traceback" in log for log in captured_logs
), "Schema statement error details: Traceback log not found"

print("✅ Schema exception logging and fallback works")
return True

except Exception as e:
print(f"❌ Test failed: {e}")
return False
finally:
if temp_path and Path(temp_path).exists():
Path(temp_path).unlink(missing_ok=True)


def main():
"""Main test function"""
print("🚀 Schema Exception Logging Test")
print("=" * 40)

success = test_schema_exception_logging()

if success:
print("\n✅ Schema exception logging test passed!")
return 0
else:
print("\n❌ Schema exception logging test failed!")
return 1


if __name__ == "__main__":
sys.exit(main())