Skip to content

Commit 362633d

Browse files
committed
SQLite engine and schema definition
1 parent 58c0e28 commit 362633d

1 file changed

Lines changed: 145 additions & 0 deletions

File tree

src/storage/db.py

Lines changed: 145 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,145 @@
1+
"""
2+
db.py
3+
-----
4+
SQLite engine and schema definition for SchedPlus.
5+
6+
This module is responsible for:
7+
- Providing a lazy-loaded SQLite connection
8+
- Enabling foreign key support
9+
- Creating the database schema on first use
10+
11+
It is completely UI-agnostic and intended to be used by the logic layer only.
12+
"""
13+
14+
import sqlite3
15+
from typing import Optional
16+
17+
from storage.paths import get_db_path
18+
19+
_connection: Optional[sqlite3.Connection] = None
20+
21+
22+
def get_connection() -> sqlite3.Connection:
23+
"""
24+
Returns the global SQLite connection for SchedPlus.
25+
26+
The connection is:
27+
- Created lazily on first use
28+
- Configured with foreign key support
29+
- Configured with sqlite3.Row for dict-like access
30+
- Ensures the schema exists before use
31+
"""
32+
global _connection
33+
34+
if _connection is None:
35+
db_path = get_db_path()
36+
_connection = sqlite3.connect(db_path)
37+
_connection.row_factory = sqlite3.Row
38+
39+
# Enable foreign key constraints
40+
_connection.execute("PRAGMA foreign_keys = ON;")
41+
42+
# Ensure schema exists
43+
_create_schema(_connection)
44+
45+
return _connection
46+
47+
48+
def init_db() -> None:
49+
"""
50+
Initializes the database by ensuring the connection is created
51+
and the schema exists.
52+
53+
This can be called at startup to guarantee the DB is ready.
54+
"""
55+
_ = get_connection()
56+
57+
58+
def _create_schema(conn: sqlite3.Connection) -> None:
59+
"""
60+
Creates the database schema if it does not already exist.
61+
62+
Tables:
63+
- entries
64+
- comments
65+
- tags
66+
- entry_tags (many-to-many)
67+
- recurrence_rules
68+
- metadata
69+
"""
70+
cursor = conn.cursor()
71+
72+
# entries: core tasks
73+
cursor.execute(
74+
"""
75+
CREATE TABLE IF NOT EXISTS entries (
76+
id INTEGER PRIMARY KEY AUTOINCREMENT,
77+
title TEXT NOT NULL,
78+
description TEXT,
79+
due_date TEXT,
80+
completed INTEGER NOT NULL DEFAULT 0,
81+
created_at TEXT NOT NULL,
82+
updated_at TEXT NOT NULL
83+
);
84+
"""
85+
)
86+
87+
# comments: notes attached to entries
88+
cursor.execute(
89+
"""
90+
CREATE TABLE IF NOT EXISTS comments (
91+
id INTEGER PRIMARY KEY AUTOINCREMENT,
92+
entry_id INTEGER NOT NULL,
93+
text TEXT NOT NULL,
94+
created_at TEXT NOT NULL,
95+
FOREIGN KEY(entry_id) REFERENCES entries(id) ON DELETE CASCADE
96+
);
97+
"""
98+
)
99+
100+
# tags: reusable labels
101+
cursor.execute(
102+
"""
103+
CREATE TABLE IF NOT EXISTS tags (
104+
id INTEGER PRIMARY KEY AUTOINCREMENT,
105+
name TEXT NOT NULL UNIQUE
106+
);
107+
"""
108+
)
109+
110+
# entry_tags: many-to-many between entries and tags
111+
cursor.execute(
112+
"""
113+
CREATE TABLE IF NOT EXISTS entry_tags (
114+
entry_id INTEGER NOT NULL,
115+
tag_id INTEGER NOT NULL,
116+
PRIMARY KEY (entry_id, tag_id),
117+
FOREIGN KEY(entry_id) REFERENCES entries(id) ON DELETE CASCADE,
118+
FOREIGN KEY(tag_id) REFERENCES tags(id) ON DELETE CASCADE
119+
);
120+
"""
121+
)
122+
123+
# recurrence_rules: future recurrence logic
124+
cursor.execute(
125+
"""
126+
CREATE TABLE IF NOT EXISTS recurrence_rules (
127+
id INTEGER PRIMARY KEY AUTOINCREMENT,
128+
entry_id INTEGER NOT NULL,
129+
rule TEXT NOT NULL,
130+
FOREIGN KEY(entry_id) REFERENCES entries(id) ON DELETE CASCADE
131+
);
132+
"""
133+
)
134+
135+
# metadata: migration/versioning info
136+
cursor.execute(
137+
"""
138+
CREATE TABLE IF NOT EXISTS metadata (
139+
key TEXT PRIMARY KEY,
140+
value TEXT
141+
);
142+
"""
143+
)
144+
145+
conn.commit()

0 commit comments

Comments
 (0)