"""printers: printerobservedqueues table (what a bay reported it HAS). ShopDB already knows what a bay SHOULD have (usesprinter/defaultprinter rows on the machine). This table holds the other half: the queues a PC reported through POST /api/collector/printers. Observed and assigned stay in separate tables on purpose, so observed drift can never be mistaken for desired state. One row per observed queue; the latest report for a host replaces all of that host's rows. The unique index on (hostname, queuename) is the guard that turns a half-finished replace into an IntegrityError instead of duplicate queues. Explicit ops rather than create_plugin_tables: the helper builds a per-plugin MetaData filtered to the plugin's own tables, so the foreign key to the core assets table cannot resolve at CreateTable-compile time. Same reason the backups baseline spells its ops out. Guarded both ways, so a re-run (or a database where db.create_all already built the table) is a no-op. Revision ID: printers0005observedqueues Revises: printers0004drivervendor """ from alembic import op import sqlalchemy as sa # revision identifiers, used by Alembic. revision = 'printers0005observedqueues' down_revision = 'printers0004drivervendor' branch_labels = None depends_on = None def upgrade(): bind = op.get_bind() inspector = sa.inspect(bind) if 'printerobservedqueues' in inspector.get_table_names(): return op.create_table( 'printerobservedqueues', sa.Column('printerobservedqueueid', sa.Integer(), nullable=False), # Nullable and no index of its own beyond the explicit one below: an # unenrolled bay still gets to report, and hostname is what identifies # the report. sa.Column('assetid', sa.Integer(), nullable=True), sa.Column('hostname', sa.String(length=255), nullable=False), sa.Column('queuename', sa.String(length=255), nullable=False), sa.Column('drivername', sa.String(length=255), nullable=True), sa.Column('portname', sa.String(length=255), nullable=True), sa.Column('portaddress', sa.String(length=255), nullable=True), sa.Column('isdefault', sa.Boolean(), nullable=False, server_default=sa.false()), sa.Column('isshared', sa.Boolean(), nullable=False, server_default=sa.false()), sa.Column('observedat', sa.DateTime(), nullable=False), sa.Column('createddate', sa.DateTime(), nullable=False), sa.Column('modifieddate', sa.DateTime(), nullable=False), sa.Column('isactive', sa.Boolean(), nullable=False, server_default=sa.true()), sa.ForeignKeyConstraint(['assetid'], ['assets.assetid'], ondelete='CASCADE'), sa.PrimaryKeyConstraint('printerobservedqueueid'), # Doubles as the read index for the hostname filter (leftmost column), # so no separate hostname index is created. sa.UniqueConstraint('hostname', 'queuename', name='uq_printerobservedqueue_host_queue'), ) # Port address is the primary match key from an observed queue back to a # printer asset, so every comparison read hits it. op.create_index('idx_printerobservedqueues_portaddress', 'printerobservedqueues', ['portaddress']) op.create_index('idx_printerobservedqueues_assetid', 'printerobservedqueues', ['assetid']) def downgrade(): bind = op.get_bind() inspector = sa.inspect(bind) if 'printerobservedqueues' not in inspector.get_table_names(): return op.drop_index('idx_printerobservedqueues_assetid', table_name='printerobservedqueues') op.drop_index('idx_printerobservedqueues_portaddress', table_name='printerobservedqueues') op.drop_table('printerobservedqueues')