Skip to content

Commit f2b9253

Browse files
committed
260 updates
1 parent cc94f23 commit f2b9253

File tree

2 files changed

+82
-23
lines changed

2 files changed

+82
-23
lines changed

force-app/repository/Cursor.cls

Lines changed: 21 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,11 @@
1-
@SuppressWarnings('PMD.EmptyCatchBlock,PMD.EmptyStatementBlock')
21
public virtual without sharing class Cursor {
3-
private static final Integer MAX_FETCH_SIZE = 2000;
2+
@TestVisible
3+
private static Integer maxFetchSize = 2000;
44
private static final Integer MAX_FETCHES_PER_TRANSACTION = Limits.getLimitFetchCallsOnApexCursor();
5+
@TestVisible
6+
private static Integer localFetchesMade;
57

8+
private Integer cursorNumRecords;
69
private Integer fetchesPerTransaction = 1;
710
private final Database.Cursor cursor;
811

@@ -45,33 +48,41 @@ public virtual without sharing class Cursor {
4548
System.debug(System.LoggingLevel.DEBUG, 'Bypassing fetch call, no records to fetch');
4649
return new List<SObject>();
4750
}
48-
Integer localFetchesMade = 0;
51+
localFetchesMade = localFetchesMade ?? 0;
4952
Integer localStart = start;
5053
List<SObject> results = new List<SObject>();
51-
while (localFetchesMade < this.fetchesPerTransaction) {
52-
results.addAll(this.cursor?.fetch(start, this.getAdvanceBy(start, advanceBy)) ?? new List<SObject>());
53-
localStart += advanceBy;
54+
while (
55+
localFetchesMade < this.fetchesPerTransaction &&
56+
results.size() < this.getNumRecords() &&
57+
localStart < start + advanceBy
58+
) {
59+
Integer actualAdvanceBy = this.getAdvanceBy(localStart, advanceBy);
60+
results.addAll(this.cursor?.fetch(localStart, actualAdvanceBy) ?? new List<SObject>());
61+
localStart += actualAdvanceBy;
5462
localFetchesMade++;
5563
}
5664
return results;
5765
}
5866

5967
public virtual Integer getNumRecords() {
60-
return this.cursor?.getNumRecords() ?? 0;
68+
// cache the value for now, as changes made to Apex Cursors currently have calling getNumRecords()
69+
// consume a SOQL query each time
70+
this.cursorNumRecords = this.cursorNumRecords ?? this.cursor?.getNumRecords() ?? 0;
71+
return this.cursorNumRecords;
6172
}
6273

6374
protected Integer getAdvanceBy(Integer start, Integer advanceBy) {
6475
Integer possibleFetchSize = Math.min(advanceBy, this.getNumRecords() - start);
65-
if (possibleFetchSize > MAX_FETCH_SIZE) {
76+
if (possibleFetchSize > maxFetchSize) {
6677
System.debug(
6778
System.LoggingLevel.DEBUG,
6879
'Fetch size: ' +
6980
possibleFetchSize +
7081
' exceeded platform max fetch size of ' +
71-
MAX_FETCH_SIZE +
82+
maxFetchSize +
7283
', defaulting to max fetch size'
7384
);
74-
possibleFetchSize = MAX_FETCH_SIZE;
85+
possibleFetchSize = maxFetchSize;
7586
} else if (possibleFetchSize < 0) {
7687
possibleFetchSize = 0;
7788
}

force-app/repository/CursorTest.cls

Lines changed: 61 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -2,47 +2,95 @@
22
private class CursorTest {
33
@IsTest
44
static void itCapsAdvanceByArgument() {
5-
String query = 'SELECT Id FROM User WHERE Id = :bindVar0';
6-
Map<String, Object> bindVars = new Map<String, Object>{ 'bindVar0' => UserInfo.getUserId() };
5+
String accountName = 'helloWorld!';
6+
insert new Account(Name = accountName);
7+
String query = 'SELECT Name FROM Account WHERE Name = :bindVar0';
8+
Map<String, Object> bindVars = new Map<String, Object>{ 'bindVar0' => accountName };
79

8-
Cursor cursor = new Cursor(query, bindVars, System.AccessLevel.SYSTEM_MODE);
10+
Cursor instance = new Cursor(query, bindVars, System.AccessLevel.SYSTEM_MODE);
911

10-
Assert.areEqual(1, cursor.getNumRecords());
11-
Assert.areEqual(UserInfo.getUserId(), cursor.fetch(0, 1000).get(0).Id);
12+
Assert.areEqual(1, instance.getNumRecords());
13+
Assert.areEqual(accountName, instance.fetch(0, 1000).get(0).get('Name'));
1214
Assert.areEqual(1, System.Limits.getApexCursorRows());
1315
}
1416

1517
@IsTest
1618
static void itCapsMaxFetchSize() {
19+
Cursor.maxFetchSize = 20;
1720
List<Account> accounts = new List<Account>();
18-
for (Integer i = 0; i < 2001; i++) {
21+
for (Integer i = 0; i < Cursor.maxFetchSize + 1; i++) {
1922
accounts.add(new Account(Name = 'Fetch ' + i));
2023
}
2124
insert accounts;
2225

23-
Integer oneMoreThanMaxFetch = 2001;
26+
Integer oneMoreThanMaxFetch = Cursor.maxFetchSize + 1;
2427
Exception ex;
28+
List<SObject> results;
29+
Cursor instance = new Cursor('SELECT Id FROM Account', new Map<String, Object>(), System.AccessLevel.SYSTEM_MODE);
2530
try {
26-
new Cursor('SELECT Id FROM Account', new Map<String, Object>(), System.AccessLevel.SYSTEM_MODE)
27-
.fetch(0, oneMoreThanMaxFetch);
31+
results = instance.fetch(0, oneMoreThanMaxFetch);
2832
} catch (System.InvalidParameterValueException e) {
2933
ex = e;
3034
}
3135

3236
Assert.areEqual(null, ex?.getMessage());
37+
Assert.areEqual(Cursor.maxFetchSize, results.size());
38+
Assert.areEqual(1, Cursor.localFetchesMade);
39+
}
40+
41+
@IsTest
42+
static void itFetchesMultipleTimesPerTransactionWhenMoreThanMaxFetch() {
43+
Cursor.maxFetchSize = 20;
44+
List<Account> accounts = new List<Account>();
45+
Set<String> expectedFetchNames = new Set<String>();
46+
for (Integer i = 0; i < Cursor.maxFetchSize + 1; i++) {
47+
String accountName = 'Fetch' + i;
48+
expectedFetchNames.add(accountName);
49+
accounts.add(new Account(Name = accountName));
50+
}
51+
insert accounts;
52+
53+
Integer oneMoreThanMaxFetch = Cursor.maxFetchSize + 1;
54+
Cursor instance = new Cursor('SELECT Name FROM Account', new Map<String, Object>(), System.AccessLevel.SYSTEM_MODE);
55+
List<SObject> results = instance.setFetchesPerTransaction(2).fetch(0, oneMoreThanMaxFetch);
56+
57+
Assert.areEqual(Cursor.maxFetchSize + 1, results.size());
58+
Assert.areEqual(2, Cursor.localFetchesMade);
59+
Set<String> actuallyFetchedNames = new Set<String>();
60+
for (Account account : (List<Account>) results) {
61+
actuallyFetchedNames.add(account.Name);
62+
}
63+
Assert.areEqual(expectedFetchNames, actuallyFetchedNames);
3364
}
3465

3566
@IsTest
3667
static void itFetchesMultipleTimesPerTransaction() {
68+
Cursor.maxFetchSize = 1;
3769
insert new List<Account>{ new Account(Name = 'One'), new Account(Name = 'Two') };
3870

39-
Cursor cursor = new Cursor('SELECT Id FROM Account', new Map<String, Object>(), System.AccessLevel.SYSTEM_MODE)
71+
Cursor instance = new Cursor('SELECT Id FROM Account', new Map<String, Object>(), System.AccessLevel.SYSTEM_MODE)
4072
.setFetchesPerTransaction(2);
41-
List<SObject> results = cursor.fetch(0, 1);
73+
List<SObject> results = instance.fetch(0, 2);
4274

75+
Assert.areEqual(2, instance.getNumRecords());
4376
Assert.areEqual(2, results.size());
44-
results = cursor.fetch(2, 1);
77+
results = instance.fetch(2, 1);
4578
Assert.areEqual(0, results.size());
46-
Assert.areEqual(2, cursor.getNumRecords());
79+
}
80+
81+
@IsTest
82+
static void fetchesCorrectAmountOfRecords() {
83+
List<Account> accounts = new List<Account>();
84+
for (Integer i = 0; i < 10; i++) {
85+
accounts.add(new Account(Name = 'Fetch ' + i));
86+
}
87+
insert accounts;
88+
89+
Cursor instance = new Cursor('SELECT Id FROM Account', new Map<String, Object>(), System.AccessLevel.SYSTEM_MODE)
90+
.setFetchesPerTransaction(10);
91+
List<SObject> results = instance.fetch(0, 2);
92+
93+
Assert.areEqual(2, results.size(), '' + results);
94+
Assert.areEqual(1, Cursor.localFetchesMade);
4795
}
4896
}

0 commit comments

Comments
 (0)