2026-01-30 11:05:00 +08:00
|
|
|
import { logger } from '../utils/logger.js';
|
|
|
|
|
import dbManager from './databaseManager.js';
|
|
|
|
|
|
2026-03-03 18:22:17 +08:00
|
|
|
const PARENT_TABLE = 'rcu_action.rcu_action_events';
|
|
|
|
|
const PARENT_INDEX_STATEMENTS = [
|
|
|
|
|
'CREATE INDEX IF NOT EXISTS idx_rcu_action_hotel_id ON rcu_action.rcu_action_events (hotel_id);',
|
|
|
|
|
'CREATE INDEX IF NOT EXISTS idx_rcu_action_room_id ON rcu_action.rcu_action_events (room_id);',
|
|
|
|
|
'CREATE INDEX IF NOT EXISTS idx_rcu_action_device_id ON rcu_action.rcu_action_events (device_id);',
|
|
|
|
|
'CREATE INDEX IF NOT EXISTS idx_rcu_action_direction ON rcu_action.rcu_action_events (direction);',
|
|
|
|
|
'CREATE INDEX IF NOT EXISTS idx_rcu_action_cmd_word ON rcu_action.rcu_action_events (cmd_word);',
|
|
|
|
|
'CREATE INDEX IF NOT EXISTS idx_rcu_action_action_type ON rcu_action.rcu_action_events (action_type);',
|
|
|
|
|
'CREATE INDEX IF NOT EXISTS idx_rcu_action_query_main ON rcu_action.rcu_action_events (hotel_id, room_id, ts_ms DESC);'
|
|
|
|
|
];
|
|
|
|
|
|
2026-01-30 11:05:00 +08:00
|
|
|
class PartitionManager {
|
2026-03-03 18:22:17 +08:00
|
|
|
async ensureParentIndexes(client) {
|
|
|
|
|
for (const sql of PARENT_INDEX_STATEMENTS) {
|
|
|
|
|
await client.query(sql);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-01-30 11:05:00 +08:00
|
|
|
/**
|
|
|
|
|
* Calculate the start and end timestamps (milliseconds) for a given date.
|
|
|
|
|
* @param {Date} date - The date to calculate for.
|
|
|
|
|
* @returns {Object} { startMs, endMs, partitionSuffix }
|
|
|
|
|
*/
|
|
|
|
|
getPartitionInfo(date) {
|
|
|
|
|
const yyyy = date.getFullYear();
|
|
|
|
|
const mm = String(date.getMonth() + 1).padStart(2, '0');
|
|
|
|
|
const dd = String(date.getDate()).padStart(2, '0');
|
|
|
|
|
const partitionSuffix = `${yyyy}${mm}${dd}`;
|
|
|
|
|
|
|
|
|
|
const start = new Date(date);
|
|
|
|
|
start.setHours(0, 0, 0, 0);
|
|
|
|
|
const startMs = start.getTime();
|
|
|
|
|
|
|
|
|
|
const end = new Date(date);
|
|
|
|
|
end.setDate(end.getDate() + 1);
|
|
|
|
|
end.setHours(0, 0, 0, 0);
|
|
|
|
|
const endMs = end.getTime();
|
|
|
|
|
|
|
|
|
|
return { startMs, endMs, partitionSuffix };
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Ensure partitions exist for the next N days.
|
|
|
|
|
* @param {number} daysAhead - Number of days to pre-create.
|
|
|
|
|
*/
|
|
|
|
|
async ensurePartitions(daysAhead = 30) {
|
|
|
|
|
const client = await dbManager.pool.connect();
|
|
|
|
|
try {
|
|
|
|
|
logger.info(`Starting partition check for the next ${daysAhead} days...`);
|
2026-03-03 18:22:17 +08:00
|
|
|
await this.ensureParentIndexes(client);
|
2026-01-30 11:05:00 +08:00
|
|
|
const now = new Date();
|
|
|
|
|
|
|
|
|
|
for (let i = 0; i < daysAhead; i++) {
|
|
|
|
|
const targetDate = new Date(now);
|
|
|
|
|
targetDate.setDate(now.getDate() + i);
|
|
|
|
|
|
|
|
|
|
const { startMs, endMs, partitionSuffix } = this.getPartitionInfo(targetDate);
|
|
|
|
|
const partitionName = `rcu_action.rcu_action_events_${partitionSuffix}`;
|
|
|
|
|
|
|
|
|
|
// Check if partition exists
|
|
|
|
|
const checkSql = `
|
|
|
|
|
SELECT to_regclass($1) as exists;
|
|
|
|
|
`;
|
|
|
|
|
const checkRes = await client.query(checkSql, [partitionName]);
|
|
|
|
|
|
|
|
|
|
if (!checkRes.rows[0].exists) {
|
|
|
|
|
logger.info(`Creating partition ${partitionName} for range [${startMs}, ${endMs})`);
|
|
|
|
|
const createSql = `
|
|
|
|
|
CREATE TABLE IF NOT EXISTS ${partitionName}
|
2026-03-03 18:22:17 +08:00
|
|
|
PARTITION OF ${PARENT_TABLE}
|
2026-01-30 11:05:00 +08:00
|
|
|
FOR VALUES FROM (${startMs}) TO (${endMs});
|
|
|
|
|
`;
|
|
|
|
|
await client.query(createSql);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
logger.info('Partition check completed.');
|
|
|
|
|
} catch (err) {
|
|
|
|
|
logger.error('Error ensuring partitions:', err);
|
|
|
|
|
throw err;
|
|
|
|
|
} finally {
|
|
|
|
|
client.release();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export default new PartitionManager();
|