Skip to content
This repository was archived by the owner on Oct 20, 2022. It is now read-only.

Commit b7c3e37

Browse files
Add modulo operator to query language
1 parent 51a6c7e commit b7c3e37

File tree

3 files changed

+237
-8
lines changed

3 files changed

+237
-8
lines changed

src/main/java/com/google/visualization/datasource/query/parser/QueryParser.jj

Lines changed: 15 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@ import com.google.visualization.datasource.query.scalarfunction.CurrentDateTime;
5858
import com.google.visualization.datasource.query.scalarfunction.DateDiff;
5959
import com.google.visualization.datasource.query.scalarfunction.Difference;
6060
import com.google.visualization.datasource.query.scalarfunction.Lower;
61+
import com.google.visualization.datasource.query.scalarfunction.Modulo;
6162
import com.google.visualization.datasource.query.scalarfunction.Product;
6263
import com.google.visualization.datasource.query.scalarfunction.Quotient;
6364
import com.google.visualization.datasource.query.scalarfunction.ScalarFunction;
@@ -192,6 +193,7 @@ TOKEN:
192193
| <OP_PLUS: "+">
193194
| <OP_MINUS: "-">
194195
| <OP_SLASH: "/">
196+
| <OP_MODULO: "%">
195197
}
196198

197199
// The entire query
@@ -592,36 +594,36 @@ AbstractColumn arithmeticExpression() throws InvalidQueryException :
592594
AbstractColumn column;
593595
}
594596
{
595-
column = possibleSummationOrSubtraction()
597+
column = possibleSecondOrderArithmeticExpression()
596598
{ return column; }
597599
}
598600

599601
// A possible sum or subtraction expression of possible multiplication or division expressions.
600602
// It is "possible" because it can also match a single expression without any
601603
// multiplications and divisions, and then it is just returned as is.
602-
AbstractColumn possibleSummationOrSubtraction() throws
604+
AbstractColumn possibleSecondOrderArithmeticExpression() throws
603605
InvalidQueryException :
604606
{
605607
AbstractColumn column;
606608
AbstractColumn column1;
607609
}
608610
{
609-
column = possibleMultiplicationOrDivision()
611+
column = possibleFirstOrderArithmeticExpression()
610612
(
611-
( (<OP_PLUS> column1 = possibleMultiplicationOrDivision() {
613+
( (<OP_PLUS> column1 = possibleFirstOrderArithmeticExpression() {
612614
column =
613615
new ScalarFunctionColumn(GenericsHelper.makeAbstractColumnList(
614616
new AbstractColumn[] {column, column1}), Sum.getInstance() );} )
615-
| (<OP_MINUS> column1 = possibleMultiplicationOrDivision() {
617+
| (<OP_MINUS> column1 = possibleFirstOrderArithmeticExpression() {
616618
column =
617619
new ScalarFunctionColumn(GenericsHelper.makeAbstractColumnList(
618620
new AbstractColumn[]{column, column1}),
619621
Difference.getInstance()); } ) ) )*
620622
{ return column; }
621623
}
622624

623-
// A possible multiplication or division expression of atomic abstract column descriptors.
624-
AbstractColumn possibleMultiplicationOrDivision() throws
625+
// A possible multiplication division or modulo expression of atomic abstract column descriptors.
626+
AbstractColumn possibleFirstOrderArithmeticExpression() throws
625627
InvalidQueryException :
626628
{
627629
AbstractColumn column;
@@ -638,7 +640,12 @@ AbstractColumn possibleMultiplicationOrDivision() throws
638640
column =
639641
new ScalarFunctionColumn(GenericsHelper.makeAbstractColumnList(
640642
new AbstractColumn[]{column, column1}),
641-
Quotient.getInstance()); } ) ) )*
643+
Quotient.getInstance()); } )
644+
| (<OP_MODULO> column1 = atomicAbstractColumnDescriptor() {
645+
column =
646+
new ScalarFunctionColumn(GenericsHelper.makeAbstractColumnList(
647+
new AbstractColumn[]{column, column1}),
648+
Modulo.getInstance()); } ) ) )*
642649
{ return column; }
643650
}
644651

Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
1+
// Copyright 2010 Google Inc.
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
package com.google.visualization.datasource.query.scalarfunction;
16+
17+
import com.google.visualization.datasource.base.InvalidQueryException;
18+
import com.google.visualization.datasource.datatable.value.NumberValue;
19+
import com.google.visualization.datasource.datatable.value.Value;
20+
import com.google.visualization.datasource.datatable.value.ValueType;
21+
22+
import java.util.List;
23+
24+
/**
25+
* The binary scalar function modulo().
26+
* Returns the modulo between two number values.
27+
*
28+
* @author Roee E.
29+
*/
30+
public class Modulo implements ScalarFunction {
31+
32+
/**
33+
* The name of this function.
34+
*/
35+
private static final String FUNCTION_NAME = "modulo";
36+
37+
/**
38+
* A singleton instance of this class.
39+
*/
40+
private static final Modulo INSTANCE = new Modulo();
41+
42+
/**
43+
* A private constructor, to prevent instantiation other than by the singleton.
44+
*/
45+
private Modulo() {}
46+
47+
/**
48+
* Returns the singleton instance of this class.
49+
*
50+
* @return The singleton instance of this class.
51+
*/
52+
public static Modulo getInstance() {
53+
return INSTANCE;
54+
}
55+
56+
/**
57+
* {@inheritDoc}
58+
*/
59+
public String getFunctionName() {
60+
return FUNCTION_NAME;
61+
}
62+
63+
/**
64+
* Executes a binary scalar function modulo() between the first and the second
65+
* values in the list. Returns the modulo between the given values. All values
66+
* are number values. The method does not validate the parameters,
67+
* the user must check the parameters before calling this method.
68+
*
69+
* @param values A list of values on which the scalar function is performed.
70+
*
71+
*
72+
* @return Value with the modulo between two given values, or number null value
73+
* if one of the values is null.
74+
*/
75+
public Value evaluate(List<Value> values) {
76+
if (values.get(0).isNull() || values.get(1).isNull()) {
77+
return NumberValue.getNullValue();
78+
}
79+
double modulo = ((NumberValue) values.get(0)).getValue() %
80+
((NumberValue) values.get(1)).getValue();
81+
return new NumberValue(modulo);
82+
}
83+
84+
/**
85+
* Returns the return type of the function. In this case, NUMBER. The method
86+
* does not validate the parameters, the user must check the parameters
87+
* before calling this method.
88+
*
89+
* @param types A list of the types of the scalar function parameters.
90+
*
91+
* @return The type of the returned value: Number.
92+
*/
93+
public ValueType getReturnType(List<ValueType> types) {
94+
return ValueType.NUMBER;
95+
}
96+
97+
/**
98+
* Validates that all function parameters are of type NUMBER, and that there
99+
* are exactly 2 parameters. Throws a ScalarFunctionException otherwise.
100+
*
101+
* @param types A list with parameters types.
102+
*
103+
* @throws InvalidQueryException Thrown if the parameters are invalid.
104+
*/
105+
public void validateParameters(List<ValueType> types) throws InvalidQueryException {
106+
if (types.size() != 2) {
107+
throw new InvalidQueryException("The function " + FUNCTION_NAME
108+
+ " requires 2 parmaeters ");
109+
}
110+
for (ValueType type : types) {
111+
if (type != ValueType.NUMBER) {
112+
throw new InvalidQueryException("Can't perform the function "
113+
+ FUNCTION_NAME + " on values that are not numbers");
114+
}
115+
}
116+
}
117+
118+
/**
119+
* {@inheritDoc}
120+
*/
121+
public String toQueryString(List<String> argumentsQueryStrings) {
122+
return "(" + argumentsQueryStrings.get(0) + " % " + argumentsQueryStrings.get(1) + ")";
123+
}
124+
}

src/test/java/com/google/visualization/datasource/query/engine/QueryEngineTest.java

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@
4646
import com.google.visualization.datasource.query.scalarfunction.Constant;
4747
import com.google.visualization.datasource.query.scalarfunction.DateDiff;
4848
import com.google.visualization.datasource.query.scalarfunction.Difference;
49+
import com.google.visualization.datasource.query.scalarfunction.Modulo;
4950
import com.google.visualization.datasource.query.scalarfunction.Product;
5051
import com.google.visualization.datasource.query.scalarfunction.Quotient;
5152
import com.google.visualization.datasource.query.scalarfunction.Sum;
@@ -1339,6 +1340,103 @@ public void testFilterArithmeticExpression() throws Exception {
13391340
resultStrings[1]);
13401341
}
13411342

1343+
public void testModuloInSelection() throws Exception {
1344+
DataTable res = MockDataSource.getData(0);
1345+
1346+
// selection with modulo ('weight' is a column id): "select weight, weight % 10"
1347+
Query q = new Query();
1348+
1349+
AbstractColumn col1 = new ScalarFunctionColumn(
1350+
Lists.<AbstractColumn>newArrayList(), new Constant(new NumberValue(10)));
1351+
AbstractColumn col2 = new ScalarFunctionColumn(
1352+
Lists.<AbstractColumn>newArrayList(new SimpleColumn("weight"), col1),
1353+
Modulo.getInstance());
1354+
1355+
// Add selection
1356+
QuerySelection selection = new QuerySelection();
1357+
selection.addColumn(new SimpleColumn("weight"));
1358+
selection.addColumn(col2);
1359+
q.setSelection(selection);
1360+
1361+
q.validate();
1362+
1363+
DataTable result = QueryEngine.executeQuery(q, res, ULocale.US);
1364+
1365+
// Test column description
1366+
List<ColumnDescription> cols = result.getColumnDescriptions();
1367+
1368+
assertEquals(2, cols.size());
1369+
assertEquals("weight", cols.get(0).getId());
1370+
assertEquals("modulo_weight,10.0_", cols.get(1).getId());
1371+
1372+
String[][] resultStrings = MockDataSource.queryResultToStringMatrix(result);
1373+
assertEquals(7, resultStrings.length);
1374+
1375+
assertStringArraysEqual(new String[]{"222.0", "2.0"},
1376+
resultStrings[0]);
1377+
assertStringArraysEqual(new String[]{"111.0", "1.0"},
1378+
resultStrings[1]);
1379+
assertStringArraysEqual(new String[]{"333.0", "3.0"},
1380+
resultStrings[2]);
1381+
assertStringArraysEqual(new String[]{"222.0", "2.0"},
1382+
resultStrings[3]);
1383+
assertStringArraysEqual(new String[]{"1234.0", "4.0"},
1384+
resultStrings[4]);
1385+
assertStringArraysEqual(new String[]{"1234.0", "4.0"},
1386+
resultStrings[5]);
1387+
assertStringArraysEqual(new String[]{"222.0", "2.0"},
1388+
resultStrings[6]);
1389+
}
1390+
1391+
public void testModuloInFilter() throws Exception {
1392+
DataTable res = MockDataSource.getData(0);
1393+
1394+
// Create a query with filter clause using module:
1395+
// select name, weight where weight%2=0.0
1396+
Query q = new Query();
1397+
1398+
// Add selection
1399+
QuerySelection selection = new QuerySelection();
1400+
selection.addColumn(new SimpleColumn("name"));
1401+
selection.addColumn(new SimpleColumn("weight"));
1402+
q.setSelection(selection);
1403+
1404+
AbstractColumn col1 = new ScalarFunctionColumn(
1405+
Lists.<AbstractColumn>newArrayList(), new Constant(new NumberValue(2)));
1406+
AbstractColumn col2 = new ScalarFunctionColumn(
1407+
Lists.<AbstractColumn>newArrayList(new SimpleColumn("weight"), col1),
1408+
Modulo.getInstance());
1409+
1410+
QueryFilter filter = new ColumnValueFilter(col2,
1411+
new NumberValue(0), ComparisonFilter.Operator.EQ);
1412+
q.setFilter(filter);
1413+
1414+
q.validate();
1415+
1416+
DataTable result = QueryEngine.executeQuery(q, res, ULocale.US);
1417+
1418+
// Test column description
1419+
List<ColumnDescription> cols = result.getColumnDescriptions();
1420+
1421+
assertEquals(2, cols.size());
1422+
assertEquals("name", cols.get(0).getId());
1423+
assertEquals("weight", cols.get(1).getId());
1424+
1425+
String[][] resultStrings = MockDataSource.queryResultToStringMatrix(result);
1426+
assertEquals(5, resultStrings.length);
1427+
1428+
assertStringArraysEqual(new String[]{"aaa", "222.0"},
1429+
resultStrings[0]);
1430+
assertStringArraysEqual(new String[]{"ddd", "222.0"},
1431+
resultStrings[1]);
1432+
assertStringArraysEqual(new String[]{"eee", "1234.0"},
1433+
resultStrings[2]);
1434+
assertStringArraysEqual(new String[]{"eee", "1234.0"},
1435+
resultStrings[3]);
1436+
assertStringArraysEqual(new String[]{"bbb", "222.0"},
1437+
resultStrings[4]);
1438+
}
1439+
13421440
public void testLabels() throws Exception {
13431441
Query query = new Query();
13441442

0 commit comments

Comments
 (0)