from __future__ import annotations import csv from dataclasses import dataclass @dataclass(frozen=True) class Column: name: str source_type: str nullable: bool = True precision: int | None = None scale: int | None = None primary_key: bool = False @dataclass(frozen=True, order=True) class TableName: source_schema: str source_table: str @classmethod def from_qualified(cls, value: str) -> TableName: text = value.strip() if not text: raise ValueError("must be a non-empty source table identifier") source_schema, source_table = _parse_qualified_table_name(text) return cls(source_schema=source_schema, source_table=source_table) @property def quoted(self) -> str: return f"`{quote_table_identifier(self.source_schema)}`.`{quote_table_identifier(self.source_table)}`" def __str__(self) -> str: return self.quoted @dataclass(frozen=True) class Table: name: TableName primary_key: tuple[str, ...] columns: tuple[Column, ...] @property def source_schema(self) -> str: return self.name.source_schema @property def source_table(self) -> str: return self.name.source_table @property def quoted(self) -> str: return self.name.quoted def __str__(self) -> str: return self.quoted @property def column_names(self) -> list[str]: return [column.name for column in self.columns] def column(self, name: str) -> Column: for column in self.columns: if column.name == name: return column raise KeyError(name) def quote_table_identifier(value: str) -> str: return value.replace("`", "``") def _parse_qualified_table_name(value: str) -> tuple[str, str]: try: parts = next(csv.reader([value], delimiter=".", quotechar="`", doublequote=True, strict=True)) except csv.Error as exc: raise ValueError("must contain exactly one schema/table separator") from exc if len(parts) != 2: raise ValueError("must contain exactly one schema/table separator") source_schema, source_table = (part.strip() for part in parts) if not source_schema or not source_table: raise ValueError("must contain non-empty schema and table identifiers") if "`" in value and value != TableName(source_schema, source_table).quoted: raise ValueError("quoted identifiers must wrap the complete schema and table identifiers") return source_schema, source_table __all__ = [ "Column", "Table", "TableName", "quote_table_identifier", ]