Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
51 changes: 33 additions & 18 deletions web/server/codechecker_server/database/run_db_model.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,9 @@
import os
from typing import Optional

from sqlalchemy import Boolean, Column, DateTime, Enum, ForeignKey, Integer, \
LargeBinary, MetaData, String, UniqueConstraint, Table, Text, JSON
from sqlalchemy import BigInteger, Boolean, Column, DateTime, Enum, \
ForeignKey, Integer, LargeBinary, MetaData, String, UniqueConstraint, \
Table, Text, JSON
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import relationship
from sqlalchemy.sql.expression import true, false
Expand Down Expand Up @@ -185,8 +186,15 @@ def __init__(self, run_history_id, analyzer_type, version, successful,
deferrable=True,
initially="DEFERRED",
ondelete="CASCADE"),
index=True),
Column('analysis_info_id', Integer, ForeignKey('analysis_info.id'))
nullable=False,
index=True,
primary_key=True),
Column(
'analysis_info_id',
Integer,
ForeignKey('analysis_info.id'),
nullable=False,
primary_key=True)
)


Expand Down Expand Up @@ -277,9 +285,9 @@ class BugPathEvent(Base):
file_id = Column(Integer, ForeignKey('files.id', deferrable=True,
initially="DEFERRED",
ondelete='CASCADE'), index=True)
report_id = Column(Integer, ForeignKey('reports.id', deferrable=True,
initially="DEFERRED",
ondelete='CASCADE'),
report_id = Column(BigInteger, ForeignKey('reports.id', deferrable=True,
initially="DEFERRED",
ondelete='CASCADE'),
index=True,
primary_key=True)

Expand Down Expand Up @@ -307,9 +315,9 @@ class BugReportPoint(Base):
file_id = Column(Integer, ForeignKey('files.id', deferrable=True,
initially="DEFERRED",
ondelete='CASCADE'), index=True)
report_id = Column(Integer, ForeignKey('reports.id', deferrable=True,
initially="DEFERRED",
ondelete='CASCADE'),
report_id = Column(BigInteger, ForeignKey('reports.id', deferrable=True,
initially="DEFERRED",
ondelete='CASCADE'),
index=True,
primary_key=True)

Expand All @@ -331,9 +339,9 @@ class ExtendedReportData(Base):

id = Column(Integer, autoincrement=True, primary_key=True)

report_id = Column(Integer, ForeignKey('reports.id', deferrable=True,
initially="DEFERRED",
ondelete='CASCADE'),
report_id = Column(BigInteger, ForeignKey('reports.id', deferrable=True,
initially="DEFERRED",
ondelete='CASCADE'),
index=True)

file_id = Column(Integer, ForeignKey('files.id', deferrable=True,
Expand Down Expand Up @@ -370,13 +378,20 @@ def __init__(self, line_begin, col_begin, line_end, col_end,
Base.metadata,
Column(
'report_id',
Integer,
BigInteger,
ForeignKey('reports.id',
deferrable=True,
initially="DEFERRED",
ondelete="CASCADE"),
index=True),
Column('analysis_info_id', Integer, ForeignKey('analysis_info.id'))
nullable=False,
index=True,
primary_key=True),
Column(
'analysis_info_id',
Integer,
ForeignKey('analysis_info.id'),
nullable=False,
primary_key=True)
)


Expand All @@ -391,7 +406,7 @@ def __init__(self, line_begin, col_begin, line_end, col_end,
class Report(Base):
__tablename__ = 'reports'

id = Column(Integer, autoincrement=True, primary_key=True)
id = Column(BigInteger, autoincrement=True, primary_key=True)
file_id = Column(Integer, ForeignKey('files.id', deferrable=True,
initially="DEFERRED",
ondelete='CASCADE'),
Expand Down Expand Up @@ -497,7 +512,7 @@ def __init__(self, report_id: int, key: str, value: str):
self.value = value

report_id = Column(
Integer,
BigInteger,
ForeignKey("reports.id", ondelete="CASCADE"),
primary_key=True,
index=True)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,196 @@
"""
Report id bigint

Revision ID: a7f3c8e21b90
Revises: 24c9660f82b1
Create Date: 2026-06-30 00:00:00.000000
"""

from alembic import op
import sqlalchemy as sa


# Revision identifiers, used by Alembic.
revision = 'a7f3c8e21b90'
down_revision = '24c9660f82b1'
branch_labels = None
depends_on = None


REPORT_ID_FK_TABLES = [
'bug_path_events',
'bug_report_points',
'extended_report_data',
'report_analysis_info',
'report_annotations',
]

ASSOCIATION_TABLES = [
{
'name': 'report_analysis_info',
'pk_name': 'pk_report_analysis_info',
'columns': ('report_id', 'analysis_info_id'),
},
{
'name': 'run_history_analysis_info',
'pk_name': 'pk_run_history_analysis_info',
'columns': ('run_history_id', 'analysis_info_id'),
},
]


def _alter_report_id_columns_upgrade(type_, existing_type):
for table in REPORT_ID_FK_TABLES:
op.alter_column(
table,
'report_id',
existing_type=existing_type,
type_=type_,
existing_nullable=(table == 'report_analysis_info'),
nullable=False)


def _alter_report_id_columns_downgrade(type_, existing_type):
for table in REPORT_ID_FK_TABLES:
op.alter_column(
table,
'report_id',
existing_type=existing_type,
type_=type_,
existing_nullable=False,
nullable=(table == 'report_analysis_info'))


def _cleanup_association_tables(dialect):
for table_info in ASSOCIATION_TABLES:
table = table_info['name']
columns = table_info['columns']
null_condition = " OR ".join(f"{column} IS NULL" for column in columns)
op.execute(sa.text(
f"DELETE FROM {table} WHERE {null_condition}"))

col_a = " AND ".join(
f"a.{column} IS NOT DISTINCT FROM b.{column}"
for column in columns)
if dialect == 'postgresql':
op.execute(sa.text(f"""
DELETE FROM {table} AS a
USING {table} AS b
WHERE a.ctid < b.ctid
AND {col_a}
"""))
else:
grouped_columns = ", ".join(columns)
op.execute(sa.text(f"""
DELETE FROM {table}
WHERE rowid NOT IN (
SELECT MIN(rowid)
FROM {table}
GROUP BY {grouped_columns}
)
"""))


def _tighten_association_tables_postgresql():
op.alter_column(
'run_history_analysis_info',
'run_history_id',
existing_type=sa.Integer(),
nullable=False)
for table_info in ASSOCIATION_TABLES:
op.alter_column(
table_info['name'],
'analysis_info_id',
existing_type=sa.Integer(),
nullable=False)

op.create_primary_key(
'pk_report_analysis_info',
'report_analysis_info',
['report_id', 'analysis_info_id'])
op.create_primary_key(
'pk_run_history_analysis_info',
'run_history_analysis_info',
['run_history_id', 'analysis_info_id'])


def _tighten_association_tables_sqlite():
for table_info in ASSOCIATION_TABLES:
with op.batch_alter_table(table_info['name']) as batch_op:
for column in table_info['columns']:
batch_op.alter_column(column, nullable=False)
batch_op.create_primary_key(
table_info['pk_name'],
list(table_info['columns']))


def _relax_association_tables_postgresql():
op.drop_constraint(
'pk_run_history_analysis_info',
'run_history_analysis_info',
type_='primary')
op.drop_constraint(
'pk_report_analysis_info',
'report_analysis_info',
type_='primary')

op.alter_column(
'run_history_analysis_info',
'run_history_id',
existing_type=sa.Integer(),
nullable=True)
for table_info in ASSOCIATION_TABLES:
op.alter_column(
table_info['name'],
'analysis_info_id',
existing_type=sa.Integer(),
nullable=True)


def _relax_association_tables_sqlite():
for table_info in ASSOCIATION_TABLES:
with op.batch_alter_table(table_info['name']) as batch_op:
batch_op.drop_constraint(table_info['pk_name'], type_='primary')
for column in table_info['columns']:
batch_op.alter_column(column, nullable=True)


def upgrade():
ctx = op.get_context()
dialect = ctx.dialect.name

_cleanup_association_tables(dialect)

if dialect == 'postgresql':
_alter_report_id_columns_upgrade(sa.BigInteger(), sa.Integer())
op.alter_column(
'reports',
'id',
existing_type=sa.Integer(),
type_=sa.BigInteger(),
existing_nullable=False,
autoincrement=True)
op.execute(sa.text('ALTER SEQUENCE reports_id_seq AS BIGINT'))
_tighten_association_tables_postgresql()
elif dialect == 'sqlite':
# SQLite INTEGER columns already store 64-bit signed integers.
_tighten_association_tables_sqlite()


def downgrade():
ctx = op.get_context()
dialect = ctx.dialect.name

if dialect == 'postgresql':
_relax_association_tables_postgresql()
_alter_report_id_columns_downgrade(sa.Integer(), sa.BigInteger())
op.alter_column(
'reports',
'id',
existing_type=sa.BigInteger(),
type_=sa.Integer(),
existing_nullable=False,
autoincrement=True)
op.execute(sa.text('ALTER SEQUENCE reports_id_seq AS INTEGER'))
elif dialect == 'sqlite':
_relax_association_tables_sqlite()
Loading