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

Commit d06e637

Browse files
Add skipping clause to query language
1 parent b7c3e37 commit d06e637

File tree

9 files changed

+357
-11
lines changed

9 files changed

+357
-11
lines changed

src/main/java/com/google/visualization/datasource/QuerySplitter.java

Lines changed: 29 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -162,6 +162,7 @@ private static QueryPair splitSQL(Query query) {
162162
dataSourceQuery.setUserFormatOptions(null);
163163

164164
try {
165+
dataSourceQuery.setRowSkipping(0);
165166
dataSourceQuery.setRowLimit(-1);
166167
dataSourceQuery.setRowOffset(0);
167168
} catch (InvalidQueryException e) {
@@ -224,11 +225,21 @@ private static QueryPair splitSQL(Query query) {
224225

225226
completionQuery.setSelection(completionSelection);
226227
} else {
227-
// When there is no pivoting, sql does everything (except options, labels, format).
228+
// When there is no pivoting, sql does everything (except skipping, options, labels, format).
228229
dataSourceQuery.copyFrom(query);
229230
dataSourceQuery.setOptions(null);
230231
completionQuery.setOptions(query.getOptions());
231232
try {
233+
// If there is skipping pagination should be done in the completion query
234+
if (query.hasRowSkipping()) {
235+
dataSourceQuery.setRowSkipping(0);
236+
dataSourceQuery.setRowLimit(-1);
237+
dataSourceQuery.setRowOffset(0);
238+
239+
completionQuery.copyRowSkipping(query);
240+
completionQuery.copyRowLimit(query);
241+
completionQuery.copyRowOffset(query);
242+
}
232243
if (query.hasLabels()) {
233244
dataSourceQuery.setLabels(null);
234245
QueryLabels labels = query.getLabels();
@@ -256,9 +267,9 @@ private static QueryPair splitSQL(Query query) {
256267

257268
/**
258269
* Splits the query for a data source with capabilities SORT_AND_PAGINATION.
259-
* Algorithm: if the query has filter, grouping, or pivoting requirements the query is
270+
* Algorithm: if the query has filter, grouping, pivoting or skipping requirements the query is
260271
* split as in the NONE case.
261-
* If the query does not have filter, grouping, or pivoting the data source query
272+
* If the query does not have filter, grouping, pivoting or skipping the data source query
262273
* receives any sorting or pagination requirements and the completion query receives
263274
* any selection requirements.
264275
*
@@ -279,9 +290,21 @@ private static QueryPair splitSortAndPagination(Query query) {
279290
// The query is copied to the completion query.
280291
completionQuery.copyFrom(query);
281292
} else {
282-
dataSourceQuery.setSort(query.getSort());
283-
dataSourceQuery.copyRowLimit(query);
284-
dataSourceQuery.copyRowOffset(query);
293+
// The execution order of the 3 relevant operators is:
294+
// sort -> skip -> paginate (limit and offset).
295+
// Skipping is not a possible data source capability, Therefore:
296+
// 1. Sorting can be performed in the data source query.
297+
// 2. Pagination should be performed in the data source query IFF skipping
298+
// isn't stated in the original query.
299+
dataSourceQuery.setSort(query.getSort());
300+
if (query.hasRowSkipping()) {
301+
completionQuery.copyRowSkipping(query);
302+
completionQuery.copyRowLimit(query);
303+
completionQuery.copyRowOffset(query);
304+
} else {
305+
dataSourceQuery.copyRowLimit(query);
306+
dataSourceQuery.copyRowOffset(query);
307+
}
285308

286309
completionQuery.setSelection(query.getSelection());
287310
completionQuery.setOptions(query.getOptions());

src/main/java/com/google/visualization/datasource/base/ErrorMessages.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,7 @@ public class ErrorMessages extends ListResourceBundle {
7575
"because SELECT contains aggregated columns."},
7676
{"NO_COL_IN_GROUP_AND_PIVOT", "Column [{0}] cannot appear both in GROUP BY and in PIVOT."},
7777
{"INVALID_OFFSET", "Invalid value for row offset: {0}"},
78+
{"INVALID_SKIPPING", "Invalid value for row skipping: {0}"},
7879
{"COLUMN_ONLY_ONCE", "Column [{0}] cannot appear more than once in {1}."}
7980

8081

src/main/java/com/google/visualization/datasource/base/MessagesEnum.java

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -147,7 +147,13 @@ public enum MessagesEnum {
147147
* @param value
148148
*/
149149
INVALID_OFFSET,
150-
150+
151+
/**
152+
* Invalid value for row skipping.
153+
* @param value
154+
*/
155+
INVALID_SKIPPING,
156+
151157
/**
152158
* Column cannot appear more than once.
153159
* @param column id
@@ -202,6 +208,8 @@ public enum MessagesEnum {
202208
"NO_COL_IN_GROUP_AND_PIVOT");
203209
QUERY_ERROR_TO_MESSAGE.put(MessagesEnum.INVALID_OFFSET,
204210
"INVALID_OFFSET");
211+
QUERY_ERROR_TO_MESSAGE.put(MessagesEnum.INVALID_SKIPPING,
212+
"INVALID_SKIPPING");
205213
QUERY_ERROR_TO_MESSAGE.put(MessagesEnum.COLUMN_ONLY_ONCE,
206214
"COLUMN_ONLY_ONCE");
207215
}

src/main/java/com/google/visualization/datasource/query/Query.java

Lines changed: 75 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@
3232
* Holds the query data and clauses. This class is the result of parsing the query
3333
* string that comes from the user.
3434
* The clauses that can be included in a query are: select, filter, sort, group by, pivot, options,
35-
* labels, format, limit, and offset.
35+
* labels, format, limit, offset and skipping.
3636
* For more details see the
3737
* <a href="http://code.google.com/apis/visualization/documentation/querylanguage.html">
3838
* query language reference
@@ -112,6 +112,17 @@ private static<T> void checkForDuplicates(List<T>
112112
*/
113113
private QueryPivot pivot = null;
114114

115+
/**
116+
* The number of rows to skip when selecting only a subset of the rows using
117+
* skipping clause, e.g., skipping 4.
118+
* If a skipping clause, "skip k", is added to the query, the resulting table
119+
* will be consisted of the first row of every k rows of the table returned by
120+
* the earlier part of the query, when k corresponds to rowSkipping value.
121+
* For example, if rowSkipping = 10 the returned table will consist of rows 0, 10...
122+
* The default value is 0, meaning no skipping should be performed.
123+
*/
124+
private int rowSkipping = 0;
125+
115126
/**
116127
* Max number of rows to return to caller.
117128
* If the caller specified this parameter, and the data table returned from
@@ -293,6 +304,59 @@ public boolean hasPivot() {
293304
return pivot != null && !pivot.getColumnIds().isEmpty();
294305
}
295306

307+
/**
308+
* Returns the number of rows to skip when selecting only a subset of
309+
* the rows using skipping clause, e.g., skipping 4.
310+
* The default value is 0, meaning no skipping should be performed.
311+
*
312+
* @return The number of rows to skip between each row selection.
313+
*/
314+
public int getRowSkipping() {
315+
return rowSkipping;
316+
}
317+
318+
/**
319+
* Sets the number of rows to skip when selecting only a subset of
320+
* the rows using skipping clause, e.g., skipping 4.
321+
* The default value is 0, meaning no skipping should be performed.
322+
*
323+
* If there is is an attempt to set the skipping value to non positive value,
324+
* then an InvalidQueryException is thrown.
325+
*
326+
* @param rowSkipping The number of rows to skip between each row selection.
327+
* 0 value means no skipping.
328+
*
329+
* @throws InvalidQueryException Thrown if an invalid value is specified.
330+
*/
331+
public void setRowSkipping(int rowSkipping) throws InvalidQueryException {
332+
if (rowSkipping < 0) {
333+
String messageToLogAndUser = MessagesEnum.INVALID_SKIPPING.getMessageWithArgs(
334+
localeForUserMessages, Integer.toString(rowSkipping));
335+
log.error(messageToLogAndUser);
336+
throw new InvalidQueryException(messageToLogAndUser);
337+
}
338+
this.rowSkipping = rowSkipping;
339+
}
340+
341+
/**
342+
* Sets the number of rows to skip when selecting only a subset of
343+
* the row, based on another query.
344+
*
345+
* @param originalQuery The query from which the row skipping should be taken.
346+
*/
347+
public void copyRowSkipping(Query originalQuery) {
348+
rowSkipping = originalQuery.getRowSkipping();
349+
}
350+
351+
/**
352+
* Returns true if this query has a row skipping set. A value of 0 means no
353+
* skipping.
354+
*
355+
* @return True if this query has a row skipping set.
356+
*/
357+
public boolean hasRowSkipping() {
358+
return rowSkipping > 0;
359+
}
296360

297361
/**
298362
* Returns the maximum number of rows to return to the caller.
@@ -498,8 +562,8 @@ public boolean hasOptions() {
498562
*/
499563
public boolean isEmpty() {
500564
return (!hasSort() && !hasSelection() && !hasFilter() && !hasGroup() && !hasPivot()
501-
&& !hasRowLimit() && !hasRowOffset() && !hasUserFormatOptions() && !hasLabels()
502-
&& !hasOptions());
565+
&& !hasRowSkipping() && !hasRowLimit() && !hasRowOffset()
566+
&& !hasUserFormatOptions() && !hasLabels() && !hasOptions());
503567
}
504568

505569

@@ -522,6 +586,7 @@ public void copyFrom(Query query) {
522586
setFilter(query.getFilter());
523587
setGroup(query.getGroup());
524588
setPivot(query.getPivot());
589+
copyRowSkipping(query);
525590
copyRowLimit(query);
526591
copyRowOffset(query);
527592
setUserFormatOptions(query.getUserFormatOptions());
@@ -925,6 +990,7 @@ public int hashCode() {
925990
result = prime * result + ((labels == null) ? 0 : labels.hashCode());
926991
result = prime * result + ((options == null) ? 0 : options.hashCode());
927992
result = prime * result + ((pivot == null) ? 0 : pivot.hashCode());
993+
result = prime * result + rowSkipping;
928994
result = prime * result + rowLimit;
929995
result = prime * result + rowOffset;
930996
result = prime * result + ((selection == null) ? 0 : selection.hashCode());
@@ -980,6 +1046,9 @@ public boolean equals(Object obj) {
9801046
} else if (!pivot.equals(other.pivot)) {
9811047
return false;
9821048
}
1049+
if (rowSkipping != other.rowSkipping) {
1050+
return false;
1051+
}
9831052
if (rowLimit != other.rowLimit) {
9841053
return false;
9851054
}
@@ -1073,6 +1142,9 @@ public String toQueryString() {
10731142
if (hasSort()) {
10741143
clauses.add("ORDER BY " + sort.toQueryString());
10751144
}
1145+
if (hasRowSkipping()) {
1146+
clauses.add("SKIPPING " + rowSkipping);
1147+
}
10761148
if (hasRowLimit()) {
10771149
clauses.add("LIMIT " + rowLimit);
10781150
}

src/main/java/com/google/visualization/datasource/query/engine/QueryEngine.java

Lines changed: 38 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@
4444

4545
import com.ibm.icu.util.ULocale;
4646

47+
import java.util.ArrayList;
4748
import java.util.Collections;
4849
import java.util.List;
4950
import java.util.Map;
@@ -55,7 +56,7 @@
5556

5657
/**
5758
* A collection of static methods that perform the operations involved in executing a query,
58-
* i.e., selection, sorting, paging (limit and offset), grouping and pivoting, filtering,
59+
* i.e., selection, sorting, paging (limit and offset), grouping and pivoting, filtering, skipping,
5960
* applying labels, and custom formatting. This also takes care of calculated columns.
6061
* This also takes care of scalar function columns.
6162
*
@@ -125,6 +126,7 @@ public static DataTable executeQuery(Query query, DataTable table, ULocale local
125126
table = performFilter(table, query);
126127
table = performGroupingAndPivoting(table, query, columnIndices, columnLookups);
127128
table = performSort(table, query, locale);
129+
table = performSkipping(table, query);
128130
table = performPagination(table, query);
129131

130132
AtomicReference<ColumnIndices> columnIndicesReference =
@@ -140,6 +142,41 @@ public static DataTable executeQuery(Query query, DataTable table, ULocale local
140142
return table;
141143
}
142144

145+
/**
146+
* Returns a table consisted of a subset of rows of the input table.
147+
* We select the first out of every k rows in the table according to the
148+
* skipping value in the query.
149+
* If there is no need to do anything, returns the original table.
150+
*
151+
* @param table The original table.
152+
* @param query The query.
153+
*
154+
* @return The skipped table, or the original if no skipping is needed.
155+
*/
156+
private static DataTable performSkipping(DataTable table, Query query)
157+
throws TypeMismatchException {
158+
int rowSkipping = query.getRowSkipping();
159+
160+
// Return the original table if no skipping is needed
161+
if (rowSkipping <= 1) {
162+
return table;
163+
}
164+
165+
// Add the first out of every k rows of the original table to TableRow
166+
int numRows = table.getNumberOfRows();
167+
List<TableRow> relevantRows = new ArrayList<TableRow>();
168+
for (int rowIndex = 0; rowIndex < numRows; rowIndex += rowSkipping) {
169+
relevantRows.add(table.getRows().get(rowIndex));
170+
}
171+
172+
// Create a table out of the TableRow array
173+
DataTable newTable = new DataTable();
174+
newTable.addColumns(table.getColumnDescriptions());
175+
newTable.addRows(relevantRows);
176+
177+
return newTable;
178+
}
179+
143180
/**
144181
* Returns a paginated table, based on the row limit and offset parameters.
145182
* If there is no need to do anything, returns the original table.

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

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,7 @@ TOKEN[IGNORE_CASE]:
107107
| <KW_PIVOT: "pivot">
108108
| <KW_ORDER: "order">
109109
| <KW_BY: "by">
110+
| <KW_SKIPPING: "skipping">
110111
| <KW_LIMIT: "limit">
111112
| <KW_OFFSET: "offset">
112113
| <KW_LABEL: "label">
@@ -208,6 +209,7 @@ Query queryStatement() throws InvalidQueryException :
208209
[ groupByClause(query) ]
209210
[ pivotClause(query) ]
210211
[ orderByClause(query) ]
212+
[ skippingClause(query) ]
211213
[ limitClause(query) ]
212214
[ offsetClause(query) ]
213215
[ labelClause(query) ]
@@ -294,6 +296,17 @@ void orderByClause(Query query) throws InvalidQueryException :
294296
{ query.setSort(sort); }
295297
}
296298

299+
// The skipping clause (e.g., SKIPPING 10)
300+
void skippingClause(Query query) throws InvalidQueryException :
301+
{
302+
int skipping;
303+
}
304+
{
305+
<KW_SKIPPING>
306+
skipping = integerLiteral()
307+
{ query.setRowSkipping(skipping); }
308+
}
309+
297310
// The limit clause (e.g., LIMIT 100)
298311
void limitClause(Query query) throws InvalidQueryException :
299312
{

0 commit comments

Comments
 (0)