Skip to content

Commit b29a2cb

Browse files
committed
SQL: Check case where the pivot limit is reached (#47121)
In some cases, the fetch size affects the way the groups are returned causing the last page to go beyond the limit. Add dedicated check to prevent extra data from being returned. Fix #47002 (cherry picked from commit f4c2964)
1 parent 9b4f377 commit b29a2cb

File tree

3 files changed

+83
-46
lines changed

3 files changed

+83
-46
lines changed

x-pack/plugin/sql/qa/src/main/java/org/elasticsearch/xpack/sql/qa/jdbc/FetchSizeTestCase.java

Lines changed: 47 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -156,20 +156,7 @@ private void assertNestedDocuments(ResultSet rs, int i) throws SQLException {
156156
* page size affects the result not the intermediate query.
157157
*/
158158
public void testPivotPaging() throws Exception {
159-
Request request = new Request("PUT", "/test_pivot/_bulk");
160-
request.addParameter("refresh", "true");
161-
StringBuilder bulk = new StringBuilder();
162-
String[] continent = new String[] { "AF", "AS", "EU", "NA", "SA", "AQ", "AU" };
163-
for (int i = 0; i <= 100; i++) {
164-
bulk.append("{\"index\":{}}\n");
165-
bulk.append("{\"item\":").append(i % 10)
166-
.append(", \"entry\":").append(i)
167-
.append(", \"amount\" : ").append(randomInt(999))
168-
.append(", \"location\" : \"").append(continent[i % (continent.length)]).append("\"")
169-
.append("}\n");
170-
}
171-
request.setJsonEntity(bulk.toString());
172-
assertEquals(200, client().performRequest(request).getStatusLine().getStatusCode());
159+
addPivotData();
173160

174161
try (Connection c = esJdbc();
175162
Statement s = c.createStatement()) {
@@ -204,4 +191,50 @@ public void testPivotPaging() throws Exception {
204191
}
205192
assertNoSearchContexts();
206193
}
194+
195+
196+
public void testPivotPagingWithLimit() throws Exception {
197+
addPivotData();
198+
199+
try (Connection c = esJdbc();
200+
Statement s = c.createStatement()) {
201+
202+
// run a query with a limit that is not a multiple of the fetch size
203+
String query = "SELECT * FROM "
204+
+ "(SELECT item, amount, location FROM test_pivot)"
205+
+ " PIVOT (AVG(amount) FOR location IN ( 'EU', 'NA' ) ) LIMIT 5";
206+
// set size smaller than an agg page
207+
s.setFetchSize(20);
208+
try (ResultSet rs = s.executeQuery(query)) {
209+
assertEquals(3, rs.getMetaData().getColumnCount());
210+
for (int i = 0; i < 4; i++) {
211+
assertTrue(rs.next());
212+
assertEquals(2, rs.getFetchSize());
213+
assertEquals(Long.valueOf(i), rs.getObject("item"));
214+
}
215+
// last entry
216+
assertTrue(rs.next());
217+
assertEquals(1, rs.getFetchSize());
218+
assertFalse("LIMIT should be reached", rs.next());
219+
}
220+
}
221+
assertNoSearchContexts();
222+
}
223+
224+
private void addPivotData() throws Exception {
225+
Request request = new Request("PUT", "/test_pivot/_bulk");
226+
request.addParameter("refresh", "true");
227+
StringBuilder bulk = new StringBuilder();
228+
String[] continent = new String[] { "AF", "AS", "EU", "NA", "SA", "AQ", "AU" };
229+
for (int i = 0; i <= 100; i++) {
230+
bulk.append("{\"index\":{}}\n");
231+
bulk.append("{\"item\":").append(i % 10)
232+
.append(", \"entry\":").append(i)
233+
.append(", \"amount\" : ").append(randomInt(999))
234+
.append(", \"location\" : \"").append(continent[i % (continent.length)]).append("\"")
235+
.append("}\n");
236+
}
237+
request.setJsonEntity(bulk.toString());
238+
assertEquals(200, client().performRequest(request).getStatusLine().getStatusCode());
239+
}
207240
}

x-pack/plugin/sql/qa/src/main/resources/pivot.csv-spec

Lines changed: 22 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -114,18 +114,16 @@ null |48396.28571428572|62140.666666666664
114114
1 |49767.22222222222|47073.25
115115
;
116116

117-
// AwaitsFix https://github.com/elastic/elasticsearch/issues/47002
118-
// averageWithOneValueAndOrder
119-
// schema::languages:bt|'F':d
120-
// SELECT * FROM (SELECT languages, gender, salary FROM test_emp) PIVOT (AVG(salary) FOR gender IN ('F')) ORDER BY languages DESC LIMIT 4;
121-
//
122-
// languages | 'F'
123-
// ---------------+------------------
124-
// 5 |46705.555555555555
125-
// 4 |49291.5
126-
// 3 |53660.0
127-
// 2 |50684.4
128-
// ;
117+
averageWithOneValueAndOrder
118+
schema::languages:bt|'F':d
119+
SELECT * FROM (SELECT languages, gender, salary FROM test_emp) PIVOT (AVG(salary) FOR gender IN ('F')) ORDER BY languages DESC LIMIT 4;
120+
languages | 'F'
121+
---------------+------------------
122+
5 |46705.555555555555
123+
4 |49291.5
124+
3 |53660.0
125+
2 |50684.4
126+
;
129127

130128
averageWithTwoValuesAndOrderDesc
131129
schema::languages:bt|'M':d|'F':d
@@ -165,20 +163,18 @@ null |48396.28571428572|62140.666666666664
165163
5 |39052.875 |46705.555555555555
166164
;
167165

168-
// AwaitsFix https://github.com/elastic/elasticsearch/issues/47002
169-
// sumWithoutSubquery
170-
// schema::birth_date:ts|emp_no:i|first_name:s|gender:s|hire_date:ts|last_name:s|1:i|2:i
171-
// SELECT * FROM test_emp PIVOT (SUM(salary) FOR languages IN (1, 2)) LIMIT 5;
172-
//
173-
// birth_date | emp_no | first_name | gender | hire_date | last_name | 1 | 2
174-
// ---------------------+---------------+---------------+---------------+---------------------+---------------+---------------+---------------
175-
// null |10041 |Uri |F |1989-11-12 00:00:00.0|Lenart |56415 |null
176-
// null |10043 |Yishay |M |1990-10-20 00:00:00.0|Tzvieli |34341 |null
177-
// null |10044 |Mingsen |F |1994-05-21 00:00:00.0|Casley |39728 |null
178-
// 1952-04-19 00:00:00.0|10009 |Sumant |F |1985-02-18 00:00:00.0|Peac |66174 |null
179-
// 1953-01-07 00:00:00.0|10067 |Claudi |M |1987-03-04 00:00:00.0|Stavenow |null |52044
180-
// 1953-01-23 00:00:00.0|10019 |Lillian |null |1999-04-30 00:00:00.0|Haddadi |73717 |null
181-
// ;
166+
sumWithoutSubquery
167+
schema::birth_date:ts|emp_no:i|first_name:s|gender:s|hire_date:ts|last_name:s|1:i|2:i
168+
SELECT * FROM test_emp PIVOT (SUM(salary) FOR languages IN (1, 2)) LIMIT 5;
169+
170+
birth_date | emp_no | first_name | gender | hire_date | last_name | 1 | 2
171+
---------------------+---------------+---------------+---------------+---------------------+---------------+---------------+---------------
172+
null |10041 |Uri |F |1989-11-12 00:00:00.0|Lenart |56415 |null
173+
null |10043 |Yishay |M |1990-10-20 00:00:00.0|Tzvieli |34341 |null
174+
null |10044 |Mingsen |F |1994-05-21 00:00:00.0|Casley |39728 |null
175+
1952-04-19 00:00:00.0|10009 |Sumant |F |1985-02-18 00:00:00.0|Peac |66174 |null
176+
1953-01-07 00:00:00.0|10067 |Claudi |M |1987-03-04 00:00:00.0|Stavenow |null |52044
177+
;
182178

183179
averageWithOneValueAndMath
184180
schema::languages:bt|'F':d

x-pack/plugin/sql/src/main/java/org/elasticsearch/xpack/sql/execution/search/PivotRowSet.java

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,10 @@ class PivotRowSet extends SchemaCompositeAggRowSet {
6060
currentRowGroupKey = key;
6161
// save the data
6262
data.add(currentRow);
63+
64+
if (limit > 0 && data.size() == limit) {
65+
break;
66+
}
6367
// create a new row
6468
currentRow = new Object[columnCount()];
6569
}
@@ -76,19 +80,23 @@ class PivotRowSet extends SchemaCompositeAggRowSet {
7680
}
7781
}
7882

79-
// add the last group if any of the following matches:
80-
// a. the last key has been sent before (it's the last page)
81-
if ((previousLastKey != null && sameCompositeKey(previousLastKey, currentRowGroupKey))) {
83+
// check the last group using the following:
84+
// a. limit has been reached, the rest of the data is ignored.
85+
if (limit > 0 && data.size() == limit) {
86+
afterKey = null;
87+
}
88+
// b. the last key has been sent before (it's the last page)
89+
else if ((previousLastKey != null && sameCompositeKey(previousLastKey, currentRowGroupKey))) {
8290
data.add(currentRow);
8391
afterKey = null;
8492
}
85-
// b. all the values are initialized (there might be another page but no need to ask for the group again)
86-
// c. or no data was added (typically because there's a null value such as the group)
93+
// c. all the values are initialized (there might be another page but no need to ask for the group again)
94+
// d. or no data was added (typically because there's a null value such as the group)
8795
else if (hasNull(currentRow) == false || data.isEmpty()) {
8896
data.add(currentRow);
8997
afterKey = currentRowGroupKey;
9098
}
91-
//otherwise we can't tell whether it's complete or not
99+
// otherwise we can't tell whether it's complete or not
92100
// so discard the last group and ask for it on the next page
93101
else {
94102
afterKey = lastCompletedGroupKey;

0 commit comments

Comments
 (0)