trackdirect2/server/trackdirect/database/PacketTableCreator.py

124 lines
4.9 KiB
Python

import logging
import psycopg2
import psycopg2.extras
import datetime
import time
import calendar
from server.trackdirect.database.DatabaseObjectFinder import DatabaseObjectFinder
from server.trackdirect.exceptions.TrackDirectMissingTableError import TrackDirectMissingTableError
class PacketTableCreator:
"""The PacketTableCreator class handles packet table name logic.
Note:
Packets are stored in different tables depending on what day they are received,
new packet tables are created by this class.
"""
def __init__(self, db: psycopg2.extensions.connection):
"""The __init__ method.
Args:
db (psycopg2.extensions.connection): Database connection
"""
self.db = db
self.dbObjectFinder = DatabaseObjectFinder(db)
self.logger = logging.getLogger('trackdirect')
self.createIfMissing = True
def disable_create_if_missing(self) -> None:
"""Disable feature that creates new tables if missing."""
self.createIfMissing = False
def enable_create_if_missing(self) -> None:
"""Enable feature that creates new tables if missing."""
self.createIfMissing = True
def get_table(self, timestamp: int) -> str:
"""Returns the name of the packet table.
Args:
timestamp (int): Unix timestamp that we need the table for
Returns:
str: The name of the packet table
"""
date = datetime.datetime.utcfromtimestamp(timestamp).strftime('%Y%m%d')
packet_table = f'packet{date}'
if not self.dbObjectFinder.check_table_exists(packet_table):
if self.createIfMissing:
min_timestamp = timestamp // (24 * 60 * 60) * (24 * 60 * 60)
max_timestamp = min_timestamp + (24 * 60 * 60)
self._create_packet_table(packet_table, min_timestamp, max_timestamp)
self.dbObjectFinder.set_table_exists(packet_table)
else:
raise TrackDirectMissingTableError(f'Database table {packet_table} does not exist')
return packet_table
def get_tables(self, start_timestamp: int, end_timestamp: int = None) -> list[str]:
"""Returns an array of packet table names based on the specified timestamp range.
Note:
If table does not exist we will not include the packet table name in array.
Args:
start_timestamp (int): Start unix timestamp for requested packet tables
end_timestamp (int): End unix timestamp for requested packet tables
Returns:
list[str]: Array of packet table names
"""
if end_timestamp is None:
end_timestamp = int(time.time())
end_date_time = datetime.datetime.utcfromtimestamp(end_timestamp)
end_date_time = end_date_time.replace(hour=0, minute=0, second=0, microsecond=0) + datetime.timedelta(days=1)
end_timestamp = calendar.timegm(end_date_time.timetuple())
result = []
ts = start_timestamp if start_timestamp is not None and start_timestamp != 0 else int(time.time()) - (60 * 60 * 24 * 366)
while ts < end_timestamp:
date = datetime.datetime.utcfromtimestamp(ts).strftime('%Y%m%d')
date_packet_table = f'packet{date}'
if self.dbObjectFinder.check_table_exists(date_packet_table):
result.append(date_packet_table)
ts += 86400 # 1 day in seconds
return result
def _create_packet_table(self, table_name: str, min_timestamp: int, max_timestamp: int) -> None:
"""Create a packet table with the specified name.
Args:
table_name (str): Name of the packet table to create
min_timestamp (int): Min Unix timestamp for this table
max_timestamp (int): Max Unix timestamp for this table
"""
try:
cur = self.db.cursor()
sql_statements = [
f"CREATE TABLE {table_name} () INHERITS (packet)",
f"ALTER TABLE {table_name} ADD CONSTRAINT timestamp_range_check CHECK (timestamp >= {min_timestamp} AND timestamp < {max_timestamp})",
f"CREATE INDEX {table_name}_pkey ON {table_name} USING btree (id)",
f"CREATE INDEX {table_name}_station_id_idx ON {table_name} (station_id, map_id, marker_id, timestamp)",
f"CREATE INDEX {table_name}_map_sector_idx ON {table_name} (map_sector, timestamp, map_id)",
f"CREATE INDEX {table_name}_sender_id_idx ON {table_name} (sender_id)"
]
for sql in sql_statements:
cur.execute(sql)
cur.close()
except (psycopg2.IntegrityError, psycopg2.ProgrammingError) as e:
if 'already exists' not in str(e):
self.logger.error(e, exc_info=1)
time.sleep(10)
except Exception as e:
self.logger.error(e, exc_info=1)
time.sleep(10)