Legacy Database Schema (Preserved)
February 11, 2026 ยท View on GitHub
This document preserves the legacy schema snapshot for migration development and validation. SQL DDL is the single source of truth for table/column definitions.
Full DDL (Authoritative)
CREATE TABLE `groups` (
`id` integer PRIMARY KEY AUTOINCREMENT,
`created_at` datetime,
`updated_at` datetime,
`deleted_at` integer,
`name` text NOT NULL
);
CREATE TABLE sqlite_sequence(name,seq);
CREATE UNIQUE INDEX `idx_name` ON `groups`(`deleted_at`,`name`);
CREATE TABLE `feeds` (
`id` integer PRIMARY KEY AUTOINCREMENT,
`created_at` datetime,
`updated_at` datetime,
`deleted_at` integer,
`name` text NOT NULL,
`link` text NOT NULL,
`last_build` datetime,
`failure` text DEFAULT "",
`group_id` integer,
`suspended` numeric DEFAULT false,
`req_proxy` text,
`consecutive_failures` integer DEFAULT 0,
CONSTRAINT `fk_feeds_group` FOREIGN KEY (`group_id`) REFERENCES `groups`(`id`)
);
CREATE TABLE `items` (
`id` integer PRIMARY KEY AUTOINCREMENT,
`created_at` datetime,
`updated_at` datetime,
`deleted_at` integer,
`title` text NOT NULL,
`guid` text,
`link` text,
`content` text,
`pub_date` datetime,
`unread` numeric DEFAULT true,
`feed_id` integer,
`bookmark` numeric DEFAULT false,
CONSTRAINT `fk_items_feed` FOREIGN KEY (`feed_id`) REFERENCES `feeds`(`id`)
);
CREATE INDEX `idx_items_feed_id` ON `items`(`feed_id`);
CREATE INDEX `idx_items_title` ON `items`(`title`);
CREATE INDEX `idx_items_unread` ON `items`(`unread`);
CREATE INDEX `idx_items_bookmark` ON `items`(`bookmark`);
CREATE UNIQUE INDEX `idx_guid` ON `items`(`deleted_at`,`guid`,`feed_id`);
CREATE UNIQUE INDEX `idx_link` ON `feeds`(`deleted_at`,`link`);
Business Logic Notes
1) Entity relationships
groups -> feeds -> itemsis a two-level one-to-many chain.feeds.group_idlinks each feed to a group.items.feed_idlinks each item to a feed.- Legacy schema declares foreign keys (
fk_feeds_group,fk_items_feed).
2) Soft-delete model
- All business tables use
deleted_at(Unix timestamp) for logical delete. - Deletes update
deleted_at; rows are not physically removed. - Unique indexes include
deleted_at, so deleted historical rows can coexist with new active rows.
3) Deduplication and uniqueness semantics
- Group name uniqueness:
idx_name(deleted_at, name). - Feed link uniqueness:
idx_link(deleted_at, link). - Item deduplication key:
idx_guid(deleted_at, guid, feed_id). - SQLite caveat:
NULL != NULLin unique indexes, so multiple rows withguid=NULLare allowed (must be preserved in migrations).
4) Feed pull status fields (feeds)
last_build: last content update time.failure+consecutive_failures: latest error and failure streak.suspended: pull disabled flag.req_proxy: per-feed proxy setting.
5) Reading state fields (items)
unread: unread flag (defaulttrue).bookmark: bookmark flag (defaultfalse).- Both are indexed for high-frequency filtering.
6) Application-level semantics
- App bootstrap ensures default group exists:
id=1,name="Default". unread_countis a virtual field in app models and is not persisted.
Legacy Migration History
After v0.8.7
- Added unique constraint for feed links via
idx_link. - Migration removed duplicate feeds and kept the row with the smallest
id.