Skip to content

Commit

Permalink
Where: Start where, add Condition.
Browse files Browse the repository at this point in the history
  • Loading branch information
scragly committed Aug 6, 2021
1 parent bc0aa64 commit a780cb5
Show file tree
Hide file tree
Showing 5 changed files with 151 additions and 30 deletions.
100 changes: 70 additions & 30 deletions everstone/sql/comparisons.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,47 @@
from __future__ import annotations

import abc
import typing as t


class Condition:
def __init__(self, expression: t.Union[str, Condition]):
self.expression = expression

def __str__(self):
return str(self.expression)

def __repr__(self):
return f'<Condition "{self.expression}">'

def __eq__(self, other):
return str(self) == str(other)

def __and__(self, other):
return Condition(f"({self} AND {other})")

def __or__(self, other):
return Condition(f"({self} OR {other})")

@classmethod
def and_(cls, *conditions): # pragma: no cover
joined = " AND ".join(str(c) for c in conditions)
return cls(f"({joined})")

@classmethod
def or_(cls, *conditions): # pragma: no cover
joined = " OR ".join(str(c) for c in conditions)
return cls(f"({joined})")

def and_(self, *conditions):
joined = " AND ".join(str(c) for c in [self, *conditions])
return Condition(f"({joined})")

def or_(self, *conditions):
joined = " OR ".join(str(c) for c in [self, *conditions])
return Condition(f"({joined})")


class Comparable(metaclass=abc.ABCMeta):
"""Base class to define an SQL object as able to use SQL comparison operations."""

Expand All @@ -20,79 +60,79 @@ def _sql_value(value: t.Any) -> str:
def __hash__(self):
return hash(str(self))

def __lt__(self, value: t.Any) -> str:
def __lt__(self, value: t.Any) -> Condition:
"""Evaluate if less than a value."""
value = self._sql_value(value)
return f"{self} < {value}"
return Condition(f"{self} < {value}")

def __le__(self, value: t.Any) -> str:
def __le__(self, value: t.Any) -> Condition:
"""Evaluate if less than or equal to a value."""
value = self._sql_value(value)
return f"{self} <= {value}"
return Condition(f"{self} <= {value}")

def __eq__(self, value: t.Any) -> str:
def __eq__(self, value: t.Any) -> Condition:
"""Evaluate if equal to a value."""
value = self._sql_value(value)
return f"{self} = {value}"
return Condition(f"{self} = {value}")

def __ne__(self, value: t.Any) -> str:
def __ne__(self, value: t.Any) -> Condition:
"""Evaluate if not equal to a value."""
value = self._sql_value(value)
return f"{self} <> {value}"
return Condition(f"{self} <> {value}")

def __gt__(self, value: t.Any) -> str:
def __gt__(self, value: t.Any) -> Condition:
"""Evaluate if greater than a value."""
value = self._sql_value(value)
return f"{self} > {value}"
return Condition(f"{self} > {value}")

def __ge__(self, value: t.Any) -> str:
def __ge__(self, value: t.Any) -> Condition:
"""Evaluate if greater than or equal to a value."""
value = self._sql_value(value)
return f"{self} >= {value}"
return Condition(f"{self} >= {value}")

def like(self, value: t.Any) -> str:
def like(self, value: t.Any) -> Condition:
"""Evaluate if like a value."""
value = self._sql_value(value)
return f"{self} LIKE {value}"
return Condition(f"{self} LIKE {value}")

def not_like(self, value: t.Any) -> str:
def not_like(self, value: t.Any) -> Condition:
"""Evaluate if not like a value."""
value = self._sql_value(value)
return f"{self} NOT LIKE {value}"
return Condition(f"{self} NOT LIKE {value}")

def ilike(self, value: t.Any) -> str:
def ilike(self, value: t.Any) -> Condition:
"""Evaluate if like a value, ignoring case."""
value = self._sql_value(value)
return f"{self} ILIKE {value}"
return Condition(f"{self} ILIKE {value}")

def not_ilike(self, value: t.Any) -> str:
def not_ilike(self, value: t.Any) -> Condition:
"""Evaluate if not like a value, ignoring case."""
value = self._sql_value(value)
return f"{self} NOT ILIKE {value}"
return Condition(f"{self} NOT ILIKE {value}")

def between(self, minvalue: t.Any, maxvalue: t.Any) -> str:
def between(self, minvalue: t.Any, maxvalue: t.Any) -> Condition:
"""Evaluate if between two values."""
minvalue = self._sql_value(minvalue)
maxvalue = self._sql_value(maxvalue)
return f"{self} BETWEEN {minvalue} AND {maxvalue}"
return Condition(f"{self} BETWEEN {minvalue} AND {maxvalue}")

def not_between(self, minvalue: t.Any, maxvalue: t.Any) -> str:
def not_between(self, minvalue: t.Any, maxvalue: t.Any) -> Condition:
"""Evaluate if not between two values."""
minvalue = self._sql_value(minvalue)
maxvalue = self._sql_value(maxvalue)
return f"{self} NOT BETWEEN {minvalue} AND {maxvalue}"
return Condition(f"{self} NOT BETWEEN {minvalue} AND {maxvalue}")

def is_(self, value: t.Any) -> str:
def is_(self, value: t.Any) -> Condition:
"""Evaluate if is a value."""
value = self._sql_value(value)
return f"{self} IS {value}"
return Condition(f"{self} IS {value}")

def is_not(self, value: t.Any) -> str:
def is_not(self, value: t.Any) -> Condition:
"""Evaluate if is not a value."""
value = self._sql_value(value)
return f"{self} IS NOT {value}"
return Condition(f"{self} IS NOT {value}")

def in_(self, value: t.Any) -> str:
def in_(self, value: t.Any) -> Condition:
"""Evaluate if in a value."""
value = self._sql_value(value)
return f"{self} IN {value}"
return Condition(f"{self} IN {value}")
2 changes: 2 additions & 0 deletions everstone/sql/select.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import typing as t

from . import where
from .. import database

if t.TYPE_CHECKING:
Expand All @@ -13,6 +14,7 @@
class Select:
def __init__(self, db: database.Database = None):
self.db = db or database.Database.get_default()
self.where = where.Where(self)

# references
self._columns: t.List[t.Union[Column, Aggregate]] = []
Expand Down
28 changes: 28 additions & 0 deletions everstone/sql/where.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
from __future__ import annotations

import typing as t

from .comparisons import Condition


class Where:
def __init__(self, query):
self.query = query
self._conditions: list[Condition] = []

def add_condition(self, condition: Condition):
self._conditions.append(condition)

def clear(self):
self._conditions.clear()

def __call__(self, *conditions: t.Union[Condition, str]) -> Where:
for c in conditions:
if isinstance(c, str):
c = Condition(c)
self.add_condition(c)
return self

@property
def sql(self):
return " AND ".join(str(c) for c in self._conditions)
13 changes: 13 additions & 0 deletions tests/test_comparisons.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,3 +36,16 @@ def test_comparable_operators():
assert (example.is_("something")) == "'example_text' IS 'something'"
assert (example.is_not("something")) == "'example_text' IS NOT 'something'"
assert (example.in_("something")) == "'example_text' IN 'something'"


def test_condition():
c = comparisons.Condition("example")
assert str(c) == "example"
assert repr(c) == '<Condition "example">'
assert c == "example"
assert (c & "a") == "(example AND a)"
assert (c | "a") == "(example OR a)"
assert c.and_("b") == "(example AND b)"
assert c.or_("b") == "(example OR b)"
assert comparisons.Condition.and_(c, "b") == "(example AND b)"
assert comparisons.Condition.or_(c, "b") == "(example OR b)"
38 changes: 38 additions & 0 deletions tests/test_where.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
"""Testing of Where statement functionality."""

import pytest

import everstone
from everstone.sql import constraints, types

everstone.db.disable_execution()


@pytest.fixture
def example_table():
t = everstone.db.Table("sample_table")
t.Column("col_a", types.Text, constraints.PrimaryKey)
t.Column("col_b", types.Integer)
return t


def test_where(example_table):
s = example_table.select("col_a")
a = example_table.columns.col_a
b = example_table.columns.col_b
s.where(example_table.columns.col_a == example_table.columns.col_b)
assert s.where.sql == "public.sample_table.col_a = public.sample_table.col_b"
s.where.clear()
assert s.where.sql == ""
s.where(a=="john", b >= 100)
assert s.where.sql == "public.sample_table.col_a = 'john' AND public.sample_table.col_b >= 100"
s.where.clear()
s.where(a.is_(True) | (a == 100) & b.ilike("dan"))
assert s.where.sql == (
"(public.sample_table.col_a IS TRUE"
" OR (public.sample_table.col_a = 100"
" AND public.sample_table.col_b ILIKE 'dan'))"
)
s.where.clear()
s.where("string_example IS NOT NULL")
assert s.where.sql == "string_example IS NOT NULL"

0 comments on commit a780cb5

Please sign in to comment.