Skip to content

Commit cf3b779

Browse files
authored
Add functions and good parsing in select keyword (#5)
1 parent 1080728 commit cf3b779

File tree

10 files changed

+154
-25
lines changed

10 files changed

+154
-25
lines changed

flashdb/core/query/exceptions/query_exception.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -8,4 +8,4 @@ class ValidationError(Exception):
88

99

1010
class EmptyValueError(ValidationError):
11-
pass
11+
pass
+19
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
from flashdb.core.query.functions.aggregate_functions import Avg, Count, Max, Min, Sum
2+
from flashdb.core.query.functions.string_functions import Length, Lower, Trim, Upper
3+
from flashdb.core.query.functions.time_functions import Now
4+
from flashdb.core.query.functions.misc_functions import Distinct
5+
6+
7+
VALID_FUNCTIONS = {
8+
"avg": Avg,
9+
"count": Count,
10+
"distinct": Distinct,
11+
"length": Length,
12+
"lower": Lower,
13+
"max": Max,
14+
"min": Min,
15+
"now": Now,
16+
"sum": Sum,
17+
"trim": Trim,
18+
"upper": Upper
19+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
from flashdb.core.query.functions.base import Function
2+
3+
4+
class Avg(Function):
5+
name = "avg"
6+
7+
8+
class Count(Function):
9+
name = "count"
10+
11+
12+
class Max(Function):
13+
name = "max"
14+
15+
16+
class Min(Function):
17+
name = "min"
18+
19+
20+
class Sum(Function):
21+
name = "sum"

flashdb/core/query/functions/base.py

+9
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
from abc import ABC
2+
3+
4+
class Function(ABC):
5+
6+
def __eq__(self, other):
7+
if isinstance(other, self.__class__):
8+
return getattr(other, "name") == getattr(self, "name")
9+
return False
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
from flashdb.core.query.functions.base import Function
2+
3+
4+
class Distinct(Function):
5+
name = "distinct"
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
from flashdb.core.query.functions.base import Function
2+
3+
4+
class Length(Function):
5+
name = "length"
6+
7+
8+
class Lower(Function):
9+
name = "lower"
10+
11+
12+
class Trim(Function):
13+
name = "trim"
14+
15+
16+
class Upper(Function):
17+
name = "upper"
18+
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
from flashdb.core.query.functions.base import Function
2+
3+
4+
class Now(Function):
5+
name = "now"
+44-13
Original file line numberDiff line numberDiff line change
@@ -1,34 +1,65 @@
1-
from typing import Set, List, Dict
1+
from typing import List
22

33
from flashdb.core.query.exceptions.query_exception import ParseException, EmptyValueError, ValidationError
4+
from flashdb.core.query.functions import VALID_FUNCTIONS
45
from flashdb.core.query.keywords.base import Keyword
56

67

8+
class Column(object):
9+
10+
def __init__(self, name: str, alias: str = None, functions: List = None):
11+
self.name = name
12+
self.alias = alias
13+
self.functions = functions
14+
15+
def __eq__(self, other):
16+
if isinstance(other, self.__class__):
17+
print(other.__dict__)
18+
return other.__dict__ == self.__dict__
19+
return False
20+
21+
722
class SelectKeyword(Keyword):
823

24+
NAME = "name"
25+
AS = "as"
26+
FUNCTION = "apply_function"
27+
FUNCTION_DELIMITER = '>'
28+
929
def __init__(self, s_query: List):
1030
self.data = s_query
1131
self.columns = []
1232

1333
def validate(self) -> "Keyword":
1434
for item in self.data:
1535
keys = item.keys()
16-
if 'name' not in keys:
17-
raise ValidationError("[SELECT] `name` is mandatory")
18-
if not item['name']:
19-
raise EmptyValueError(f"[SELECT] Empty `name` is not allowed")
20-
if 'as' in keys and not item['as']:
21-
raise EmptyValueError(f"[SELECT] Empty `as` is not allowed")
22-
# check if function exists
36+
if self.NAME not in keys:
37+
raise ValidationError(f"[SELECT] `{self.NAME}` is mandatory")
38+
if not item[self.NAME]:
39+
raise EmptyValueError(f"[SELECT] Empty `{self.NAME}` is not allowed")
40+
for keyword in [self.AS, self.FUNCTION]:
41+
if keyword in keys and not item[keyword]:
42+
raise EmptyValueError(f"[SELECT] Empty `{keyword}` is not allowed")
2343
return self
2444

2545
def parse(self):
2646
if not self.data:
2747
return
2848
for item in self.data:
29-
self.columns.append({
30-
'name': item['name'],
31-
'as': item.get('as', None),
32-
'functions': None # create function with a split
33-
})
49+
self.columns.append(Column(
50+
item[self.NAME],
51+
item.get(self.AS, None),
52+
self.parse_functions(item.get(self.FUNCTION, None))
53+
))
3454
return self
55+
56+
def parse_functions(self, raw_functions: str):
57+
if raw_functions is None:
58+
return None
59+
split_functions = raw_functions.split(self.FUNCTION_DELIMITER)
60+
if any(f not in VALID_FUNCTIONS for f in split_functions):
61+
raise ParseException(f"[SELECT] {raw_functions} is invalid")
62+
functions = list()
63+
for f_name in split_functions:
64+
functions.append(VALID_FUNCTIONS[f_name]())
65+
return functions

install/requirements.txt

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
parameterized

tests/core/query/keywords/test_select.py

+31-11
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,11 @@
11
import unittest
2+
from typing import Dict
23

3-
from flashdb.core.query.exceptions.query_exception import EmptyValueError
4-
from flashdb.core.query.keywords.select_keyword import SelectKeyword
4+
from parameterized import parameterized
5+
6+
from flashdb.core.query.exceptions.query_exception import EmptyValueError, ParseException
7+
from flashdb.core.query.functions import Length, Avg
8+
from flashdb.core.query.keywords.select_keyword import SelectKeyword, Column
59

610

711
class TestSelectKeyword(unittest.TestCase):
@@ -14,18 +18,34 @@ def test_validate_success(self):
1418
}
1519
SelectKeyword([expected]).validate()
1620

17-
def test_validate_emptyName_throwException(self):
18-
with self.assertRaises(EmptyValueError):
19-
SelectKeyword([{
20-
"name": ""
21-
}]).validate()
21+
@parameterized.expand([
22+
({"name": ""}, EmptyValueError),
23+
({"name": "myColumn", "as": ""}, EmptyValueError),
24+
({"name": "myColumn", "apply_function": ""}, EmptyValueError),
25+
])
26+
def test_validate_emptyFieldInItem_throwException(self, item: Dict, expected_exception: Exception):
27+
with self.assertRaises(expected_exception):
28+
SelectKeyword([item]).validate()
29+
30+
def test_parse_success(self):
31+
expected = Column("myColumn", "toto", [
32+
Avg(), Length()
33+
])
34+
select = SelectKeyword([{
35+
"name": "myColumn",
36+
"as": "toto",
37+
"apply_function": "avg>length"
38+
}]).parse()
39+
self.assertEqual(1, len(select.columns))
40+
self.assertEqual(expected, select.columns[0])
2241

23-
def test_validate_emptyAs_throwException(self):
24-
with self.assertRaises(EmptyValueError):
42+
def test_parse_unknownFunction_throwException(self):
43+
with self.assertRaises(ParseException):
2544
SelectKeyword([{
2645
"name": "myColumn",
27-
"as": ""
28-
}]).validate()
46+
"as": "toto",
47+
"apply_function": "dfdf"
48+
}]).parse()
2949

3050

3151
if __name__ == "__main__":

0 commit comments

Comments
 (0)