Skip to content
This repository was archived by the owner on May 17, 2024. It is now read-only.
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
256 changes: 1 addition & 255 deletions data_diff/abcs/database_types.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,10 @@
import decimal
from abc import ABC, abstractmethod
from typing import Sequence, Optional, Tuple, Union, Dict, List
from typing import Tuple, Union
from datetime import datetime

from runtype import dataclass
from typing_extensions import Self

from data_diff.abcs.compiler import AbstractCompiler
from data_diff.utils import ArithAlphanumeric, ArithUUID, Unknown


Expand Down Expand Up @@ -172,255 +170,3 @@ class UnknownColType(ColType):
text: str

supported = False


class AbstractDialect(ABC):
"""Dialect-dependent query expressions"""

@abstractmethod
def compile(self, compiler: AbstractCompiler, elem, params=None) -> str:
raise NotImplementedError

@abstractmethod
def parse_table_name(self, name: str) -> DbPath:
"Parse the given table name into a DbPath"

@property
@abstractmethod
def name(self) -> str:
"Name of the dialect"

@classmethod
@abstractmethod
def load_mixins(cls, *abstract_mixins) -> Self:
"Load a list of mixins that implement the given abstract mixins"

@property
@abstractmethod
def ROUNDS_ON_PREC_LOSS(self) -> bool:
"True if db rounds real values when losing precision, False if it truncates."

@abstractmethod
def quote(self, s: str):
"Quote SQL name"

@abstractmethod
def concat(self, items: List[str]) -> str:
"Provide SQL for concatenating a bunch of columns into a string"

@abstractmethod
def is_distinct_from(self, a: str, b: str) -> str:
"Provide SQL for a comparison where NULL = NULL is true"

@abstractmethod
def to_string(self, s: str) -> str:
# TODO rewrite using cast_to(x, str)
"Provide SQL for casting a column to string"

@abstractmethod
def random(self) -> str:
"Provide SQL for generating a random number betweein 0..1"

@abstractmethod
def current_timestamp(self) -> str:
"Provide SQL for returning the current timestamp, aka now"

@abstractmethod
def current_database(self) -> str:
"Provide SQL for returning the current default database."

@abstractmethod
def current_schema(self) -> str:
"Provide SQL for returning the current default schema."

@abstractmethod
def offset_limit(
self, offset: Optional[int] = None, limit: Optional[int] = None, has_order_by: Optional[bool] = None
) -> str:
"Provide SQL fragment for limit and offset inside a select"

@abstractmethod
def explain_as_text(self, query: str) -> str:
"Provide SQL for explaining a query, returned as table(varchar)"

@abstractmethod
def timestamp_value(self, t: datetime) -> str:
"Provide SQL for the given timestamp value"

@abstractmethod
def set_timezone_to_utc(self) -> str:
"Provide SQL for setting the session timezone to UTC"

@abstractmethod
def parse_type(
self,
table_path: DbPath,
col_name: str,
type_repr: str,
datetime_precision: int = None,
numeric_precision: int = None,
numeric_scale: int = None,
) -> ColType:
"Parse type info as returned by the database"

@abstractmethod
def to_comparable(self, value: str, coltype: ColType) -> str:
"""Ensure that the expression is comparable in ``IS DISTINCT FROM``."""


from typing import TypeVar, Generic

T_Dialect = TypeVar("T_Dialect", bound=AbstractDialect)


class AbstractDatabase(Generic[T_Dialect]):
@property
@abstractmethod
def dialect(self) -> T_Dialect:
"The dialect of the database. Used internally by Database, and also available publicly."

@classmethod
@abstractmethod
def load_mixins(cls, *abstract_mixins) -> type:
"Extend the dialect with a list of mixins that implement the given abstract mixins."

@property
@abstractmethod
def CONNECT_URI_HELP(self) -> str:
"Example URI to show the user in help and error messages"

@property
@abstractmethod
def CONNECT_URI_PARAMS(self) -> List[str]:
"List of parameters given in the path of the URI"

@abstractmethod
def _query(self, sql_code: str) -> list:
"Send query to database and return result"

@abstractmethod
def query_table_schema(self, path: DbPath) -> Dict[str, tuple]:
"""Query the table for its schema for table in 'path', and return {column: tuple}
where the tuple is (table_name, col_name, type_repr, datetime_precision?, numeric_precision?, numeric_scale?)

Note: This method exists instead of select_table_schema(), just because not all databases support
accessing the schema using a SQL query.
"""

@abstractmethod
def select_table_unique_columns(self, path: DbPath) -> str:
"Provide SQL for selecting the names of unique columns in the table"

@abstractmethod
def query_table_unique_columns(self, path: DbPath) -> List[str]:
"""Query the table for its unique columns for table in 'path', and return {column}"""

@abstractmethod
def _process_table_schema(
self, path: DbPath, raw_schema: Dict[str, tuple], filter_columns: Sequence[str], where: str = None
):
"""Process the result of query_table_schema().

Done in a separate step, to minimize the amount of processed columns.
Needed because processing each column may:
* throw errors and warnings
* query the database to sample values

"""

@abstractmethod
def close(self):
"Close connection(s) to the database instance. Querying will stop functioning."

@property
@abstractmethod
def is_autocommit(self) -> bool:
"Return whether the database autocommits changes. When false, COMMIT statements are skipped."


class AbstractTable(ABC):
@abstractmethod
def select(self, *exprs, distinct=False, **named_exprs) -> "AbstractTable":
"""Choose new columns, based on the old ones. (aka Projection)

Parameters:
exprs: List of expressions to constitute the columns of the new table.
If not provided, returns all columns in source table (i.e. ``select *``)
distinct: 'select' or 'select distinct'
named_exprs: More expressions to constitute the columns of the new table, aliased to keyword name.

"""
# XXX distinct=SKIP

@abstractmethod
def where(self, *exprs) -> "AbstractTable":
"""Filter the rows, based on the given predicates. (aka Selection)"""

@abstractmethod
def order_by(self, *exprs) -> "AbstractTable":
"""Order the rows lexicographically, according to the given expressions."""

@abstractmethod
def limit(self, limit: int) -> "AbstractTable":
"""Stop yielding rows after the given limit. i.e. take the first 'n=limit' rows"""

@abstractmethod
def join(self, target) -> "AbstractTable":
"""Join the current table with the target table, returning a new table containing both side-by-side.

When joining, it's recommended to use explicit tables names, instead of `this`, in order to avoid potential name collisions.

Example:
::

person = table('person')
city = table('city')

name_and_city = (
person
.join(city)
.on(person['city_id'] == city['id'])
.select(person['id'], city['name'])
)
"""

@abstractmethod
def group_by(self, *keys):
"""Behaves like in SQL, except for a small change in syntax:

A call to `.agg()` must follow every call to `.group_by()`.

Example:
::

# SELECT a, sum(b) FROM tmp GROUP BY 1
table('tmp').group_by(this.a).agg(this.b.sum())

# SELECT a, sum(b) FROM a GROUP BY 1 HAVING (b > 10)
(table('tmp')
.group_by(this.a)
.agg(this.b.sum())
.having(this.b > 10)
)

"""

@abstractmethod
def count(self) -> int:
"""SELECT count() FROM self"""

@abstractmethod
def union(self, other: "ITable"):
"""SELECT * FROM self UNION other"""

@abstractmethod
def union_all(self, other: "ITable"):
"""SELECT * FROM self UNION ALL other"""

@abstractmethod
def minus(self, other: "ITable"):
"""SELECT * FROM self EXCEPT other"""

@abstractmethod
def intersect(self, other: "ITable"):
"""SELECT * FROM self INTERSECT other"""
8 changes: 1 addition & 7 deletions data_diff/abcs/mixins.py
Original file line number Diff line number Diff line change
Expand Up @@ -134,12 +134,6 @@ def list_tables(self, table_schema: str, like: Compilable = None) -> Compilable:
"""


class AbstractMixin_Regex(AbstractMixin):
@abstractmethod
def test_regex(self, string: Compilable, pattern: Compilable) -> Compilable:
"""Tests whether the regex pattern matches the string. Returns a bool expression."""


class AbstractMixin_RandomSample(AbstractMixin):
@abstractmethod
def random_sample_n(self, tbl: str, size: int) -> str:
Expand All @@ -152,7 +146,7 @@ def random_sample_ratio_approx(self, tbl: str, ratio: float) -> str:
i.e. the actual mount of rows returned may vary by standard deviation.
"""

# def random_sample_ratio(self, table: AbstractTable, ratio: float):
# def random_sample_ratio(self, table: ITable, ratio: float):
# """Take a random sample of the size determined by the ratio (0..1), where 0 means no rows, and 1 means all rows
# """

Expand Down
94 changes: 0 additions & 94 deletions data_diff/bound_exprs.py

This file was deleted.

Loading