Skip to content

Commit 8ea08fa

Browse files
authored
Implement scanner API for single CRUD transactions (#2701)
1 parent 29ca31b commit 8ea08fa

File tree

3 files changed

+213
-6
lines changed

3 files changed

+213
-6
lines changed

core/src/main/java/com/scalar/db/transaction/singlecrudoperation/SingleCrudOperationTransactionManager.java

Lines changed: 38 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
import com.scalar.db.api.UpdateIfExists;
2828
import com.scalar.db.api.Upsert;
2929
import com.scalar.db.common.AbstractDistributedTransactionManager;
30+
import com.scalar.db.common.AbstractTransactionManagerCrudOperableScanner;
3031
import com.scalar.db.common.error.CoreError;
3132
import com.scalar.db.config.DatabaseConfig;
3233
import com.scalar.db.exception.storage.ExecutionException;
@@ -165,7 +166,43 @@ public List<Result> scan(Scan scan) throws CrudException {
165166

166167
@Override
167168
public Scanner getScanner(Scan scan) throws CrudException {
168-
throw new UnsupportedOperationException("Implement later");
169+
scan = copyAndSetTargetToIfNot(scan);
170+
171+
com.scalar.db.api.Scanner scanner;
172+
try {
173+
scanner = storage.scan(scan);
174+
} catch (ExecutionException e) {
175+
throw new CrudException(e.getMessage(), e, null);
176+
}
177+
178+
return new AbstractTransactionManagerCrudOperableScanner() {
179+
@Override
180+
public Optional<Result> one() throws CrudException {
181+
try {
182+
return scanner.one();
183+
} catch (ExecutionException e) {
184+
throw new CrudException(e.getMessage(), e, null);
185+
}
186+
}
187+
188+
@Override
189+
public List<Result> all() throws CrudException {
190+
try {
191+
return scanner.all();
192+
} catch (ExecutionException e) {
193+
throw new CrudException(e.getMessage(), e, null);
194+
}
195+
}
196+
197+
@Override
198+
public void close() throws CrudException {
199+
try {
200+
scanner.close();
201+
} catch (IOException e) {
202+
throw new CrudException(e.getMessage(), e, null);
203+
}
204+
}
205+
};
169206
}
170207

171208
/** @deprecated As of release 3.13.0. Will be removed in release 5.0.0. */

core/src/test/java/com/scalar/db/transaction/singlecrudoperation/SingleCrudOperationTransactionManagerTest.java

Lines changed: 175 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
import com.scalar.db.api.Result;
1919
import com.scalar.db.api.Scan;
2020
import com.scalar.db.api.Scanner;
21+
import com.scalar.db.api.TransactionManagerCrudOperable;
2122
import com.scalar.db.api.Update;
2223
import com.scalar.db.api.Upsert;
2324
import com.scalar.db.config.DatabaseConfig;
@@ -28,7 +29,9 @@
2829
import com.scalar.db.exception.transaction.TransactionException;
2930
import com.scalar.db.exception.transaction.UnsatisfiedConditionException;
3031
import com.scalar.db.io.Key;
32+
import java.io.IOException;
3133
import java.util.Arrays;
34+
import java.util.Iterator;
3235
import java.util.List;
3336
import java.util.Optional;
3437
import org.junit.jupiter.api.BeforeEach;
@@ -151,6 +154,178 @@ public void scan_ExecutionExceptionThrownByStorage_ShouldThrowCrudException()
151154
.hasCause(exception);
152155
}
153156

157+
@Test
158+
public void getScannerAndScannerOne_ShouldReturnScannerAndShouldReturnProperResult()
159+
throws ExecutionException, TransactionException, IOException {
160+
// Arrange
161+
Scan scan =
162+
Scan.newBuilder().namespace("ns").table("tbl").partitionKey(Key.ofInt("id", 0)).build();
163+
164+
Result result1 = mock(Result.class);
165+
Result result2 = mock(Result.class);
166+
Result result3 = mock(Result.class);
167+
168+
Scanner scanner = mock(Scanner.class);
169+
when(scanner.one())
170+
.thenReturn(Optional.of(result1))
171+
.thenReturn(Optional.of(result2))
172+
.thenReturn(Optional.of(result3))
173+
.thenReturn(Optional.empty());
174+
175+
when(storage.scan(scan)).thenReturn(scanner);
176+
177+
// Act Assert
178+
TransactionManagerCrudOperable.Scanner actual = transactionManager.getScanner(scan);
179+
assertThat(actual.one()).hasValue(result1);
180+
assertThat(actual.one()).hasValue(result2);
181+
assertThat(actual.one()).hasValue(result3);
182+
assertThat(actual.one()).isEmpty();
183+
actual.close();
184+
185+
verify(storage).scan(scan);
186+
verify(scanner).close();
187+
}
188+
189+
@Test
190+
public void getScannerAndScannerAll_ShouldReturnScannerAndShouldReturnProperResults()
191+
throws ExecutionException, TransactionException, IOException {
192+
// Arrange
193+
Scan scan =
194+
Scan.newBuilder().namespace("ns").table("tbl").partitionKey(Key.ofInt("id", 0)).build();
195+
196+
Result result1 = mock(Result.class);
197+
Result result2 = mock(Result.class);
198+
Result result3 = mock(Result.class);
199+
200+
Scanner scanner = mock(Scanner.class);
201+
when(scanner.all()).thenReturn(Arrays.asList(result1, result2, result3));
202+
203+
when(storage.scan(scan)).thenReturn(scanner);
204+
205+
// Act Assert
206+
TransactionManagerCrudOperable.Scanner actual = transactionManager.getScanner(scan);
207+
assertThat(actual.all()).containsExactly(result1, result2, result3);
208+
actual.close();
209+
210+
verify(storage).scan(scan);
211+
verify(scanner).close();
212+
}
213+
214+
@Test
215+
public void getScannerAndScannerIterator_ShouldReturnScannerAndShouldReturnProperResults()
216+
throws ExecutionException, TransactionException, IOException {
217+
// Arrange
218+
Scan scan =
219+
Scan.newBuilder().namespace("ns").table("tbl").partitionKey(Key.ofInt("id", 0)).build();
220+
221+
Result result1 = mock(Result.class);
222+
Result result2 = mock(Result.class);
223+
Result result3 = mock(Result.class);
224+
225+
Scanner scanner = mock(Scanner.class);
226+
when(scanner.one())
227+
.thenReturn(Optional.of(result1))
228+
.thenReturn(Optional.of(result2))
229+
.thenReturn(Optional.of(result3))
230+
.thenReturn(Optional.empty());
231+
232+
when(storage.scan(scan)).thenReturn(scanner);
233+
234+
// Act Assert
235+
TransactionManagerCrudOperable.Scanner actual = transactionManager.getScanner(scan);
236+
237+
Iterator<Result> iterator = actual.iterator();
238+
assertThat(iterator.hasNext()).isTrue();
239+
assertThat(iterator.next()).isEqualTo(result1);
240+
assertThat(iterator.hasNext()).isTrue();
241+
assertThat(iterator.next()).isEqualTo(result2);
242+
assertThat(iterator.hasNext()).isTrue();
243+
assertThat(iterator.next()).isEqualTo(result3);
244+
assertThat(iterator.hasNext()).isFalse();
245+
actual.close();
246+
247+
verify(storage).scan(scan);
248+
verify(scanner).close();
249+
}
250+
251+
@Test
252+
public void getScanner_WhenExecutionExceptionThrownByJdbcService_ShouldThrowCrudException()
253+
throws ExecutionException {
254+
// Arrange
255+
Scan scan =
256+
Scan.newBuilder().namespace("ns").table("tbl").partitionKey(Key.ofInt("id", 0)).build();
257+
258+
ExecutionException executionException = mock(ExecutionException.class);
259+
when(executionException.getMessage()).thenReturn("error");
260+
when(storage.scan(scan)).thenThrow(executionException);
261+
262+
// Act Assert
263+
assertThatThrownBy(() -> transactionManager.getScanner(scan)).isInstanceOf(CrudException.class);
264+
}
265+
266+
@Test
267+
public void
268+
getScannerAndScannerOne_WhenExecutionExceptionThrownByScannerOne_ShouldThrowCrudException()
269+
throws ExecutionException, CrudException {
270+
// Arrange
271+
Scan scan =
272+
Scan.newBuilder().namespace("ns").table("tbl").partitionKey(Key.ofInt("id", 0)).build();
273+
274+
Scanner scanner = mock(Scanner.class);
275+
276+
ExecutionException executionException = mock(ExecutionException.class);
277+
when(executionException.getMessage()).thenReturn("error");
278+
when(scanner.one()).thenThrow(executionException);
279+
280+
when(storage.scan(scan)).thenReturn(scanner);
281+
282+
// Act Assert
283+
TransactionManagerCrudOperable.Scanner actual = transactionManager.getScanner(scan);
284+
assertThatThrownBy(actual::one).isInstanceOf(CrudException.class);
285+
}
286+
287+
@Test
288+
public void
289+
getScannerAndScannerAll_WhenExecutionExceptionThrownByScannerAll_ShouldThrowCrudException()
290+
throws ExecutionException, CrudException {
291+
// Arrange
292+
Scan scan =
293+
Scan.newBuilder().namespace("ns").table("tbl").partitionKey(Key.ofInt("id", 0)).build();
294+
295+
Scanner scanner = mock(Scanner.class);
296+
297+
ExecutionException executionException = mock(ExecutionException.class);
298+
when(executionException.getMessage()).thenReturn("error");
299+
when(scanner.all()).thenThrow(executionException);
300+
301+
when(storage.scan(scan)).thenReturn(scanner);
302+
303+
// Act Assert
304+
TransactionManagerCrudOperable.Scanner actual = transactionManager.getScanner(scan);
305+
assertThatThrownBy(actual::all).isInstanceOf(CrudException.class);
306+
}
307+
308+
@Test
309+
public void
310+
getScannerAndScannerClose_WhenIOExceptionThrownByScannerClose_ShouldThrowCrudException()
311+
throws ExecutionException, CrudException, IOException {
312+
// Arrange
313+
Scan scan =
314+
Scan.newBuilder().namespace("ns").table("tbl").partitionKey(Key.ofInt("id", 0)).build();
315+
316+
Scanner scanner = mock(Scanner.class);
317+
318+
IOException ioException = mock(IOException.class);
319+
when(ioException.getMessage()).thenReturn("error");
320+
doThrow(ioException).when(scanner).close();
321+
322+
when(storage.scan(scan)).thenReturn(scanner);
323+
324+
// Act Assert
325+
TransactionManagerCrudOperable.Scanner actual = transactionManager.getScanner(scan);
326+
assertThatThrownBy(actual::close).isInstanceOf(CrudException.class);
327+
}
328+
154329
@Test
155330
public void put_ShouldCallStorageProperly() throws ExecutionException, TransactionException {
156331
// Arrange

integration-test/src/main/java/com/scalar/db/transaction/singlecrudoperation/SingleCrudOperationTransactionIntegrationTestBase.java

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -410,11 +410,6 @@ public void abort_forOngoingTransaction_ShouldAbortCorrectly() {}
410410
@Test
411411
public void rollback_forOngoingTransaction_ShouldRollbackCorrectly() {}
412412

413-
@Disabled("Implement later")
414-
@Override
415-
@Test
416-
public void manager_getScanner_ScanGivenForCommittedRecord_ShouldReturnRecords() {}
417-
418413
@Disabled(
419414
"Single CRUD operation transactions don't support executing multiple mutations in a transaction")
420415
@Override

0 commit comments

Comments
 (0)