Skip to content

statement

Module to define a SQL Statement

Statement

Bases: BaseModel

SQL Statment class

Source code in xerini/statement.py
class Statement(BaseModel):
    """SQL Statment class"""

    text: str
    _parsed_text: Optional[Parser] = None
    _query_type: Optional[QueryType] = None

    @model_validator(mode="before")
    @classmethod
    def only_one_statement(cls, data: dict):
        """Model validator to make sure that there is only a single sql statement in text"""
        if not 0 < meaningful_strings_count(data["text"]) < 2:
            raise ValueError(
                f"Expected a string containing a single SQL statement, but {data['text']=} !"
            )
        return data

    @property
    def formatted_text(self) -> str:
        """Returns sqlparse.formatted text"""
        return sqlparse.format(
            self.text,
            reindent=True,
            keyword_case="upper",
            strip_comments=True,
            comma_first=True,
            indent_tabs=True,
        )

    @model_validator(mode="after")
    def set_parsed_text(self) -> Self:
        """Model validator to set the parsed text"""
        self._parsed_text = Parser(self.formatted_text)
        return self

    @model_validator(mode="after")
    def set_query_type(self) -> Self:
        """Model validator to fix the augmented via the supported_query_types.py query type"""
        self._query_type = None
        try:
            self._query_type = self.parsed_text.query_type
        except ValueError as _ve:
            pass
        return self

    @property
    def query_type(self) -> QueryType | None:
        """The augmented via the supported_query_types.py query type"""
        return self._query_type

    @property
    def parsed_text(self) -> Parser:
        """Parsed_text property"""
        return self._parsed_text

    def __str__(self) -> str:
        """Returns a string for printing"""
        return self.text

    @property
    def statement_type(self) -> StatementType:
        """Whether the statement is a views, updates, or transfers data"""
        return StatementType.from_query_type(self.query_type)

    @property
    def write_type(self) -> TableWriteType:
        """Whether the statement builds, updates, or temporarily write"""
        return TableWriteType.from_statement_query(self.statement_type, self.query_type)

    @property
    def tables(self) -> Set[Optional[str]]:
        """Tables integral to the statement"""
        ans: Set = set()
        if self.query_type is not None:
            ans = set(self.parsed_text.tables)
        return ans

    @property
    def non_empty_tokens(self) -> list[sqlparse.sql.Token]:
        """From the sql_metadata Parser"""
        ans = []
        if self.query_type is None:
            pass
        else:
            ans = self.parsed_text.non_empty_tokens
        return ans

    @property
    def normalized_successive_token_pairs(self) -> list[tuple[str, str]]:
        """Needed to get to the affected tables"""
        return [
            (sp[0].normalized, sp[1].normalized)
            for sp in successive_pairs(self.parsed_text.tokens)
        ]

    @property
    def affected_table(self) -> str | None:
        """Where the data is going, according to the statement"""
        ans = None
        if self.query_type in {QueryType.TRUNCATE, QueryType.DROP}:
            ans = list(self.tables)[0]
        elif self.query_type == QueryType.UPDATE:
            ans = [
                tbl
                for tbl in self.tables
                if tbl
                and ("UPDATE", tbl.upper()) in self.normalized_successive_token_pairs
            ][0]
        elif ("DELETE", "FROM") in self.normalized_successive_token_pairs:
            ans = following_pairs_second_item(
                self.normalized_successive_token_pairs, ("DELETE", "FROM")
            )
        elif ("CREATE", "TABLE") in self.normalized_successive_token_pairs:
            ans = following_pairs_second_item(
                self.normalized_successive_token_pairs, ("CREATE", "TABLE")
            )
        elif ("TEMP", "TABLE") in self.normalized_successive_token_pairs:
            ans = following_pairs_second_item(
                self.normalized_successive_token_pairs, ("TEMP", "TABLE")
            )
        elif ("TEMPORARY", "TABLE") in self.normalized_successive_token_pairs:
            ans = following_pairs_second_item(
                self.normalized_successive_token_pairs, ("TEMPORARY", "TABLE")
            )
        elif ("INSERT", "INTO") in self.normalized_successive_token_pairs:
            ans = following_pairs_second_item(
                self.normalized_successive_token_pairs, ("INSERT", "INTO")
            )
        else:
            pass
        return ans.lower() if ans is not None else None

    def what_are_we_truncating_dropping(self) -> str | None:
        """We need to specify the destination table when we truncate"""
        if not self.query_type in {QueryType.TRUNCATE, QueryType.DROP}:
            raise TypeError(f"This method is unsupported for {self.query_type}!")
        return self.affected_table

    @property
    def source_tables(self) -> Set[Optional[str]]:
        """Tables from which the data is being sourced, according to the statement"""
        sources: Set = set()
        if self.statement_type in {StatementType.UPDATE, StatementType.VIEW}:
            if self.query_type in {QueryType.TRUNCATE, QueryType.DROP}:
                pass
            elif self.query_type == QueryType.DELETE:
                sources = self.tables - {
                    self.affected_table,
                }
            else:
                sources = self.tables
        else:
            sources = self.tables - {
                self.affected_table,
            }
        return sources

affected_table property

Where the data is going, according to the statement

formatted_text property

Returns sqlparse.formatted text

non_empty_tokens property

From the sql_metadata Parser

normalized_successive_token_pairs property

Needed to get to the affected tables

parsed_text property

Parsed_text property

query_type property

The augmented via the supported_query_types.py query type

source_tables property

Tables from which the data is being sourced, according to the statement

statement_type property

Whether the statement is a views, updates, or transfers data

tables property

Tables integral to the statement

write_type property

Whether the statement builds, updates, or temporarily write

__str__()

Returns a string for printing

Source code in xerini/statement.py
def __str__(self) -> str:
    """Returns a string for printing"""
    return self.text

only_one_statement(data) classmethod

Model validator to make sure that there is only a single sql statement in text

Source code in xerini/statement.py
@model_validator(mode="before")
@classmethod
def only_one_statement(cls, data: dict):
    """Model validator to make sure that there is only a single sql statement in text"""
    if not 0 < meaningful_strings_count(data["text"]) < 2:
        raise ValueError(
            f"Expected a string containing a single SQL statement, but {data['text']=} !"
        )
    return data

set_parsed_text()

Model validator to set the parsed text

Source code in xerini/statement.py
@model_validator(mode="after")
def set_parsed_text(self) -> Self:
    """Model validator to set the parsed text"""
    self._parsed_text = Parser(self.formatted_text)
    return self

set_query_type()

Model validator to fix the augmented via the supported_query_types.py query type

Source code in xerini/statement.py
@model_validator(mode="after")
def set_query_type(self) -> Self:
    """Model validator to fix the augmented via the supported_query_types.py query type"""
    self._query_type = None
    try:
        self._query_type = self.parsed_text.query_type
    except ValueError as _ve:
        pass
    return self

what_are_we_truncating_dropping()

We need to specify the destination table when we truncate

Source code in xerini/statement.py
def what_are_we_truncating_dropping(self) -> str | None:
    """We need to specify the destination table when we truncate"""
    if not self.query_type in {QueryType.TRUNCATE, QueryType.DROP}:
        raise TypeError(f"This method is unsupported for {self.query_type}!")
    return self.affected_table