-
-
Notifications
You must be signed in to change notification settings - Fork 320
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add URI and text filters #1079
Add URI and text filters #1079
Changes from 19 commits
1f2eb2c
93a0391
afffa09
37ca6cc
19c741d
ee4b76f
929403b
e747e91
f1a9692
46768ec
f4ab28a
dd2aff8
eccffc1
f220966
cd6447c
a0b120d
3a4ef35
6e5e0ee
19a19f2
feedcb2
34a288f
15e4ef7
b85949b
2475e26
f2fd9da
90b4ee1
cb9a118
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -20,6 +20,10 @@ | |
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 | ||
|
@@ -73,7 +77,7 @@ class Literal(DBFunction): | |
name = 'as literal' | ||
hints = tuple([ | ||
hints.parameter_count(1), | ||
hints.parameter(1, hints.literal), | ||
hints.parameter(0, hints.literal), | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why this change? (and others like it) There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I wanted to use 1-based indexing, but changed my mind. Everything in Python is 0-based, so I decided to go with the flow. |
||
]) | ||
|
||
@staticmethod | ||
|
@@ -87,7 +91,7 @@ class ColumnName(DBFunction): | |
name = 'as column name' | ||
hints = tuple([ | ||
hints.parameter_count(1), | ||
hints.parameter(1, hints.column), | ||
hints.parameter(0, hints.column), | ||
]) | ||
|
||
@property | ||
|
@@ -190,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 | ||
|
@@ -234,7 +239,24 @@ class StartsWith(DBFunction): | |
|
||
@staticmethod | ||
def to_sa_expression(string, prefix): | ||
return string.like(f'{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), | ||
hints.mathesar_filter, | ||
]) | ||
|
||
@staticmethod | ||
def to_sa_expression(string, sub_string): | ||
pattern = func.concat('%', sub_string, '%') | ||
return string.like(pattern) | ||
|
||
|
||
class ToLowercase(DBFunction): | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -4,18 +4,33 @@ | |
Mathesar filters not supporting composition. | ||
""" | ||
|
||
from db.functions.base import DBFunction | ||
from abc import abstractmethod | ||
|
||
from db.functions.base import DBFunction, Or, Lesser, Equal, Greater, StartsWith, Contains, ToLowercase | ||
|
||
from db.functions import hints | ||
|
||
|
||
# TODO (tech debt) define in terms of other DBFunctions | ||
# would involve creating an alternative to to_sa_expression: something like to_db_function | ||
# execution engine would see that to_sa_expression is not implemented, and it would look for | ||
# to_db_function. | ||
class RedundantDBFunction(DBFunction): | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I find this name confusing. Perhaps something like There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think DBFunctionCombination can be even more confusing, since a regular DBFunction instance can contain other DBFunction instances as parameters, and thus could be called a combination or supporting combination. I like "redundant", because it highlights that it's made of DBFunctions that could already be combined together without this particular DBFunction. I'm open to other names. Maybe There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I like |
||
""" | ||
A DBFunction that is meant to be unpacked into another DBFunction. A way to define a DBFunction | ||
as a combination of DBFunctions. Its to_sa_expression method is not to used. Its concrete | ||
implementations are expected to implement the unpack method. | ||
""" | ||
@staticmethod | ||
def to_sa_expression(*_): | ||
raise Exception("UnpackabelDBFunction.to_sa_expression should never be used.") | ||
|
||
@abstractmethod | ||
def unpack(self): | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. For my own education: Should implementations of this There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think it's desirable not to have to do manual recursion inside |
||
""" | ||
Should return a DBFunction instance with self.parameters forwarded to it. A way to define | ||
a DBFunction in terms of other DBFunctions. | ||
""" | ||
pass | ||
|
||
|
||
class LesserOrEqual(DBFunction): | ||
class LesserOrEqual(RedundantDBFunction): | ||
id = 'lesser_or_equal' | ||
name = 'is lesser or equal to' | ||
hints = tuple([ | ||
|
@@ -25,12 +40,16 @@ class LesserOrEqual(DBFunction): | |
hints.mathesar_filter, | ||
]) | ||
|
||
@staticmethod | ||
def to_sa_expression(value1, value2): | ||
return value1 <= value2 | ||
def unpack(self): | ||
param0 = self.parameters[0] | ||
param1 = self.parameters[1] | ||
return Or([ | ||
Lesser([param0, param1]), | ||
Equal([param0, param1]), | ||
]) | ||
|
||
|
||
class GreaterOrEqual(DBFunction): | ||
class GreaterOrEqual(RedundantDBFunction): | ||
id = 'greater_or_equal' | ||
name = 'is greater or equal to' | ||
hints = tuple([ | ||
|
@@ -40,6 +59,48 @@ class GreaterOrEqual(DBFunction): | |
hints.mathesar_filter, | ||
]) | ||
|
||
@staticmethod | ||
def to_sa_expression(value1, value2): | ||
return value1 >= value2 | ||
def unpack(self): | ||
param0 = self.parameters[0] | ||
param1 = self.parameters[1] | ||
return Or([ | ||
Greater([param0, param1]), | ||
Equal([param0, param1]), | ||
]) | ||
|
||
|
||
class StartsWithCaseInsensitive(RedundantDBFunction): | ||
id = 'starts_with_case_insensitive' | ||
name = 'starts with (case insensitive)' | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If this name is meant to be used in the frontend, please remove There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It is. If the suffix is removed, it will have the same display name as a regular (case-sensitive) There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. There shouldn't be a case-sensitive "starts with" in the frontend, only the case insensitive version. We don't want to overwhelm users with a lot of similar filtering options, it will make finding the filter they want harder. And non-technical users may not know what "case sensitive" refers to. |
||
hints = tuple([ | ||
hints.returns(hints.boolean), | ||
hints.parameter_count(2), | ||
hints.all_parameters(hints.string_like), | ||
hints.mathesar_filter, | ||
]) | ||
|
||
def unpack(self): | ||
param0 = self.parameters[0] | ||
param1 = self.parameters[1] | ||
return StartsWith([ | ||
ToLowercase([param0]), | ||
ToLowercase([param1]), | ||
]) | ||
|
||
|
||
class ContainsCaseInsensitive(RedundantDBFunction): | ||
id = 'contains_case_insensitive' | ||
name = 'contains (case insensitive)' | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Case insensitive comment from above applies here as well. |
||
hints = tuple([ | ||
hints.returns(hints.boolean), | ||
hints.parameter_count(2), | ||
hints.all_parameters(hints.string_like), | ||
hints.mathesar_filter, | ||
]) | ||
|
||
def unpack(self): | ||
param0 = self.parameters[0] | ||
param1 = self.parameters[1] | ||
return Contains([ | ||
ToLowercase([param0]), | ||
ToLowercase([param1]), | ||
]) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Awesome idea.