|
|
@@ -10,37 +10,60 @@ _log = logging.getLogger(__name__)
|
|
|
|
|
|
_CREATE_TABLE = '''
|
|
|
CREATE TABLE IF NOT EXISTS events (
|
|
|
- uid TEXT NOT NULL,
|
|
|
- calendar_name TEXT NOT NULL,
|
|
|
- start TEXT NOT NULL,
|
|
|
- end TEXT NOT NULL,
|
|
|
- all_day INTEGER NOT NULL,
|
|
|
- title TEXT NOT NULL,
|
|
|
- description TEXT NOT NULL DEFAULT '',
|
|
|
- x_data TEXT,
|
|
|
- PRIMARY KEY (uid, calendar_name)
|
|
|
+ uid TEXT NOT NULL PRIMARY KEY,
|
|
|
+ start TEXT NOT NULL,
|
|
|
+ end TEXT NOT NULL,
|
|
|
+ all_day INTEGER NOT NULL,
|
|
|
+ title TEXT NOT NULL,
|
|
|
+ description TEXT NOT NULL DEFAULT '',
|
|
|
+ x_data TEXT
|
|
|
)
|
|
|
'''
|
|
|
|
|
|
+_CREATE_INDEX = 'CREATE INDEX IF NOT EXISTS idx_events_start_end ON events(start, end)'
|
|
|
+
|
|
|
# This is not a carefully optimized SQL implementation. It shoves JSON blobs into a column
|
|
|
# But it is very easy to map to CalDAV, which is where the room_cal module was born, and
|
|
|
# it's so much faster than CalDAV that it's hard to care about the simplistic design
|
|
|
-_COLS = 'uid, calendar_name, start, end, all_day, title, description, x_data'
|
|
|
+_COLS = 'uid, start, end, all_day, title, description, x_data'
|
|
|
+
|
|
|
+_MIGRATE_DROP_CALENDAR_NAME = '''
|
|
|
+CREATE TABLE events_new (
|
|
|
+ uid TEXT NOT NULL PRIMARY KEY,
|
|
|
+ start TEXT NOT NULL,
|
|
|
+ end TEXT NOT NULL,
|
|
|
+ all_day INTEGER NOT NULL,
|
|
|
+ title TEXT NOT NULL,
|
|
|
+ description TEXT NOT NULL DEFAULT '',
|
|
|
+ x_data TEXT
|
|
|
+);
|
|
|
+INSERT INTO events_new SELECT uid, start, end, all_day, title, description, x_data FROM events;
|
|
|
+DROP TABLE events;
|
|
|
+ALTER TABLE events_new RENAME TO events;
|
|
|
+CREATE INDEX IF NOT EXISTS idx_events_start_end ON events(start, end);
|
|
|
+'''
|
|
|
|
|
|
|
|
|
@register_backend('sqlite')
|
|
|
class SQLiteBackend:
|
|
|
- def __init__(self, db_path: Path, calendar_name: str):
|
|
|
+ def __init__(self, db_path: Path):
|
|
|
self._db_path = db_path
|
|
|
- self._calendar_name = calendar_name
|
|
|
|
|
|
@classmethod
|
|
|
- def from_store_config(cls, store_cfg, calendar_name: str) -> 'SQLiteBackend':
|
|
|
+ def from_store_config(cls, store_cfg) -> 'SQLiteBackend':
|
|
|
+ import sqlite3
|
|
|
from room_cal._utils import data_home
|
|
|
db_path = Path(store_cfg.sqlite.db_path)
|
|
|
if not db_path.is_absolute():
|
|
|
db_path = data_home() / db_path
|
|
|
- return cls(db_path, calendar_name)
|
|
|
+ with sqlite3.connect(db_path) as conn:
|
|
|
+ cols = {row[1] for row in conn.execute('PRAGMA table_info(events)')}
|
|
|
+ if 'calendar_name' in cols:
|
|
|
+ conn.executescript(_MIGRATE_DROP_CALENDAR_NAME)
|
|
|
+ else:
|
|
|
+ conn.execute(_CREATE_TABLE)
|
|
|
+ conn.execute(_CREATE_INDEX)
|
|
|
+ return cls(db_path)
|
|
|
|
|
|
@asynccontextmanager
|
|
|
async def _connect(self):
|
|
|
@@ -52,7 +75,7 @@ class SQLiteBackend:
|
|
|
raise sqlite3.OperationalError(f"SQLite error for {self._db_path}: {e}") from None
|
|
|
|
|
|
def _to_record(self, row) -> EventRecord:
|
|
|
- uid, _, start, end, all_day, title, description, x_data = row
|
|
|
+ uid, start, end, all_day, title, description, x_data = row
|
|
|
return EventRecord(
|
|
|
uid=uid,
|
|
|
start=datetime.fromisoformat(start),
|
|
|
@@ -67,8 +90,8 @@ class SQLiteBackend:
|
|
|
async with self._connect() as db:
|
|
|
await db.execute(_CREATE_TABLE)
|
|
|
cursor = await db.execute(
|
|
|
- f'SELECT {_COLS} FROM events WHERE calendar_name = ? AND start < ? AND end > ? ORDER BY start',
|
|
|
- (self._calendar_name, end.isoformat(), start.isoformat()),
|
|
|
+ f'SELECT {_COLS} FROM events WHERE start < ? AND end > ? ORDER BY start',
|
|
|
+ (end.isoformat(), start.isoformat()),
|
|
|
)
|
|
|
rows = await cursor.fetchall()
|
|
|
return [self._to_record(r) for r in rows]
|
|
|
@@ -77,8 +100,8 @@ class SQLiteBackend:
|
|
|
async with self._connect() as db:
|
|
|
await db.execute(_CREATE_TABLE)
|
|
|
await db.execute(
|
|
|
- f'INSERT INTO events ({_COLS}) VALUES (?, ?, ?, ?, ?, ?, ?, ?)',
|
|
|
- (record.uid, self._calendar_name, record.start.isoformat(), record.end.isoformat(),
|
|
|
+ f'INSERT INTO events ({_COLS}) VALUES (?, ?, ?, ?, ?, ?, ?)',
|
|
|
+ (record.uid, record.start.isoformat(), record.end.isoformat(),
|
|
|
int(record.all_day), record.title, record.description, record.x_data),
|
|
|
)
|
|
|
await db.commit()
|
|
|
@@ -90,25 +113,13 @@ class SQLiteBackend:
|
|
|
async with self._connect() as db:
|
|
|
await db.execute(_CREATE_TABLE)
|
|
|
if title is not None:
|
|
|
- await db.execute(
|
|
|
- 'UPDATE events SET title = ? WHERE uid = ? AND calendar_name = ?',
|
|
|
- (title, uid, self._calendar_name),
|
|
|
- )
|
|
|
+ await db.execute('UPDATE events SET title = ? WHERE uid = ?', (title, uid))
|
|
|
if description is not None:
|
|
|
- await db.execute(
|
|
|
- 'UPDATE events SET description = ? WHERE uid = ? AND calendar_name = ?',
|
|
|
- (description, uid, self._calendar_name),
|
|
|
- )
|
|
|
+ await db.execute('UPDATE events SET description = ? WHERE uid = ?', (description, uid))
|
|
|
if x_data is not None:
|
|
|
- await db.execute(
|
|
|
- 'UPDATE events SET x_data = ? WHERE uid = ? AND calendar_name = ?',
|
|
|
- (x_data, uid, self._calendar_name),
|
|
|
- )
|
|
|
+ await db.execute('UPDATE events SET x_data = ? WHERE uid = ?', (x_data, uid))
|
|
|
await db.commit()
|
|
|
- cursor = await db.execute(
|
|
|
- f'SELECT {_COLS} FROM events WHERE uid = ? AND calendar_name = ?',
|
|
|
- (uid, self._calendar_name),
|
|
|
- )
|
|
|
+ cursor = await db.execute(f'SELECT {_COLS} FROM events WHERE uid = ?', (uid,))
|
|
|
row = await cursor.fetchone()
|
|
|
_log.info('update uid=%s', uid)
|
|
|
return self._to_record(row)
|
|
|
@@ -116,9 +127,6 @@ class SQLiteBackend:
|
|
|
async def delete(self, uid: str) -> None:
|
|
|
async with self._connect() as db:
|
|
|
await db.execute(_CREATE_TABLE)
|
|
|
- await db.execute(
|
|
|
- 'DELETE FROM events WHERE uid = ? AND calendar_name = ?',
|
|
|
- (uid, self._calendar_name),
|
|
|
- )
|
|
|
+ await db.execute('DELETE FROM events WHERE uid = ?', (uid,))
|
|
|
await db.commit()
|
|
|
_log.info('delete uid=%s', uid)
|