Files
Nodecast/server/db/liveProjectionSchema.js
blomios 8b1daea23c fix(live): bound live channel listing queries
Optimize the live channel page path so unfiltered catalog pages are counted and paged in SQLite before serialization and enrichment.

Add the T42 CPU regression test covering a 50000-channel projected catalog.

Refs #42

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-10-08 17:17:16 +02:00

102 lines
3.6 KiB
JavaScript

const LIVE_PROJECTION_COLUMNS = {
logical_channel_id: 'TEXT',
grouping_fingerprint: 'TEXT',
grouping_input_hash: 'TEXT',
quality_label: 'TEXT',
quality_rank: 'INTEGER',
variant_rank: 'INTEGER',
language_code: 'TEXT',
language_confidence: 'REAL',
language_scores: 'JSON',
language_evidence: 'JSON',
language_detector: 'TEXT',
language_input_hash: 'TEXT'
};
const CATALOG_PROJECTION_STATE_COLUMNS = {
phase: "TEXT NOT NULL DEFAULT 'idle'",
processed_count: 'INTEGER NOT NULL DEFAULT 0',
total_count: 'INTEGER NOT NULL DEFAULT 0',
laya_processed_count: 'INTEGER NOT NULL DEFAULT 0',
laya_total_count: 'INTEGER NOT NULL DEFAULT 0'
};
const LIVE_PROJECTION_SCHEMA_SQL = `
CREATE INDEX IF NOT EXISTS idx_items_logical_channel ON playlist_items(logical_channel_id);
CREATE INDEX IF NOT EXISTS idx_items_grouping_fingerprint ON playlist_items(grouping_fingerprint);
CREATE INDEX IF NOT EXISTS idx_items_live_projection_page
ON playlist_items(type, logical_channel_id, language_code, source_id, variant_rank);
CREATE TABLE IF NOT EXISTS logical_channels (
id TEXT PRIMARY KEY,
fingerprint TEXT NOT NULL UNIQUE,
display_name TEXT NOT NULL,
preferred_variant_id TEXT,
variant_count INTEGER NOT NULL DEFAULT 0,
created_at INTEGER NOT NULL,
updated_at INTEGER NOT NULL
);
CREATE INDEX IF NOT EXISTS idx_logical_channels_fingerprint ON logical_channels(fingerprint);
CREATE TABLE IF NOT EXISTS iptv_org_channels (
id TEXT PRIMARY KEY,
name TEXT NOT NULL,
normalized_name TEXT NOT NULL,
alt_names JSON NOT NULL DEFAULT '[]',
country TEXT,
languages JSON NOT NULL DEFAULT '[]',
data JSON,
updated_at INTEGER NOT NULL
);
CREATE INDEX IF NOT EXISTS idx_iptv_org_channels_name ON iptv_org_channels(normalized_name);
CREATE TABLE IF NOT EXISTS iptv_org_cache_state (
resource TEXT PRIMARY KEY,
refreshed_at INTEGER NOT NULL,
item_count INTEGER NOT NULL DEFAULT 0
);
CREATE TABLE IF NOT EXISTS live_epg_matches (
id INTEGER PRIMARY KEY AUTOINCREMENT,
logical_channel_id TEXT NOT NULL,
language_code TEXT NOT NULL,
source_id INTEGER,
channel_id TEXT,
match_confidence REAL NOT NULL DEFAULT 0,
data JSON,
updated_at INTEGER NOT NULL,
UNIQUE (logical_channel_id, language_code)
);
CREATE INDEX IF NOT EXISTS idx_live_epg_matches_channel
ON live_epg_matches(logical_channel_id, language_code);
CREATE TABLE IF NOT EXISTS catalog_projection_state (
projection TEXT PRIMARY KEY,
status TEXT NOT NULL,
started_at INTEGER,
completed_at INTEGER,
input_hash TEXT,
projected_count INTEGER NOT NULL DEFAULT 0,
phase TEXT NOT NULL DEFAULT 'idle',
processed_count INTEGER NOT NULL DEFAULT 0,
total_count INTEGER NOT NULL DEFAULT 0,
laya_processed_count INTEGER NOT NULL DEFAULT 0,
laya_total_count INTEGER NOT NULL DEFAULT 0,
error TEXT
);
`;
function ensureColumns(database, table, columns) {
const existing = new Set(database.prepare(`PRAGMA table_info(${table})`).all().map(column => column.name));
for (const [name, definition] of Object.entries(columns)) {
if (!existing.has(name)) database.exec(`ALTER TABLE ${table} ADD COLUMN ${name} ${definition}`);
}
}
module.exports = {
CATALOG_PROJECTION_STATE_COLUMNS,
ensureColumns,
LIVE_PROJECTION_COLUMNS,
LIVE_PROJECTION_SCHEMA_SQL
};