Skip to content

Commit

Permalink
Add support for arithmetic operations in columns
Browse files Browse the repository at this point in the history
fixes #19
  • Loading branch information
furti committed Feb 14, 2019
1 parent 9302bc6 commit 779b601
Show file tree
Hide file tree
Showing 5 changed files with 314 additions and 5 deletions.
15 changes: 15 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -203,6 +203,21 @@ By default the name of a column will be the literal text of the expression. E.g.

Sometimes this is not what you want. Especially for columns containing a function. You can use the ```AS``` clause to give a column a explicit name like we did with the ```sum(Attribute3)``` column above.

#### Arithmetics

You can use simple arithmetics inside a column. E.g.

```sql
Select Area / 2
From document
```

The following operators are available:
- *: Multiplication
- /: Division
- +: Addition
- -: Subtraction

### From \<Source>

The objects from the document you want to select.
Expand Down
152 changes: 148 additions & 4 deletions sql/sql_grammar.py
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,14 @@ def __init__(self, text, offset, elements):
class TreeNode17(TreeNode):
def __init__(self, text, offset, elements):
super(TreeNode17, self).__init__(text, offset, elements)
self.ArithmeticOperand = elements[4]
self.__ = elements[3]
self.ArithmeticOperator = elements[2]


class TreeNode18(TreeNode):
def __init__(self, text, offset, elements):
super(TreeNode18, self).__init__(text, offset, elements)
self._ = elements[0]


Expand Down Expand Up @@ -1424,23 +1432,159 @@ def _read_MultiParamFunctionName(self):
self._cache['MultiParamFunctionName'][index0] = (address0, self._offset)
return address0

def _read_ArithmeticOperand(self):
address0, index0 = FAILURE, self._offset
cached = self._cache['ArithmeticOperand'].get(index0)
if cached:
self._offset = cached[1]
return cached[0]
index1 = self._offset
address0 = self._read_Number()
if address0 is FAILURE:
self._offset = index1
address0 = self._read_Reference()
if address0 is FAILURE:
self._offset = index1
self._cache['ArithmeticOperand'][index0] = (address0, self._offset)
return address0

def _read_ArithmeticOperation(self):
address0, index0 = FAILURE, self._offset
cached = self._cache['ArithmeticOperation'].get(index0)
if cached:
self._offset = cached[1]
return cached[0]
index1, elements0 = self._offset, []
address1 = FAILURE
address1 = self._read_ArithmeticOperand()
if address1 is not FAILURE:
elements0.append(address1)
address2 = FAILURE
address2 = self._read___()
if address2 is not FAILURE:
elements0.append(address2)
address3 = FAILURE
address3 = self._read_ArithmeticOperator()
if address3 is not FAILURE:
elements0.append(address3)
address4 = FAILURE
address4 = self._read___()
if address4 is not FAILURE:
elements0.append(address4)
address5 = FAILURE
address5 = self._read_ArithmeticOperand()
if address5 is not FAILURE:
elements0.append(address5)
else:
elements0 = None
self._offset = index1
else:
elements0 = None
self._offset = index1
else:
elements0 = None
self._offset = index1
else:
elements0 = None
self._offset = index1
else:
elements0 = None
self._offset = index1
if elements0 is None:
address0 = FAILURE
else:
address0 = self._actions.make_arithmetic_operation(self._input, index1, self._offset, elements0)
self._offset = self._offset
self._cache['ArithmeticOperation'][index0] = (address0, self._offset)
return address0

def _read_ArithmeticOperator(self):
address0, index0 = FAILURE, self._offset
cached = self._cache['ArithmeticOperator'].get(index0)
if cached:
self._offset = cached[1]
return cached[0]
index1 = self._offset
chunk0 = None
if self._offset < self._input_size:
chunk0 = self._input[self._offset:self._offset + 1]
if chunk0 == '*':
address0 = self._actions.make_multiply_arithmetic_operator(self._input, self._offset, self._offset + 1)
self._offset = self._offset + 1
else:
address0 = FAILURE
if self._offset > self._failure:
self._failure = self._offset
self._expected = []
if self._offset == self._failure:
self._expected.append('\'*\'')
if address0 is FAILURE:
self._offset = index1
chunk1 = None
if self._offset < self._input_size:
chunk1 = self._input[self._offset:self._offset + 1]
if chunk1 == '/':
address0 = self._actions.make_divide_arithmetic_operator(self._input, self._offset, self._offset + 1)
self._offset = self._offset + 1
else:
address0 = FAILURE
if self._offset > self._failure:
self._failure = self._offset
self._expected = []
if self._offset == self._failure:
self._expected.append('\'/\'')
if address0 is FAILURE:
self._offset = index1
chunk2 = None
if self._offset < self._input_size:
chunk2 = self._input[self._offset:self._offset + 1]
if chunk2 == '+':
address0 = self._actions.make_add_arithmetic_operator(self._input, self._offset, self._offset + 1)
self._offset = self._offset + 1
else:
address0 = FAILURE
if self._offset > self._failure:
self._failure = self._offset
self._expected = []
if self._offset == self._failure:
self._expected.append('\'+\'')
if address0 is FAILURE:
self._offset = index1
chunk3 = None
if self._offset < self._input_size:
chunk3 = self._input[self._offset:self._offset + 1]
if chunk3 == '-':
address0 = self._actions.make_subtract_arithmetic_operator(self._input, self._offset, self._offset + 1)
self._offset = self._offset + 1
else:
address0 = FAILURE
if self._offset > self._failure:
self._failure = self._offset
self._expected = []
if self._offset == self._failure:
self._expected.append('\'-\'')
if address0 is FAILURE:
self._offset = index1
self._cache['ArithmeticOperator'][index0] = (address0, self._offset)
return address0

def _read_Operand(self):
address0, index0 = FAILURE, self._offset
cached = self._cache['Operand'].get(index0)
if cached:
self._offset = cached[1]
return cached[0]
index1 = self._offset
address0 = self._read_Null()
address0 = self._read_ArithmeticOperation()
if address0 is FAILURE:
self._offset = index1
address0 = self._read_Number()
address0 = self._read_Null()
if address0 is FAILURE:
self._offset = index1
address0 = self._read_Literal()
if address0 is FAILURE:
self._offset = index1
address0 = self._read_Reference()
address0 = self._read_ArithmeticOperand()
if address0 is FAILURE:
self._offset = index1
self._cache['Operand'][index0] = (address0, self._offset)
Expand Down Expand Up @@ -1685,7 +1829,7 @@ def _read_Semicolon(self):
if elements0 is None:
address0 = FAILURE
else:
address0 = TreeNode17(self._input[index1:self._offset], index1, elements0)
address0 = TreeNode18(self._input[index1:self._offset], index1, elements0)
self._offset = self._offset
self._cache['Semicolon'][index0] = (address0, self._offset)
return address0
Expand Down
117 changes: 117 additions & 0 deletions sql/sql_parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -375,6 +375,23 @@ def __eq__(self, obj):
return isinstance(obj, CalculationExtractor) and self.calculation == obj.calculation


class ArithmeticExtractor(object):
def __init__(self, arithmeticOperation):
self.arithmeticOperation = arithmeticOperation

def extract(self, o):
return self.arithmeticOperation.execute(o)

def resetState(self):
pass

def __str__(self):
return '%s' % (self.arithmeticOperation)

def __eq__(self, obj):
return isinstance(obj, ArithmeticExtractor) and self.arithmeticOperation == obj.arithmeticOperation


class Reference(object):
def __init__(self, value):
self.value = value
Expand Down Expand Up @@ -436,6 +453,81 @@ def __str__(self):
return '(%s %s %s)' % (self.leftDataExtractor, self.comparisonOperator, self.rightDataExtractor)


class ArithmeticOperation(BooleanExpression):
def __init__(self, leftDataExtractor, rightDataExtractor, arithmeticOperator):
self.leftDataExtractor = leftDataExtractor
self.rightDataExtractor = rightDataExtractor
self.arithmeticOperator = arithmeticOperator

def execute(self, o):
left = self.leftDataExtractor.extract(o)
right = self.rightDataExtractor.extract(o)

if left is None or right is None:
return None

return self.arithmeticOperator.calculate(left, right)

def __str__(self):
return '(%s %s %s)' % (self.leftDataExtractor, self.arithmeticOperator, self.rightDataExtractor)

def __eq__(self, obj):
if not isinstance(obj, ArithmeticOperation):
return False

if self.leftDataExtractor != obj.leftDataExtractor:
return False

if self.rightDataExtractor != obj.rightDataExtractor:
return False

return self.arithmeticOperator == obj.arithmeticOperator


class MultiplyArithmeticOperator(object):
def calculate(self, left, right):
return left * right

def __str__(self):
return '*'

def __eq__(self, obj):
return isinstance(obj, MultiplyArithmeticOperator)


class DivideArithmeticOperator(object):
def calculate(self, left, right):
return left / right

def __str__(self):
return '/'

def __eq__(self, obj):
return isinstance(obj, DivideArithmeticOperator)


class AddArithmeticOperator(object):
def calculate(self, left, right):
return left + right

def __str__(self):
return '+'

def __eq__(self, obj):
return isinstance(obj, AddArithmeticOperator)


class SubtractArithmeticOperator(object):
def calculate(self, left, right):
return left - right

def __str__(self):
return '-'

def __eq__(self, obj):
return isinstance(obj, SubtractArithmeticOperator)


class GreaterThanOrEqualsComparisonOperator(object):
def compare(self, left, right):
return left >= right
Expand Down Expand Up @@ -749,6 +841,9 @@ def findExtractor(element):
if isinstance(element, Calculation) or isinstance(element, MultiParamCalculation):
return (CalculationExtractor(element), element.name, element.grouping)

if isinstance(element, ArithmeticOperation):
return (ArithmeticExtractor(element), str(element), False)

return (None, None, None)


Expand Down Expand Up @@ -934,6 +1029,28 @@ def make_complex_boolean_expression(self, input, start, end, elements):

return expression

def make_arithmetic_operation(self, input, start, end, elements):
leftDataExctractor = findExtractor(elements[0])
arithmeticOperator = elements[2]
rightDataExtractor = findExtractor(elements[4])

operation = ArithmeticOperation(
leftDataExctractor[0], rightDataExtractor[0], arithmeticOperator)

return operation

def make_multiply_arithmetic_operator(self, input, start, end):
return MultiplyArithmeticOperator()

def make_divide_arithmetic_operator(self, input, start, end):
return DivideArithmeticOperator()

def make_add_arithmetic_operator(self, input, start, end):
return AddArithmeticOperator()

def make_subtract_arithmetic_operator(self, input, start, end):
return SubtractArithmeticOperator()

def make_asterisk(self, input, start, end):
return Asterisk()

Expand Down
14 changes: 13 additions & 1 deletion src/sql_grammar.peg
Original file line number Diff line number Diff line change
Expand Up @@ -63,8 +63,20 @@ grammar sql

# Functions End

# Arithmetic
ArithmeticOperand <- Number / Reference

ArithmeticOperation <- ArithmeticOperand __ ArithmeticOperator __ ArithmeticOperand %make_arithmetic_operation

ArithmeticOperator <- '*' %make_multiply_arithmetic_operator
/ '/' %make_divide_arithmetic_operator
/ '+' %make_add_arithmetic_operator
/ '-' %make_subtract_arithmetic_operator

# Arithmetic End

# Basics
Operand <- Null / Number / Literal / Reference
Operand <- ArithmeticOperation / Null / Literal / ArithmeticOperand

Reference <- [a-zA-Z0-9]+ %make_reference

Expand Down
Loading

0 comments on commit 779b601

Please sign in to comment.