20
20
21
21
package com .apple .foundationdb .record .provider .foundationdb .query ;
22
22
23
+ import com .apple .foundationdb .record .ByteScanLimiterFactory ;
23
24
import com .apple .foundationdb .record .EvaluationContext ;
24
25
import com .apple .foundationdb .record .ExecuteProperties ;
25
26
import com .apple .foundationdb .record .ExecuteState ;
26
27
import com .apple .foundationdb .record .RecordCursor ;
28
+ import com .apple .foundationdb .record .RecordCursorContinuation ;
29
+ import com .apple .foundationdb .record .RecordCursorEndContinuation ;
27
30
import com .apple .foundationdb .record .RecordCursorResult ;
31
+ import com .apple .foundationdb .record .RecordCursorStartContinuation ;
28
32
import com .apple .foundationdb .record .RecordMetaData ;
29
33
import com .apple .foundationdb .record .RecordScanLimiterFactory ;
30
34
import com .apple .foundationdb .record .TestRecords1Proto ;
52
56
import org .junit .jupiter .api .Assertions ;
53
57
import org .junit .jupiter .api .BeforeEach ;
54
58
import org .junit .jupiter .api .Tag ;
59
+ import org .junit .jupiter .api .Test ;
55
60
import org .junit .jupiter .params .ParameterizedTest ;
61
+ import org .junit .jupiter .params .provider .Arguments ;
62
+ import org .junit .jupiter .params .provider .MethodSource ;
56
63
57
64
import javax .annotation .Nonnull ;
65
+ import javax .annotation .Nullable ;
58
66
import java .util .ArrayList ;
59
67
import java .util .Arrays ;
60
68
import java .util .Collections ;
61
69
import java .util .LinkedList ;
62
70
import java .util .List ;
63
71
import java .util .function .BiConsumer ;
64
72
import java .util .function .Function ;
73
+ import java .util .stream .Stream ;
65
74
66
75
/**
67
76
* Tests related to planning and executing queries with string collation.
@@ -218,6 +227,26 @@ void aggregateThreeGroupByTwo(final boolean useNestedResult) {
218
227
}
219
228
}
220
229
230
+ @ ParameterizedTest (name = "[{displayName}-{index}] {0}" )
231
+ @ MethodSource ("provideArguments" )
232
+ void aggregateOneGroupByThree (final boolean useNestedResult , final int rowLimit ) {
233
+ // each group only has one row
234
+ try (final var context = openContext ()) {
235
+ openSimpleRecordStore (context , NO_HOOK );
236
+
237
+ final var plan =
238
+ new AggregationPlanBuilder (recordStore .getRecordMetaData (), "MySimpleRecord" )
239
+ .withAggregateValue ("num_value_2" , value -> new NumericAggregationValue .Sum (NumericAggregationValue .PhysicalOperator .SUM_I , value ))
240
+ .withGroupCriterion ("num_value_3_indexed" )
241
+ .withGroupCriterion ("str_value_indexed" )
242
+ .withGroupCriterion ("num_value_unique" )
243
+ .build (useNestedResult );
244
+
245
+ final var result = executePlanWithRowLimit (plan , rowLimit );
246
+ assertResults (useNestedResult ? this ::assertResultNested : this ::assertResultFlattened , result , resultOf (0 , "0" , 0 , 0 ), resultOf (0 , "0" , 1 , 1 ), resultOf (1 , "0" , 2 , 2 ), resultOf (1 , "1" , 3 , 3 ), resultOf (2 , "1" , 4 , 4 ), resultOf (2 , "1" , 5 , 5 ));
247
+ }
248
+ }
249
+
221
250
@ ParameterizedTest (name = "[{displayName}-{index}] {0}" )
222
251
@ BooleanSource
223
252
void aggregateNoRecords (final boolean useNestedResult ) {
@@ -285,65 +314,64 @@ void aggregateNoRecordsNoGroupNoAggregate(final boolean useNestedResult) {
285
314
}
286
315
}
287
316
288
- @ ParameterizedTest (name = "[{displayName}-{index}] {0}" )
289
- @ BooleanSource
290
- void test (final boolean useNestedResult ) {
317
+ @ Test
318
+ void aggregateHitScanLimitReached () {
291
319
try (final var context = openContext ()) {
292
320
openSimpleRecordStore (context , NO_HOOK );
293
321
294
322
final var plan =
295
323
new AggregationPlanBuilder (recordStore .getRecordMetaData (), "MySimpleRecord" )
296
324
.withAggregateValue ("num_value_2" , value -> new NumericAggregationValue .Sum (NumericAggregationValue .PhysicalOperator .SUM_I , value ))
297
325
.withGroupCriterion ("str_value_indexed" )
298
- .build (useNestedResult );
299
-
300
- /*
301
- num_value_2 str_value_indexed
302
- 0 "0"
303
- 1 "0"
304
- 2 "0"
305
- */
306
- /*
307
- RecordCursorResult<QueryResult> result1 = executePlanWithRecordScanLimit(plan, 1, null, useNestedResult,
308
- List.of(resultOf("0", 0)));
309
- RecordCursorResult<QueryResult> result2 = executePlanWithRecordScanLimit(plan, 2, null, useNestedResult,
310
- List.of(resultOf("0", 1)));
311
- // only scanned 2 rows?
312
- RecordCursorResult<QueryResult> result3 = executePlanWithRecordScanLimit(plan, 3, null, useNestedResult,
313
- List.of(resultOf("0", 1)));
314
-
315
- */
316
- // only scanned 3 rows?
317
- RecordCursorResult <QueryResult > result4 = executePlanWithRecordScanLimit (plan , 4 , null , useNestedResult ,
318
- List .of (resultOf ("0" , 3 )));
319
- /*
320
- RecordCursorResult<QueryResult> result5 = executePlanWithRecordScanLimit(plan, 5, null, useNestedResult,
321
- List.of(resultOf("0", 3), resultOf("1", 3)));
322
- RecordCursorResult<QueryResult> result6 = executePlanWithRecordScanLimit(plan, 6, null, useNestedResult,
323
- List.of(resultOf("0", 3), resultOf("1", 7)));
324
- RecordCursorResult<QueryResult> result7 = executePlanWithRecordScanLimit(plan, 7, null, useNestedResult,
325
- List.of(resultOf("0", 3), resultOf("1", 12)));
326
-
327
- */
326
+ .build (false );
327
+
328
+ // In the testing data, there are 2 groups, each group has 3 rows.
329
+ // recordScanLimit = 5: scans 3 rows, and the 4th scan hits SCAN_LIMIT_REACHED
330
+ // although the first group contains exactly 3 rows, we don't know we've finished the first group before we get to the 4th row, so nothing is returned, continuation is back to START
331
+ RecordCursorContinuation continuation1 = executePlanWithRecordScanLimit (plan , 5 , null , null );
332
+ Assertions .assertEquals (RecordCursorStartContinuation .START , continuation1 );
333
+ // recordScanLimit = 6: scans 4 rows, and the 5th scan hits SCAN_LIMIT_REACHED, we know that we've finished the 1st group, aggregated result is returned
334
+ RecordCursorContinuation continuation2 = executePlanWithRecordScanLimit (plan , 6 , continuation1 .toBytes (), resultOf ("0" , 3 ));
335
+ // continue with recordScanLimit = 5, scans 3 rows and hits SCAN_LIMIT_REACHED
336
+ // again, we don't know that we've finished the 2nd group, nothing is returned, continuation is back to where the scan starts
337
+ RecordCursorContinuation continuation3 = executePlanWithRecordScanLimit (plan , 5 , continuation2 .toBytes (), null );
338
+ Assertions .assertTrue (Arrays .equals (continuation2 .toBytes (), continuation3 .toBytes ()));
339
+ // finish the 2nd group, aggregated result is returned, exhausted the source
340
+ RecordCursorContinuation continuation4 = executePlanWithRecordScanLimit (plan , 6 , continuation3 .toBytes (), resultOf ("1" , 12 ));
341
+ Assertions .assertEquals (RecordCursorEndContinuation .END , continuation4 );
328
342
}
329
343
}
330
344
331
- private RecordCursorResult <QueryResult > executePlanWithRecordScanLimit (final RecordQueryPlan plan , final int recordScanLimit , byte [] continuation , final boolean useNestedResult , List <?> expectedResult ) {
345
+ private RecordCursorContinuation executePlanWithRecordScanLimit (final RecordQueryPlan plan , final int recordScanLimit , byte [] continuation , @ Nullable List <?> expectedResult ) {
346
+ List <QueryResult > queryResults = new LinkedList <>();
347
+ RecordCursor <QueryResult > currentCursor = executePlan (plan , 0 , recordScanLimit , continuation );
332
348
RecordCursorResult <QueryResult > currentCursorResult ;
333
- List < QueryResult > currentQueryResults = new LinkedList <>() ;
349
+ RecordCursorContinuation cursorContinuation ;
334
350
while (true ) {
335
- RecordCursor <QueryResult > currentCursor = executePlan (plan , 0 , recordScanLimit , continuation );
336
351
currentCursorResult = currentCursor .getNext ();
337
- continuation = currentCursorResult .getContinuation (). toBytes ();
352
+ cursorContinuation = currentCursorResult .getContinuation ();
338
353
if (!currentCursorResult .hasNext ()) {
339
354
break ;
340
355
}
341
- currentQueryResults .add (currentCursorResult .get ());
342
- System .out .println ("current result:" + currentCursorResult .get ().getMessage ());
356
+ queryResults .add (currentCursorResult .get ());
343
357
}
344
- assertResults (useNestedResult ? this ::assertResultNested : this ::assertResultFlattened , currentQueryResults , expectedResult .toArray (new List <?>[0 ]));
345
- System .out .println ("getNoNextReson:" + currentCursorResult .getNoNextReason ());
346
- return currentCursorResult ;
358
+ if (expectedResult == null ) {
359
+ Assertions .assertTrue (queryResults .isEmpty ());
360
+ } else {
361
+ assertResults (this ::assertResultFlattened , queryResults , expectedResult );
362
+ }
363
+ return cursorContinuation ;
364
+ }
365
+
366
+ private static Stream <Arguments > provideArguments () {
367
+ // (boolean, rowLimit)
368
+ // setting rowLimit = 0 is equivalent to no limit
369
+ List <Arguments > arguments = new LinkedList <>();
370
+ for (int i = 0 ; i <= 4 ; i ++) {
371
+ arguments .add (Arguments .of (false , i ));
372
+ arguments .add (Arguments .of (true , i ));
373
+ }
374
+ return arguments .stream ();
347
375
}
348
376
349
377
private void populateDB (final int numRecords ) throws Exception {
@@ -356,6 +384,7 @@ private void populateDB(final int numRecords) throws Exception {
356
384
recBuilder .setNumValue2 (i );
357
385
recBuilder .setNumValue3Indexed (i / 2 ); // some field that changes every 2nd record
358
386
recBuilder .setStrValueIndexed (Integer .toString (i / 3 )); // some field that changes every 3rd record
387
+ recBuilder .setNumValueUnique (i );
359
388
recordStore .saveRecord (recBuilder .build ());
360
389
}
361
390
commit (context );
@@ -368,7 +397,7 @@ private RecordCursor<QueryResult> executePlan(final RecordQueryPlan plan, final
368
397
final var typeRepository = TypeRepository .newBuilder ().addAllTypes (types ).build ();
369
398
ExecuteState executeState ;
370
399
if (recordScanLimit > 0 ) {
371
- executeState = new ExecuteState (RecordScanLimiterFactory .enforce (recordScanLimit ), null );
400
+ executeState = new ExecuteState (RecordScanLimiterFactory .enforce (recordScanLimit ), ByteScanLimiterFactory . tracking () );
372
401
} else {
373
402
executeState = ExecuteState .NO_LIMITS ;
374
403
}
@@ -392,6 +421,40 @@ private List<QueryResult> executePlan(final RecordQueryPlan plan) {
392
421
}
393
422
}
394
423
424
+ @ Nonnull
425
+ private RecordCursor <QueryResult > executePlan (final RecordQueryPlan plan , final int rowLimit , final byte [] continuation ) {
426
+ final var types = plan .getDynamicTypes ();
427
+ final var typeRepository = TypeRepository .newBuilder ().addAllTypes (types ).build ();
428
+ ExecuteProperties executeProperties = ExecuteProperties .SERIAL_EXECUTE ;
429
+ executeProperties = executeProperties .setReturnedRowLimit (rowLimit );
430
+ try {
431
+ return plan .executePlan (recordStore , EvaluationContext .forTypeRepository (typeRepository ), continuation , executeProperties );
432
+ } catch (final Throwable t ) {
433
+ throw Assertions .<RuntimeException >fail (t );
434
+ }
435
+ }
436
+
437
+ private List <QueryResult > executePlanWithRowLimit (final RecordQueryPlan plan , final int rowLimit ) {
438
+ byte [] continuation = null ;
439
+ List <QueryResult > queryResults = new LinkedList <>();
440
+ while (true ) {
441
+ RecordCursor <QueryResult > currentCursor = executePlan (plan , rowLimit , continuation );
442
+ RecordCursorResult <QueryResult > currentCursorResult ;
443
+ while (true ) {
444
+ currentCursorResult = currentCursor .getNext ();
445
+ continuation = currentCursorResult .getContinuation ().toBytes ();
446
+ if (!currentCursorResult .hasNext ()) {
447
+ break ;
448
+ }
449
+ queryResults .add (currentCursorResult .get ());
450
+ }
451
+ if (currentCursorResult .getNoNextReason () == RecordCursor .NoNextReason .SOURCE_EXHAUSTED ) {
452
+ break ;
453
+ }
454
+ }
455
+ return queryResults ;
456
+ }
457
+
395
458
private void assertResults (@ Nonnull final BiConsumer <QueryResult , List <?>> checkConsumer , @ Nonnull final List <QueryResult > actual , @ Nonnull final List <?>... expected ) {
396
459
Assertions .assertEquals (expected .length , actual .size ());
397
460
for (var i = 0 ; i < actual .size () ; i ++) {
0 commit comments