import copy
from abc import ABC, abstractmethod
from typing import Union
from sqlalchemy import (
ColumnCollection,
Select,
Table,
and_,
between,
case,
distinct,
except_,
except_all,
exists,
func,
intersect,
intersect_all,
join,
lateral,
literal,
not_,
or_,
outerjoin,
union,
union_all,
)
from chainalysis.exceptions import ValueException
from chainalysis.sql.analytical import Analytical
from chainalysis.sql.transactional import Transactional
__all__ = [
"Select",
"and_",
"or_",
"not_",
"case",
"func",
"literal",
"between",
"exists",
"distinct",
"except_",
"except_all",
"intersect",
"intersect_all",
"join",
"outerjoin",
"lateral",
"union",
"union_all",
]
[docs]
class Select(Select, ABC):
"""
Select is an abstract base class that represents an interface
for querying a chain-specific table within the Data Solutions system.
It provides the structure for constructing and executing SQL queries
using Common Table Expressions. Child classes must implement methods to retrieve
tables and execute queries, which will return the appropriate result type.
"""
[docs]
def __init__(self, api_key: str, chain_table_name: str):
"""
Initialize the Select object.
:param api_key: The API key for the Data Solutions API.
:type api_key: str
:param chain_table_name: The chain and table name formatted as 'chain.table'.
:type chain_table_name: str
:raises ValueException: If the table name is not formatted correctly.
"""
chain_table_name_split = chain_table_name.split(".")
if len(chain_table_name_split) != 2:
raise ValueException(
"Table must be formatted as 'chain.table'. Check your input."
)
self.api_key = api_key
self.table = self.get_table(
chain_table_name_split[0], chain_table_name_split[1]
)
super().__init__(self.table)
@property
def c(self) -> ColumnCollection:
"""
Return the columns of the table.
:return: A collection of column objects associated with the table.
:rtype: ColumnCollection
"""
return self.table.c
[docs]
def select(self, *columns) -> "Select":
"""
Select specific columns from the table for querying.
:param columns: The columns to be selected.
:type columns: Column
:return: The Select instance with the selected columns.
:rtype: Select
"""
super().__init__(*columns)
return copy.copy(self)
[docs]
def sql(self) -> str:
"""
Compile the query into a raw SQL string.
:return: The compiled SQL query as a string.
:rtype: str
"""
return self.compile(compile_kwargs={"literal_binds": True}).string
[docs]
@abstractmethod
def get_table(self, chain, table: str) -> Table:
"""
Return a SQLAlchemy Table object for the given chain and table name.
:param chain: The chain name.
:param table: The table name.
:return: A SQLAlchemy Table object.
:rtype: Table
:raises ValueException: If the chain or table does not exist in the database.
"""
raise NotImplementedError(
"Get schemas method must be implemented by child classes."
)
[docs]
@abstractmethod
def execute(self) -> Union[Transactional, Analytical]:
"""
Execute the query and return a Transactional or Analytical object.
:return: Transactional or Analytical object.
:rtype: Union[Transactional, Analytical]
"""
raise NotImplementedError(
"Execute method must be implemented by child classes."
)