@@ -347,4 +347,119 @@ void getOptionRejectsRuntimeKeys() {
347347 "expected runtime-key error, got: " + thrown .getMessage ());
348348 }
349349 }
350+
351+ // -------------------------------------------------------------------------
352+ // disk_manager surface (disable, size cap)
353+ // -------------------------------------------------------------------------
354+
355+ @ Test
356+ void disableSpillRoundTripsThroughProto () throws Exception {
357+ byte [] bytes = SessionContext .builder ().disableSpill ().toBytes ();
358+ SessionOptions parsed = SessionOptions .parseFrom (bytes );
359+ assertTrue (parsed .hasDiskManager ());
360+ assertTrue (parsed .getDiskManager ().getDisabled ());
361+ }
362+
363+ @ Test
364+ void disableSpillProducesUsableContext () throws Exception {
365+ // SELECT 1 doesn't spill, so disabling spill must not break it.
366+ try (BufferAllocator allocator = new RootAllocator ();
367+ SessionContext ctx = SessionContext .builder ().disableSpill ().build ();
368+ DataFrame df = ctx .sql ("SELECT 1" );
369+ ArrowReader reader = df .collect (allocator )) {
370+ assertTrue (reader .loadNextBatch ());
371+ }
372+ }
373+
374+ @ Test
375+ void disableSpillAndTempDirectoryConflictThrowsAtBuild () {
376+ SessionContextBuilder b = SessionContext .builder ().disableSpill ().tempDirectory ("/tmp/x" );
377+ assertThrows (IllegalStateException .class , b ::build );
378+ }
379+
380+ @ Test
381+ void maxTempDirectorySizeRoundTripsThroughProto () throws Exception {
382+ byte [] bytes = SessionContext .builder ().maxTempDirectorySize (20L << 30 ).toBytes ();
383+ SessionOptions parsed = SessionOptions .parseFrom (bytes );
384+ assertTrue (parsed .hasDiskManager ());
385+ assertTrue (parsed .getDiskManager ().hasMaxTempDirectorySize ());
386+ assertEquals (20L << 30 , parsed .getDiskManager ().getMaxTempDirectorySize ());
387+ }
388+
389+ @ Test
390+ void maxTempDirectorySizeRejectsNegative () {
391+ SessionContextBuilder b = SessionContext .builder ();
392+ assertThrows (IllegalArgumentException .class , () -> b .maxTempDirectorySize (-1 ));
393+ }
394+
395+ @ Test
396+ void maxTempDirectorySizeZeroBuildsCleanly () throws Exception {
397+ // Upstream allows 0 -- "no spill allowed" -- so the Java setter mirrors
398+ // that. Sanity-check that the context constructs and SELECT 1 (which
399+ // doesn't spill) still works.
400+ try (BufferAllocator allocator = new RootAllocator ();
401+ SessionContext ctx = SessionContext .builder ().maxTempDirectorySize (0 ).build ();
402+ DataFrame df = ctx .sql ("SELECT 1" );
403+ ArrowReader reader = df .collect (allocator )) {
404+ assertTrue (reader .loadNextBatch ());
405+ }
406+ }
407+
408+ @ Test
409+ void disableSpillCombinesWithMaxTempDirectorySize () throws Exception {
410+ // The cap is a no-op when spill is disabled (no directory to cap), but
411+ // setting both must not throw -- callers may have code that always
412+ // configures the cap and conditionally disables spill.
413+ byte [] bytes = SessionContext .builder ().disableSpill ().maxTempDirectorySize (1L << 30 ).toBytes ();
414+ SessionOptions parsed = SessionOptions .parseFrom (bytes );
415+ assertTrue (parsed .getDiskManager ().getDisabled ());
416+ assertTrue (parsed .getDiskManager ().hasMaxTempDirectorySize ());
417+ }
418+
419+ @ Test
420+ void diskManagerFieldIsAbsentWhenNothingSet () throws Exception {
421+ // Existing-callers-see-no-change contract: a builder that doesn't touch
422+ // any of the new disk_manager setters produces no disk_manager field on
423+ // the wire.
424+ byte [] bytes = SessionContext .builder ().batchSize (8192 ).toBytes ();
425+ SessionOptions parsed = SessionOptions .parseFrom (bytes );
426+ assertFalse (parsed .hasDiskManager ());
427+ }
428+
429+ @ Test
430+ void tempDirectoryStaysOnLegacyField () throws Exception {
431+ // tempDirectory(String) writes the existing SessionOptions.temp_directory
432+ // field, not disk_manager -- bytes identical to pre-PR behaviour for
433+ // builders that touch only that setter.
434+ byte [] bytes = SessionContext .builder ().tempDirectory ("/tmp/df" ).toBytes ();
435+ SessionOptions parsed = SessionOptions .parseFrom (bytes );
436+ assertTrue (parsed .hasTempDirectory ());
437+ assertEquals ("/tmp/df" , parsed .getTempDirectory ());
438+ assertFalse (parsed .hasDiskManager ());
439+ }
440+
441+ @ Test
442+ void disableSpillUnderMemoryPressureThrowsResourcesExhausted () throws java .io .IOException {
443+ // Pin the Javadoc claim on disableSpill() / maxTempDirectorySize that
444+ // out-of-memory queries surface as ResourcesExhaustedException -- not
445+ // the parent DataFusionException, not a generic RuntimeException. A 1 MiB
446+ // pool plus a 200k-row sort plus no spill forces the OOM path
447+ // deterministically.
448+ try (BufferAllocator allocator = new RootAllocator ();
449+ SessionContext ctx =
450+ SessionContext .builder ().memoryLimit (1L << 20 , 1.0 ).disableSpill ().build ()) {
451+ DataFusionException e =
452+ assertThrows (
453+ DataFusionException .class ,
454+ () -> {
455+ try (DataFrame df =
456+ ctx .sql ("SELECT i FROM generate_series(1, 200000) AS t(i) ORDER BY i DESC" )) {
457+ df .collect (allocator ).close ();
458+ }
459+ });
460+ assertTrue (
461+ e instanceof ResourcesExhaustedException ,
462+ "expected ResourcesExhaustedException, got " + e .getClass ().getName ());
463+ }
464+ }
350465}
0 commit comments