Skip to content

Commit

Permalink
Merge branch 'master' into mathesar-993-attnum-column-drop
Browse files Browse the repository at this point in the history
  • Loading branch information
silentninja committed Mar 3, 2022
2 parents f7029fb + eb4b5e4 commit 6e8f695
Show file tree
Hide file tree
Showing 104 changed files with 3,073 additions and 1,643 deletions.
43 changes: 0 additions & 43 deletions .github/auto_request_review.yml

This file was deleted.

15 changes: 0 additions & 15 deletions .github/workflows/automate-review-requests.yml

This file was deleted.

11 changes: 5 additions & 6 deletions db/columns/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -141,14 +141,13 @@ def column_index(self):
@property
def column_attnum(self):
"""
Get the ordinal index of this column in its table, if it is
Get the attnum of this column in its table, if it is
attached to a table that is associated with the column's engine.
"""
if (
self.engine is not None
and self.table_ is not None
and inspect(self.engine).has_table(self.table_.name, schema=self.table_.schema)
):
engine_exists = self.engine is not None
table_exists = self.table_ is not None
engine_has_table = inspect(self.engine).has_table(self.table_.name, schema=self.table_.schema)
if engine_exists and table_exists and engine_has_table:
return get_column_attnum_from_name(
self.table_oid,
self.name,
Expand Down
80 changes: 72 additions & 8 deletions db/functions/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,14 @@
from sqlalchemy import column, not_, and_, or_, func, literal

from db.functions import hints
from db.functions.exceptions import BadDBFunctionFormat


def sa_call_sql_function(function_name, *parameters):
return getattr(func, function_name)(*parameters)


# NOTE: this class is abstract.
class DBFunction(ABC):
id = None
name = None
Expand All @@ -30,13 +36,22 @@ class DBFunction(ABC):
# strings.
depends_on = None

def __eq__(self, other):
return (
isinstance(other, DBFunction)
and self.id == other.id
and self.parameters == other.parameters
)

def __init__(self, parameters):
if self.id is None:
raise ValueError('DBFunction subclasses must define an ID.')
if self.name is None:
raise ValueError('DBFunction subclasses must define a name.')
if self.depends_on is not None and not isinstance(self.depends_on, tuple):
raise ValueError('DBFunction subclasses\' depends_on attribute must either be None or a tuple of SQL function names.')
if not isinstance(parameters, list):
raise BadDBFunctionFormat('DBFunction instance parameter `parameters` must be a list.')
self.parameters = parameters

@property
Expand All @@ -45,7 +60,7 @@ def referenced_columns(self):
Useful when checking if all referenced columns are present in the queried relation."""
columns = set([])
for parameter in self.parameters:
if isinstance(parameter, ColumnReference):
if isinstance(parameter, ColumnName):
columns.add(parameter.column)
elif isinstance(parameter, DBFunction):
columns.update(parameter.referenced_columns)
Expand All @@ -62,20 +77,21 @@ class Literal(DBFunction):
name = 'as literal'
hints = tuple([
hints.parameter_count(1),
hints.parameter(1, hints.literal),
hints.parameter(0, hints.literal),
])

@staticmethod
def to_sa_expression(primitive):
return literal(primitive)


class ColumnReference(DBFunction):
id = 'column_reference'
name = 'as column Reference'
# This represents referencing columns by their Postgres name.
class ColumnName(DBFunction):
id = 'column_name'
name = 'as column name'
hints = tuple([
hints.parameter_count(1),
hints.parameter(1, hints.column),
hints.parameter(0, hints.column),
])

@property
Expand Down Expand Up @@ -178,7 +194,8 @@ class In(DBFunction):
hints = tuple([
hints.returns(hints.boolean),
hints.parameter_count(2),
hints.parameter(2, hints.array),
hints.parameter(0, hints.any),
hints.parameter(1, hints.array),
])

@staticmethod
Expand Down Expand Up @@ -213,6 +230,36 @@ def to_sa_expression(*values):
class StartsWith(DBFunction):
id = 'starts_with'
name = 'starts with'
hints = tuple([
hints.returns(hints.boolean),
hints.parameter_count(2),
hints.all_parameters(hints.string_like),
])

@staticmethod
def to_sa_expression(string, prefix):
pattern = func.concat(prefix, '%')
return string.like(pattern)


class Contains(DBFunction):
id = 'contains'
name = 'contains'
hints = tuple([
hints.returns(hints.boolean),
hints.parameter_count(2),
hints.all_parameters(hints.string_like),
])

@staticmethod
def to_sa_expression(string, sub_string):
pattern = func.concat('%', sub_string, '%')
return string.like(pattern)


class StartsWithCaseInsensitive(DBFunction):
id = 'starts_with_case_insensitive'
name = 'starts with'
hints = tuple([
hints.returns(hints.boolean),
hints.parameter_count(2),
Expand All @@ -222,7 +269,24 @@ class StartsWith(DBFunction):

@staticmethod
def to_sa_expression(string, prefix):
return string.like(f'{prefix}%')
pattern = func.concat(prefix, '%')
return string.ilike(pattern)


class ContainsCaseInsensitive(DBFunction):
id = 'contains_case_insensitive'
name = 'contains'
hints = tuple([
hints.returns(hints.boolean),
hints.parameter_count(2),
hints.all_parameters(hints.string_like),
hints.mathesar_filter,
])

@staticmethod
def to_sa_expression(string, sub_string):
pattern = func.concat('%', sub_string, '%')
return string.ilike(pattern)


class ToLowercase(DBFunction):
Expand Down
8 changes: 6 additions & 2 deletions db/functions/exceptions.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,12 @@
class BadDBFunctionFormat(Exception):
class DBFunctionException(Exception):
pass


class UnknownDBFunctionId(BadDBFunctionFormat):
class BadDBFunctionFormat(DBFunctionException):
pass


class UnknownDBFunctionID(BadDBFunctionFormat):
pass


Expand Down
50 changes: 46 additions & 4 deletions db/functions/hints.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
from frozendict import frozendict


def _make_hint(id, **rest):
return frozendict({"id": id, **rest})


def get_parameter_hints(index, db_function_subclass):
"""
Returns the hints declared on the parameter at specified index. If explicit hints are not
Expand All @@ -23,10 +27,6 @@ def get_parameter_count(db_function_subclass):
return None


def _make_hint(id, **rest):
return frozendict({"id": id, **rest})


def parameter_count(count):
return _make_hint("parameter_count", count=count)

Expand All @@ -43,6 +43,40 @@ def returns(*hints):
return _make_hint("returns", hints=hints)


def get_parameter_type_hints(index, db_function_subclass):
"""
Returns the output of get_parameter_hints filtered to only include hints that are applicable to
types. Useful when comparing a parameter's hintset to a type's hintset. We do that when
matching filters to UI/Mathesar types, for example.
"""
parameter_hints = get_parameter_hints(index, db_function_subclass)
parameter_type_hints = tuple(
hint
for hint in parameter_hints
if _is_hint_applicable_to_types(hint)
)
return parameter_type_hints


def _is_hint_applicable_to_types(hint):
"""
Checks that a hint doesn't have the `not_applicable_to_types` hintset.
"""
hints_about_hints = hint.get("hints", None)
if hints_about_hints:
return not_applicable_to_types not in hints_about_hints
else:
return True


# When applied to a hint, meant to suggest that it doesn't describe type attributes.
# Useful when you want to find only the hints that describe a type (or not a type).
# For example, when checking if hints applied to a Mathesar/UI type are a superset of hints applied
# to a parameter, you are only interested in hints that describe type-related information (that
# might be applied to a type).
not_applicable_to_types = _make_hint("not_applicable_to_types")


boolean = _make_hint("boolean")


Expand All @@ -61,6 +95,9 @@ def returns(*hints):
uri = _make_hint("uri")


email = _make_hint("email")


literal = _make_hint("literal")


Expand All @@ -70,3 +107,8 @@ def returns(*hints):

# A hint that all types are meant to satisfy.
any = _make_hint("any")


# When applied to a parameter, meant to suggest values for that parameter.
def suggested_values(values):
return _make_hint("suggested_values", hints=(not_applicable_to_types,), values=values)
Loading

0 comments on commit 6e8f695

Please sign in to comment.