This is an automated email from the ASF dual-hosted git repository.

lidavidm pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/arrow.git


The following commit(s) were added to refs/heads/main by this push:
     new 17a536839e GH-40893: [Java][FlightRPC] Support 
IntervalMonthDayNanoVector in FlightSQL JDBC Driver (#40894)
17a536839e is described below

commit 17a536839ee20f80e80f93ec6ea714a301d12fdf
Author: Paul <[email protected]>
AuthorDate: Sun Mar 31 10:11:08 2024 -0500

    GH-40893: [Java][FlightRPC] Support IntervalMonthDayNanoVector in FlightSQL 
JDBC Driver (#40894)
    
    ### Rationale for this change
    
    Fixes https://github.com/apache/arrow/issues/40893.
    
    ### What changes are included in this PR?
    
     - Support IntervalMonthDayNanoVector in FlightSQL JDBC Driver
     - Return PeriodDuration as JDBC Object type, because there is no good 
java.time type for this interval
     - Return an ISO-8601 interval as the stringified version of PeriodDuration
     - Make PeriodDuration implement TemporalAccessor for standardization
    
    ### Are these changes tested?
    
    Unit tests have been added that match those for other interval types.  I'm 
unaware of any other types of tests worth adding to, but I'd be happy to if 
pointed there.
    
    ### Are there any user-facing changes?
    
    The only change users should noticed is that the FlightSQL JDBC Driver can 
now handle more query responses.
    * GitHub Issue: #40893
    
    Authored-by: paul <[email protected]>
    Signed-off-by: David Li <[email protected]>
---
 .../accessor/ArrowFlightJdbcAccessorFactory.java   |  4 ++
 .../ArrowFlightJdbcIntervalVectorAccessor.java     | 32 ++++++++++
 .../ArrowFlightJdbcAccessorFactoryTest.java        | 14 +++++
 .../ArrowFlightJdbcIntervalVectorAccessorTest.java | 51 ++++++++++++++-
 .../org/apache/arrow/vector/PeriodDuration.java    | 73 +++++++++++++++++++++-
 .../apache/arrow/vector/TestPeriodDuration.java    | 47 ++++++++++++++
 6 files changed, 217 insertions(+), 4 deletions(-)

diff --git 
a/java/flight/flight-sql-jdbc-core/src/main/java/org/apache/arrow/driver/jdbc/accessor/ArrowFlightJdbcAccessorFactory.java
 
b/java/flight/flight-sql-jdbc-core/src/main/java/org/apache/arrow/driver/jdbc/accessor/ArrowFlightJdbcAccessorFactory.java
index 813b40a807..fa45d7a867 100644
--- 
a/java/flight/flight-sql-jdbc-core/src/main/java/org/apache/arrow/driver/jdbc/accessor/ArrowFlightJdbcAccessorFactory.java
+++ 
b/java/flight/flight-sql-jdbc-core/src/main/java/org/apache/arrow/driver/jdbc/accessor/ArrowFlightJdbcAccessorFactory.java
@@ -51,6 +51,7 @@ import org.apache.arrow.vector.Float4Vector;
 import org.apache.arrow.vector.Float8Vector;
 import org.apache.arrow.vector.IntVector;
 import org.apache.arrow.vector.IntervalDayVector;
+import org.apache.arrow.vector.IntervalMonthDayNanoVector;
 import org.apache.arrow.vector.IntervalYearVector;
 import org.apache.arrow.vector.LargeVarBinaryVector;
 import org.apache.arrow.vector.LargeVarCharVector;
@@ -176,6 +177,9 @@ public class ArrowFlightJdbcAccessorFactory {
     } else if (vector instanceof IntervalYearVector) {
       return new ArrowFlightJdbcIntervalVectorAccessor(((IntervalYearVector) 
vector), getCurrentRow,
           setCursorWasNull);
+    } else if (vector instanceof IntervalMonthDayNanoVector) {
+      return new 
ArrowFlightJdbcIntervalVectorAccessor(((IntervalMonthDayNanoVector) vector), 
getCurrentRow,
+          setCursorWasNull);
     } else if (vector instanceof StructVector) {
       return new ArrowFlightJdbcStructVectorAccessor((StructVector) vector, 
getCurrentRow,
           setCursorWasNull);
diff --git 
a/java/flight/flight-sql-jdbc-core/src/main/java/org/apache/arrow/driver/jdbc/accessor/impl/calendar/ArrowFlightJdbcIntervalVectorAccessor.java
 
b/java/flight/flight-sql-jdbc-core/src/main/java/org/apache/arrow/driver/jdbc/accessor/impl/calendar/ArrowFlightJdbcIntervalVectorAccessor.java
index 21d1c15712..90b53bc856 100644
--- 
a/java/flight/flight-sql-jdbc-core/src/main/java/org/apache/arrow/driver/jdbc/accessor/impl/calendar/ArrowFlightJdbcIntervalVectorAccessor.java
+++ 
b/java/flight/flight-sql-jdbc-core/src/main/java/org/apache/arrow/driver/jdbc/accessor/impl/calendar/ArrowFlightJdbcIntervalVectorAccessor.java
@@ -30,8 +30,11 @@ import 
org.apache.arrow.driver.jdbc.accessor.ArrowFlightJdbcAccessor;
 import org.apache.arrow.driver.jdbc.accessor.ArrowFlightJdbcAccessorFactory;
 import org.apache.arrow.vector.BaseFixedWidthVector;
 import org.apache.arrow.vector.IntervalDayVector;
+import org.apache.arrow.vector.IntervalMonthDayNanoVector;
 import org.apache.arrow.vector.IntervalYearVector;
+import org.apache.arrow.vector.PeriodDuration;
 import org.apache.arrow.vector.holders.NullableIntervalDayHolder;
+import org.apache.arrow.vector.holders.NullableIntervalMonthDayNanoHolder;
 import org.apache.arrow.vector.holders.NullableIntervalYearHolder;
 
 /**
@@ -96,6 +99,35 @@ public class ArrowFlightJdbcIntervalVectorAccessor extends 
ArrowFlightJdbcAccess
     objectClass = java.time.Period.class;
   }
 
+  /**
+   * Instantiate an accessor for a {@link IntervalMonthDayNanoVector}.
+   *
+   * @param vector             an instance of a IntervalMonthDayNanoVector.
+   * @param currentRowSupplier the supplier to track the rows.
+   * @param setCursorWasNull   the consumer to set if value was null.
+   */
+  public ArrowFlightJdbcIntervalVectorAccessor(IntervalMonthDayNanoVector 
vector,
+                                               IntSupplier currentRowSupplier,
+                                               
ArrowFlightJdbcAccessorFactory.WasNullConsumer setCursorWasNull) {
+    super(currentRowSupplier, setCursorWasNull);
+    this.vector = vector;
+    stringGetter = (index) -> {
+      final NullableIntervalMonthDayNanoHolder holder = new 
NullableIntervalMonthDayNanoHolder();
+      vector.get(index, holder);
+      if (holder.isSet == 0) {
+        return null;
+      } else {
+        final int months = holder.months;
+        final int days = holder.days;
+        final long nanos = holder.nanoseconds;
+        final Period period = Period.ofMonths(months).plusDays(days);
+        final Duration duration = Duration.ofNanos(nanos);
+        return new PeriodDuration(period, duration).toISO8601IntervalString();
+      }
+    };
+    objectClass = PeriodDuration.class;
+  }
+
   @Override
   public Class<?> getObjectClass() {
     return objectClass;
diff --git 
a/java/flight/flight-sql-jdbc-core/src/test/java/org/apache/arrow/driver/jdbc/accessor/ArrowFlightJdbcAccessorFactoryTest.java
 
b/java/flight/flight-sql-jdbc-core/src/test/java/org/apache/arrow/driver/jdbc/accessor/ArrowFlightJdbcAccessorFactoryTest.java
index 4b3744372c..ab7f215f5d 100644
--- 
a/java/flight/flight-sql-jdbc-core/src/test/java/org/apache/arrow/driver/jdbc/accessor/ArrowFlightJdbcAccessorFactoryTest.java
+++ 
b/java/flight/flight-sql-jdbc-core/src/test/java/org/apache/arrow/driver/jdbc/accessor/ArrowFlightJdbcAccessorFactoryTest.java
@@ -41,6 +41,7 @@ import 
org.apache.arrow.driver.jdbc.accessor.impl.text.ArrowFlightJdbcVarCharVec
 import org.apache.arrow.driver.jdbc.utils.RootAllocatorTestRule;
 import org.apache.arrow.vector.DurationVector;
 import org.apache.arrow.vector.IntervalDayVector;
+import org.apache.arrow.vector.IntervalMonthDayNanoVector;
 import org.apache.arrow.vector.IntervalYearVector;
 import org.apache.arrow.vector.LargeVarCharVector;
 import org.apache.arrow.vector.ValueVector;
@@ -405,6 +406,19 @@ public class ArrowFlightJdbcAccessorFactoryTest {
     }
   }
 
+  @Test
+  public void createAccessorForIntervalMonthDayNanoVector() {
+    try (ValueVector valueVector = new IntervalMonthDayNanoVector("",
+        rootAllocatorTestRule.getRootAllocator())) {
+      ArrowFlightJdbcAccessor accessor =
+          ArrowFlightJdbcAccessorFactory.createAccessor(valueVector, 
GET_CURRENT_ROW,
+              (boolean wasNull) -> {
+              });
+
+      Assert.assertTrue(accessor instanceof 
ArrowFlightJdbcIntervalVectorAccessor);
+    }
+  }
+
   @Test
   public void createAccessorForUnionVector() {
     try (ValueVector valueVector = new UnionVector("", 
rootAllocatorTestRule.getRootAllocator(),
diff --git 
a/java/flight/flight-sql-jdbc-core/src/test/java/org/apache/arrow/driver/jdbc/accessor/impl/calendar/ArrowFlightJdbcIntervalVectorAccessorTest.java
 
b/java/flight/flight-sql-jdbc-core/src/test/java/org/apache/arrow/driver/jdbc/accessor/impl/calendar/ArrowFlightJdbcIntervalVectorAccessorTest.java
index 322b7d40bd..956738168f 100644
--- 
a/java/flight/flight-sql-jdbc-core/src/test/java/org/apache/arrow/driver/jdbc/accessor/impl/calendar/ArrowFlightJdbcIntervalVectorAccessorTest.java
+++ 
b/java/flight/flight-sql-jdbc-core/src/test/java/org/apache/arrow/driver/jdbc/accessor/impl/calendar/ArrowFlightJdbcIntervalVectorAccessorTest.java
@@ -24,6 +24,7 @@ import static org.hamcrest.CoreMatchers.is;
 
 import java.time.Duration;
 import java.time.Period;
+import java.time.format.DateTimeParseException;
 import java.util.Arrays;
 import java.util.Collection;
 import java.util.function.Supplier;
@@ -32,7 +33,9 @@ import 
org.apache.arrow.driver.jdbc.accessor.ArrowFlightJdbcAccessorFactory;
 import org.apache.arrow.driver.jdbc.utils.AccessorTestUtils;
 import org.apache.arrow.driver.jdbc.utils.RootAllocatorTestRule;
 import org.apache.arrow.vector.IntervalDayVector;
+import org.apache.arrow.vector.IntervalMonthDayNanoVector;
 import org.apache.arrow.vector.IntervalYearVector;
+import org.apache.arrow.vector.PeriodDuration;
 import org.apache.arrow.vector.ValueVector;
 import org.junit.After;
 import org.junit.Assert;
@@ -66,6 +69,9 @@ public class ArrowFlightJdbcIntervalVectorAccessorTest {
         } else if (vector instanceof IntervalYearVector) {
           return new 
ArrowFlightJdbcIntervalVectorAccessor((IntervalYearVector) vector,
               getCurrentRow, noOpWasNullConsumer);
+        } else if (vector instanceof IntervalMonthDayNanoVector) {
+          return new 
ArrowFlightJdbcIntervalVectorAccessor((IntervalMonthDayNanoVector) vector,
+                  getCurrentRow, noOpWasNullConsumer);
         }
         return null;
       };
@@ -98,6 +104,17 @@ public class ArrowFlightJdbcIntervalVectorAccessorTest {
           }
           return vector;
         }, "IntervalYearVector"},
+        {(Supplier<ValueVector>) () -> {
+          IntervalMonthDayNanoVector vector =
+                  new IntervalMonthDayNanoVector("", 
rootAllocatorTestRule.getRootAllocator());
+
+          int valueCount = 10;
+          vector.setValueCount(valueCount);
+          for (int i = 0; i < valueCount; i++) {
+            vector.set(i, i + 1, (i + 1) * 10, (i + 1) * 100);
+          }
+          return vector;
+        }, "IntervalMonthDayNanoVector"},
     });
   }
 
@@ -137,13 +154,31 @@ public class ArrowFlightJdbcIntervalVectorAccessorTest {
   }
 
   private String getStringOnVector(ValueVector vector, int index) {
-    String object = getExpectedObject(vector, index).toString();
+    Object object = getExpectedObject(vector, index);
     if (object == null) {
       return null;
     } else if (vector instanceof IntervalDayVector) {
-      return formatIntervalDay(Duration.parse(object));
+      return formatIntervalDay(Duration.parse(object.toString()));
     } else if (vector instanceof IntervalYearVector) {
-      return formatIntervalYear(Period.parse(object));
+      return formatIntervalYear(Period.parse(object.toString()));
+    } else if (vector instanceof IntervalMonthDayNanoVector) {
+      String iso8601IntervalString = ((PeriodDuration) 
object).toISO8601IntervalString();
+      String[] periodAndDuration = iso8601IntervalString.split("T");
+      if (periodAndDuration.length == 1) {
+        // If there is no 'T', then either Period or Duration is zero, and the 
other one will successfully parse it
+        String periodOrDuration = periodAndDuration[0];
+        try {
+          return new PeriodDuration(Period.parse(periodOrDuration), 
Duration.ZERO).toISO8601IntervalString();
+        } catch (DateTimeParseException e) {
+          return new PeriodDuration(Period.ZERO, 
Duration.parse(periodOrDuration)).toISO8601IntervalString();
+        }
+      } else {
+        // If there is a 'T', both Period and Duration are non-zero, and we 
just need to prepend the 'PT' to the
+        // duration for both to parse successfully
+        Period parse = Period.parse(periodAndDuration[0]);
+        Duration duration = Duration.parse("PT" + periodAndDuration[1]);
+        return new PeriodDuration(parse, duration).toISO8601IntervalString();
+      }
     }
     return null;
   }
@@ -225,6 +260,8 @@ public class ArrowFlightJdbcIntervalVectorAccessorTest {
       return Duration.class;
     } else if (vector instanceof IntervalYearVector) {
       return Period.class;
+    } else if (vector instanceof IntervalMonthDayNanoVector) {
+      return PeriodDuration.class;
     }
     return null;
   }
@@ -239,6 +276,10 @@ public class ArrowFlightJdbcIntervalVectorAccessorTest {
       for (int i = 0; i < valueCount; i++) {
         ((IntervalYearVector) vector).setNull(i);
       }
+    } else if (vector instanceof IntervalMonthDayNanoVector) {
+      for (int i = 0; i < valueCount; i++) {
+        ((IntervalMonthDayNanoVector) vector).setNull(i);
+      }
     }
   }
 
@@ -247,6 +288,10 @@ public class ArrowFlightJdbcIntervalVectorAccessorTest {
       return Duration.ofDays(currentRow + 1).plusMillis((currentRow + 1) * 
1000L);
     } else if (vector instanceof IntervalYearVector) {
       return Period.ofMonths(currentRow + 1);
+    } else if (vector instanceof IntervalMonthDayNanoVector) {
+      Period period = Period.ofMonths(currentRow + 1).plusDays((currentRow + 
1) * 10L);
+      Duration duration = Duration.ofNanos((currentRow + 1) * 100L);
+      return new PeriodDuration(period, duration);
     }
     return null;
   }
diff --git 
a/java/vector/src/main/java/org/apache/arrow/vector/PeriodDuration.java 
b/java/vector/src/main/java/org/apache/arrow/vector/PeriodDuration.java
index ee48fe7972..c94e4b534c 100644
--- a/java/vector/src/main/java/org/apache/arrow/vector/PeriodDuration.java
+++ b/java/vector/src/main/java/org/apache/arrow/vector/PeriodDuration.java
@@ -17,8 +17,22 @@
 
 package org.apache.arrow.vector;
 
+import static java.time.temporal.ChronoUnit.DAYS;
+import static java.time.temporal.ChronoUnit.MONTHS;
+import static java.time.temporal.ChronoUnit.NANOS;
+import static java.time.temporal.ChronoUnit.SECONDS;
+import static java.time.temporal.ChronoUnit.YEARS;
+
 import java.time.Duration;
 import java.time.Period;
+import java.time.temporal.ChronoUnit;
+import java.time.temporal.Temporal;
+import java.time.temporal.TemporalAmount;
+import java.time.temporal.TemporalUnit;
+import java.time.temporal.UnsupportedTemporalTypeException;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
 
 import org.apache.arrow.util.Preconditions;
 
@@ -26,7 +40,10 @@ import org.apache.arrow.util.Preconditions;
  * Combination of Period and Duration for representing this interval type
  * as a POJO.
  */
-public class PeriodDuration {
+public class PeriodDuration implements TemporalAmount {
+
+  private static final List<TemporalUnit> SUPPORTED_UNITS =
+          Collections.unmodifiableList(Arrays.<TemporalUnit>asList(YEARS, 
MONTHS, DAYS, SECONDS, NANOS));
   private final Period period;
   private final Duration duration;
 
@@ -43,6 +60,60 @@ public class PeriodDuration {
     return duration;
   }
 
+  @Override
+  public long get(TemporalUnit unit) {
+    if (unit instanceof ChronoUnit) {
+      switch ((ChronoUnit) unit) {
+        case YEARS:
+          return period.getYears();
+        case MONTHS:
+          return period.getMonths();
+        case DAYS:
+          return period.getDays();
+        case SECONDS:
+          return duration.getSeconds();
+        case NANOS:
+          return duration.getNano();
+        default:
+          break;
+      }
+    }
+    throw new UnsupportedTemporalTypeException("Unsupported TemporalUnit: " + 
unit);
+  }
+
+  @Override
+  public List<TemporalUnit> getUnits() {
+    return SUPPORTED_UNITS;
+  }
+
+  @Override
+  public Temporal addTo(Temporal temporal) {
+    return temporal.plus(period).plus(duration);
+  }
+
+  @Override
+  public Temporal subtractFrom(Temporal temporal) {
+    return temporal.minus(period).minus(duration);
+  }
+
+  /**
+   * Format this PeriodDuration as an ISO-8601 interval.
+   *
+   * @return An ISO-8601 formatted string representing the interval.
+   */
+  public String toISO8601IntervalString() {
+    if (duration.isZero()) {
+      return period.toString();
+    }
+    String durationString = duration.toString();
+    if (period.isZero()) {
+      return durationString;
+    }
+
+    // Remove 'P' from duration string and concatenate to produce an ISO-8601 
representation
+    return period + durationString.substring(1);
+  }
+
   @Override
   public String toString() {
     return period.toString() + " " + duration.toString();
diff --git 
a/java/vector/src/test/java/org/apache/arrow/vector/TestPeriodDuration.java 
b/java/vector/src/test/java/org/apache/arrow/vector/TestPeriodDuration.java
index c8965dec3b..2b9f4cca8c 100644
--- a/java/vector/src/test/java/org/apache/arrow/vector/TestPeriodDuration.java
+++ b/java/vector/src/test/java/org/apache/arrow/vector/TestPeriodDuration.java
@@ -21,7 +21,10 @@ import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotEquals;
 
 import java.time.Duration;
+import java.time.LocalDate;
+import java.time.LocalDateTime;
 import java.time.Period;
+import java.time.temporal.ChronoUnit;
 
 import org.junit.Test;
 
@@ -43,4 +46,48 @@ public class TestPeriodDuration {
     assertNotEquals(pd1.hashCode(), pd3.hashCode());
   }
 
+  @Test
+  public void testToISO8601IntervalString() {
+    assertEquals("P0D",
+            new PeriodDuration(Period.ZERO, 
Duration.ZERO).toISO8601IntervalString());
+    assertEquals("P1Y2M3D",
+            new PeriodDuration(Period.of(1, 2, 3), 
Duration.ZERO).toISO8601IntervalString());
+    assertEquals("PT0.000000123S",
+            new PeriodDuration(Period.ZERO, 
Duration.ofNanos(123)).toISO8601IntervalString());
+    assertEquals("PT1.000000123S",
+            new PeriodDuration(Period.ZERO, 
Duration.ofSeconds(1).withNanos(123)).toISO8601IntervalString());
+    assertEquals("PT1H1.000000123S",
+            new PeriodDuration(Period.ZERO, 
Duration.ofSeconds(3601).withNanos(123)).toISO8601IntervalString());
+    assertEquals("PT24H1M1.000000123S",
+            new PeriodDuration(Period.ZERO, 
Duration.ofSeconds(86461).withNanos(123)).toISO8601IntervalString());
+    assertEquals("P1Y2M3DT24H1M1.000000123S",
+            new PeriodDuration(Period.of(1, 2, 3), 
Duration.ofSeconds(86461).withNanos(123)).toISO8601IntervalString());
+
+    assertEquals("P-1Y-2M-3D",
+            new PeriodDuration(Period.of(-1, -2, -3), 
Duration.ZERO).toISO8601IntervalString());
+    assertEquals("PT-0.000000123S",
+            new PeriodDuration(Period.ZERO, 
Duration.ofNanos(-123)).toISO8601IntervalString());
+    assertEquals("PT-24H-1M-0.999999877S",
+            new PeriodDuration(Period.ZERO, 
Duration.ofSeconds(-86461).withNanos(123)).toISO8601IntervalString());
+    assertEquals("P-1Y-2M-3DT-0.999999877S",
+            new PeriodDuration(Period.of(-1, -2, -3), 
Duration.ofSeconds(-1).withNanos(123)).toISO8601IntervalString());
+  }
+
+  @Test
+  public void testTemporalAccessor() {
+    LocalDate date = LocalDate.of(2024, 1, 2);
+    PeriodDuration pd1 = new PeriodDuration(Period.ofYears(1), Duration.ZERO);
+    assertEquals(LocalDate.of(2025, 1, 2), pd1.addTo(date));
+
+    LocalDateTime dateTime = LocalDateTime.of(2024, 1, 2, 3, 4);
+    PeriodDuration pd2 = new PeriodDuration(Period.ZERO, 
Duration.ofMinutes(1));
+    assertEquals(LocalDateTime.of(2024, 1, 2, 3, 3), 
pd2.subtractFrom(dateTime));
+
+    PeriodDuration pd3 = new PeriodDuration(Period.of(1, 2, 3), 
Duration.ofSeconds(86461).withNanos(123));
+    assertEquals(pd3.get(ChronoUnit.YEARS), 1);
+    assertEquals(pd3.get(ChronoUnit.MONTHS), 2);
+    assertEquals(pd3.get(ChronoUnit.DAYS), 3);
+    assertEquals(pd3.get(ChronoUnit.SECONDS), 86461);
+    assertEquals(pd3.get(ChronoUnit.NANOS), 123);
+  }
 }

Reply via email to