diff --git a/src/main/java/org/duckdb/DuckDBAppender.java b/src/main/java/org/duckdb/DuckDBAppender.java index 97e7b4582..063429781 100644 --- a/src/main/java/org/duckdb/DuckDBAppender.java +++ b/src/main/java/org/duckdb/DuckDBAppender.java @@ -54,6 +54,7 @@ public class DuckDBAppender implements AutoCloseable { supportedTypes.add(DUCKDB_TYPE_ARRAY.typeId); supportedTypes.add(DUCKDB_TYPE_LIST.typeId); + supportedTypes.add(DUCKDB_TYPE_MAP.typeId); supportedTypes.add(DUCKDB_TYPE_STRUCT.typeId); supportedTypes.add(DUCKDB_TYPE_UNION.typeId); @@ -67,6 +68,7 @@ public class DuckDBAppender implements AutoCloseable { DUCKDB_TYPE_TIMESTAMP_S, DUCKDB_TYPE_TIMESTAMP_MS, DUCKDB_TYPE_TIMESTAMP, DUCKDB_TYPE_TIMESTAMP_NS}; private static final CAPIType[] timestampMicrosTypes = new CAPIType[] {DUCKDB_TYPE_TIMESTAMP, DUCKDB_TYPE_TIMESTAMP_TZ}; + private static final CAPIType[] collectionTypes = new CAPIType[] {DUCKDB_TYPE_ARRAY, DUCKDB_TYPE_LIST}; private static final int STRING_MAX_INLINE_BYTES = 12; @@ -210,32 +212,8 @@ public DuckDBAppender beginUnion(String tag) throws SQLException { if (!rowBegunInvariant()) { throw new SQLException(createErrMsg("'beginRow' must be called before calling 'beginUnion'")); } - checkCurrentColumnType(DUCKDB_TYPE_UNION); - - int fieldWithTag = 0; - for (int i = 1; i < currentColumn.children.size(); i++) { - Column childCol = currentColumn.children.get(i); - if (childCol.structFieldName.equals(tag)) { - fieldWithTag = i; - } - } - if (0 == fieldWithTag) { - throw new SQLException(createErrMsg("specified union field not found, value: '" + tag + "'")); - } - - // set tag - Column structCol = currentColumn; - this.currentColumn = currentColumn.children.get(0); - append((byte) (fieldWithTag - 1)); - // set other fields to NULL - for (int i = 1; i < structCol.children.size(); i++) { - if (i == fieldWithTag) { - continue; - } - Column childCol = structCol.children.get(i); - childCol.setNull(rowIdx); - } - this.currentColumn = structCol.children.get(fieldWithTag); + Column col = currentColumn(DUCKDB_TYPE_UNION); + this.currentColumn = putUnionTag(col, rowIdx, tag); return this; } @@ -344,10 +322,10 @@ public boolean isClosed() throws SQLException { // append primitives public DuckDBAppender append(boolean value) throws SQLException { - Column col = currentColumnWithRowPos(DUCKDB_TYPE_BOOLEAN); + Column col = currentColumn(DUCKDB_TYPE_BOOLEAN); byte val = (byte) (value ? 1 : 0); - col.data.put(val); - incrementColOrStructFieldIdx(); + putByte(col, rowIdx, val); + moveToNextColumn(); return this; } @@ -357,51 +335,51 @@ public DuckDBAppender append(char value) throws SQLException { } public DuckDBAppender append(byte value) throws SQLException { - Column col = currentColumnWithRowPos(int8Types); - col.data.put(value); - incrementColOrStructFieldIdx(); + Column col = currentColumn(int8Types); + putByte(col, rowIdx, value); + moveToNextColumn(); return this; } public DuckDBAppender append(short value) throws SQLException { - Column col = currentColumnWithRowPos(int16Types); - col.data.putShort(value); - incrementColOrStructFieldIdx(); + Column col = currentColumn(int16Types); + putShort(col, rowIdx, value); + moveToNextColumn(); return this; } public DuckDBAppender append(int value) throws SQLException { - Column col = currentColumnWithRowPos(int32Types); - col.data.putInt(value); - incrementColOrStructFieldIdx(); + Column col = currentColumn(int32Types); + putInt(col, rowIdx, value); + moveToNextColumn(); return this; } public DuckDBAppender append(long value) throws SQLException { - Column col = currentColumnWithRowPos(int64Types); - col.data.putLong(value); - incrementColOrStructFieldIdx(); + Column col = currentColumn(int64Types); + putLong(col, rowIdx, value); + moveToNextColumn(); return this; } public DuckDBAppender append(float value) throws SQLException { - Column col = currentColumnWithRowPos(DUCKDB_TYPE_FLOAT); - col.data.putFloat(value); - incrementColOrStructFieldIdx(); + Column col = currentColumn(DUCKDB_TYPE_FLOAT); + putFloat(col, rowIdx, value); + moveToNextColumn(); return this; } public DuckDBAppender append(double value) throws SQLException { - Column col = currentColumnWithRowPos(DUCKDB_TYPE_DOUBLE); - col.data.putDouble(value); - incrementColOrStructFieldIdx(); + Column col = currentColumn(DUCKDB_TYPE_DOUBLE); + putDouble(col, rowIdx, value); + moveToNextColumn(); return this; } // append primitive wrappers, int128 and decimal public DuckDBAppender append(Boolean value) throws SQLException { - checkCurrentColumnType(DUCKDB_TYPE_BOOLEAN); + currentColumn(DUCKDB_TYPE_BOOLEAN); if (value == null) { return appendNull(); } @@ -409,7 +387,7 @@ public DuckDBAppender append(Boolean value) throws SQLException { } public DuckDBAppender append(Character value) throws SQLException { - checkCurrentColumnType(DUCKDB_TYPE_VARCHAR); + currentColumn(DUCKDB_TYPE_VARCHAR); if (value == null) { return appendNull(); } @@ -417,7 +395,7 @@ public DuckDBAppender append(Character value) throws SQLException { } public DuckDBAppender append(Byte value) throws SQLException { - checkCurrentColumnType(int8Types); + currentColumn(int8Types); if (value == null) { return appendNull(); } @@ -425,7 +403,7 @@ public DuckDBAppender append(Byte value) throws SQLException { } public DuckDBAppender append(Short value) throws SQLException { - checkCurrentColumnType(int16Types); + currentColumn(int16Types); if (value == null) { return appendNull(); } @@ -433,7 +411,7 @@ public DuckDBAppender append(Short value) throws SQLException { } public DuckDBAppender append(Integer value) throws SQLException { - checkCurrentColumnType(int32Types); + currentColumn(int32Types); if (value == null) { return appendNull(); } @@ -441,7 +419,7 @@ public DuckDBAppender append(Integer value) throws SQLException { } public DuckDBAppender append(Long value) throws SQLException { - checkCurrentColumnType(int64Types); + currentColumn(int64Types); if (value == null) { return appendNull(); } @@ -449,7 +427,7 @@ public DuckDBAppender append(Long value) throws SQLException { } public DuckDBAppender append(Float value) throws SQLException { - checkCurrentColumnType(DUCKDB_TYPE_FLOAT); + currentColumn(DUCKDB_TYPE_FLOAT); if (value == null) { return appendNull(); } @@ -457,7 +435,7 @@ public DuckDBAppender append(Float value) throws SQLException { } public DuckDBAppender append(Double value) throws SQLException { - checkCurrentColumnType(DUCKDB_TYPE_DOUBLE); + currentColumn(DUCKDB_TYPE_DOUBLE); if (value == null) { return appendNull(); } @@ -465,95 +443,62 @@ public DuckDBAppender append(Double value) throws SQLException { } public DuckDBAppender appendHugeInt(long lower, long upper) throws SQLException { - Column col = currentColumnWithRowPos(int128Types); - col.data.putLong(lower); - col.data.putLong(upper); - incrementColOrStructFieldIdx(); + Column col = currentColumn(int128Types); + putHugeInt(col, rowIdx, lower, upper); + moveToNextColumn(); return this; } public DuckDBAppender append(BigInteger value) throws SQLException { - checkCurrentColumnType(int128Types); + Column col = currentColumn(int128Types); if (value == null) { return appendNull(); } - if (value.compareTo(HUGE_INT_MIN) < 0 || value.compareTo(HUGE_INT_MAX) > 0) { - throw new SQLException("Specified BigInteger value is out of range for HUGEINT field"); - } - long lower = value.longValue(); - long upper = value.shiftRight(64).longValue(); - return appendHugeInt(lower, upper); + putBigInteger(col, rowIdx, value); + moveToNextColumn(); + return this; } public DuckDBAppender appendDecimal(short value) throws SQLException { - Column col = currentDecimalColumnWithRowPos(DUCKDB_TYPE_SMALLINT); - col.data.putShort(value); - incrementColOrStructFieldIdx(); + Column col = currentColumn(DUCKDB_TYPE_DECIMAL); + checkDecimalType(col, DUCKDB_TYPE_SMALLINT); + putDecimal(col, rowIdx, value); + moveToNextColumn(); return this; } public DuckDBAppender appendDecimal(int value) throws SQLException { - Column col = currentDecimalColumnWithRowPos(DUCKDB_TYPE_INTEGER); - col.data.putInt(value); - incrementColOrStructFieldIdx(); + Column col = currentColumn(DUCKDB_TYPE_DECIMAL); + checkDecimalType(col, DUCKDB_TYPE_INTEGER); + putDecimal(col, rowIdx, value); + moveToNextColumn(); return this; } public DuckDBAppender appendDecimal(long value) throws SQLException { - Column col = currentDecimalColumnWithRowPos(DUCKDB_TYPE_BIGINT); - col.data.putLong(value); - incrementColOrStructFieldIdx(); + Column col = currentColumn(DUCKDB_TYPE_DECIMAL); + checkDecimalType(col, DUCKDB_TYPE_BIGINT); + putDecimal(col, rowIdx, value); + moveToNextColumn(); return this; } public DuckDBAppender appendDecimal(long lower, long upper) throws SQLException { - Column col = currentDecimalColumnWithRowPos(DUCKDB_TYPE_HUGEINT); - col.data.putLong(lower); - col.data.putLong(upper); - incrementColOrStructFieldIdx(); + Column col = currentColumn(DUCKDB_TYPE_DECIMAL); + checkDecimalType(col, DUCKDB_TYPE_HUGEINT); + putDecimal(col, rowIdx, lower, upper); + moveToNextColumn(); return this; } public DuckDBAppender append(BigDecimal value) throws SQLException { - Column col = currentColumnWithRowPos(DUCKDB_TYPE_DECIMAL); + Column col = currentColumn(DUCKDB_TYPE_DECIMAL); if (value == null) { return appendNull(); } - if (value.precision() > col.decimalPrecision) { - throw new SQLException(createErrMsg("invalid decimal precision, max expected: " + col.decimalPrecision + - ", actual: " + value.precision())); - } - if (col.decimalScale != value.scale()) { - throw new SQLException( - createErrMsg("invalid decimal scale, expected: " + col.decimalScale + ", actual: " + value.scale())); - } - - switch (col.decimalInternalType) { - case DUCKDB_TYPE_SMALLINT: { - checkDecimalPrecision(value, DUCKDB_TYPE_SMALLINT, 4); - short shortValue = value.unscaledValue().shortValueExact(); - return appendDecimal(shortValue); - } - case DUCKDB_TYPE_INTEGER: { - checkDecimalPrecision(value, DUCKDB_TYPE_INTEGER, 9); - int intValue = value.unscaledValue().intValueExact(); - return appendDecimal(intValue); - } - case DUCKDB_TYPE_BIGINT: { - checkDecimalPrecision(value, DUCKDB_TYPE_BIGINT, 18); - long longValue = value.unscaledValue().longValueExact(); - return appendDecimal(longValue); - } - case DUCKDB_TYPE_HUGEINT: { - checkDecimalPrecision(value, DUCKDB_TYPE_HUGEINT, 38); - BigInteger unscaledValue = value.unscaledValue(); - long lower = unscaledValue.longValue(); - long upper = unscaledValue.shiftRight(64).longValue(); - return appendDecimal(lower, upper); - } - default: - throw new SQLException(createErrMsg("invalid decimal internal type: '" + col.decimalInternalType + "'")); - } + putDecimal(col, rowIdx, value); + moveToNextColumn(); + return this; } // append arrays @@ -563,24 +508,13 @@ public DuckDBAppender append(boolean[] values) throws SQLException { } public DuckDBAppender append(boolean[] values, boolean[] nullMask) throws SQLException { - Column col = currentArrayInnerColumn(DUCKDB_TYPE_BOOLEAN); + Column col = currentColumn(collectionTypes); + arrayInnerColumn(col, DUCKDB_TYPE_BOOLEAN); if (values == null) { return appendNull(); } - - byte[] bytes = new byte[values.length]; - for (int i = 0; i < values.length; i++) { - bytes[i] = (byte) (values[i] ? 1 : 0); - } - - checkArrayLength(col, values.length); - int pos = prepareListColumn(col, values.length); - setNullMask(col, nullMask, values.length); - - col.data.position(pos); - col.data.put(bytes); - - incrementColOrStructFieldIdx(); + putBoolArray(col, rowIdx, values, nullMask); + moveToNextColumn(); return this; } @@ -589,37 +523,14 @@ public DuckDBAppender append(boolean[][] values) throws SQLException { } public DuckDBAppender append(boolean[][] values, boolean[][] nullMask) throws SQLException { - Column arrayCol = currentArrayInnerColumn(DUCKDB_TYPE_ARRAY); + Column col = currentColumn(DUCKDB_TYPE_ARRAY); + Column inner = arrayInnerColumn(col, DUCKDB_TYPE_ARRAY); + arrayInnerColumn(inner, DUCKDB_TYPE_BOOLEAN); if (values == null) { return appendNull(); } - checkArrayLength(arrayCol, values.length); - - Column col = currentNestedArrayInnerColumn(DUCKDB_TYPE_BOOLEAN); - byte[] buf = new byte[(int) col.arraySize]; - - for (int i = 0; i < values.length; i++) { - boolean[] childValues = values[i]; - - if (childValues == null) { - arrayCol.setNullOnArrayIdx(rowIdx, i); - continue; - } - checkArrayLength(col, childValues.length); - if (nullMask != null) { - setArrayNullMask(col, nullMask[i], childValues.length, i); - } - - for (int j = 0; j < childValues.length; j++) { - buf[j] = (byte) (childValues[j] ? 1 : 0); - } - - int pos = (int) ((rowIdx * arrayCol.arraySize + i) * col.arraySize); - col.data.position(pos); - col.data.put(buf); - } - - incrementColOrStructFieldIdx(); + putBoolArray2D(col, rowIdx, values, nullMask); + moveToNextColumn(); return this; } @@ -628,19 +539,13 @@ public DuckDBAppender appendByteArray(byte[] values) throws SQLException { } public DuckDBAppender appendByteArray(byte[] values, boolean[] nullMask) throws SQLException { - Column col = currentArrayInnerColumn(int8Types); + Column col = currentColumn(collectionTypes); + arrayInnerColumn(col, int8Types); if (values == null) { return appendNull(); } - - checkArrayLength(col, values.length); - int pos = prepareListColumn(col, values.length); - setNullMask(col, nullMask, values.length); - - col.data.position(pos); - col.data.put(values); - - incrementColOrStructFieldIdx(); + putByteArray(col, rowIdx, values, nullMask); + moveToNextColumn(); return this; } @@ -649,41 +554,25 @@ public DuckDBAppender appendByteArray(byte[][] values) throws SQLException { } public DuckDBAppender appendByteArray(byte[][] values, boolean[][] nullMask) throws SQLException { - Column arrayCol = currentArrayInnerColumn(DUCKDB_TYPE_ARRAY); + Column col = currentColumn(DUCKDB_TYPE_ARRAY); + Column inner = arrayInnerColumn(col, DUCKDB_TYPE_ARRAY); + arrayInnerColumn(inner, int8Types); if (values == null) { return appendNull(); } - checkArrayLength(arrayCol, values.length); - - Column col = currentNestedArrayInnerColumn(int8Types); - - for (int i = 0; i < values.length; i++) { - byte[] childValues = values[i]; - - if (childValues == null) { - arrayCol.setNullOnArrayIdx(rowIdx, i); - continue; - } - checkArrayLength(col, childValues.length); - if (nullMask != null) { - setArrayNullMask(col, nullMask[i], childValues.length, i); - } - - int pos = (int) ((rowIdx * arrayCol.arraySize + i) * col.arraySize); - col.data.position(pos); - col.data.put(childValues); - } - - incrementColOrStructFieldIdx(); + putByteArray2D(col, rowIdx, values, nullMask); + moveToNextColumn(); return this; } public DuckDBAppender append(byte[] values) throws SQLException { - checkCurrentColumnType(DUCKDB_TYPE_BLOB); + Column col = currentColumn(DUCKDB_TYPE_BLOB); if (values == null) { return appendNull(); } - return appendStringOrBlobInternal(DUCKDB_TYPE_BLOB, values); + putStringOrBlob(col, rowIdx, values); + moveToNextColumn(); + return this; } public DuckDBAppender append(char[] characters) throws SQLException { @@ -700,20 +589,13 @@ public DuckDBAppender append(short[] values) throws SQLException { } public DuckDBAppender append(short[] values, boolean[] nullMask) throws SQLException { - Column col = currentArrayInnerColumn(int16Types); + Column col = currentColumn(collectionTypes); + arrayInnerColumn(col, int16Types); if (values == null) { return appendNull(); } - - checkArrayLength(col, values.length); - int pos = prepareListColumn(col, values.length); - setNullMask(col, nullMask, values.length); - - ShortBuffer shortData = col.data.asShortBuffer(); - shortData.position(pos); - shortData.put(values); - - incrementColOrStructFieldIdx(); + putShortArray(col, rowIdx, values, nullMask); + moveToNextColumn(); return this; } @@ -722,33 +604,14 @@ public DuckDBAppender append(short[][] values) throws SQLException { } public DuckDBAppender append(short[][] values, boolean[][] nullMask) throws SQLException { - Column arrayCol = currentArrayInnerColumn(DUCKDB_TYPE_ARRAY); + Column col = currentColumn(DUCKDB_TYPE_ARRAY); + Column inner = arrayInnerColumn(col, DUCKDB_TYPE_ARRAY); + arrayInnerColumn(inner, int16Types); if (values == null) { return appendNull(); } - checkArrayLength(arrayCol, values.length); - - Column col = currentNestedArrayInnerColumn(int16Types); - - for (int i = 0; i < values.length; i++) { - short[] childValues = values[i]; - - if (childValues == null) { - arrayCol.setNullOnArrayIdx(rowIdx, i); - continue; - } - checkArrayLength(col, childValues.length); - if (nullMask != null) { - setArrayNullMask(col, nullMask[i], childValues.length, i); - } - - ShortBuffer shortBuffer = col.data.asShortBuffer(); - int pos = (int) ((rowIdx * arrayCol.arraySize + i) * col.arraySize); - shortBuffer.position(pos); - shortBuffer.put(childValues); - } - - incrementColOrStructFieldIdx(); + putShortArray2D(col, rowIdx, values, nullMask); + moveToNextColumn(); return this; } @@ -757,20 +620,13 @@ public DuckDBAppender append(int[] values) throws SQLException { } public DuckDBAppender append(int[] values, boolean[] nullMask) throws SQLException { - Column col = currentArrayInnerColumn(int32Types); + Column col = currentColumn(collectionTypes); + arrayInnerColumn(col, int32Types); if (values == null) { return appendNull(); } - - checkArrayLength(col, values.length); - int pos = prepareListColumn(col, values.length); - setNullMask(col, nullMask, values.length); - - IntBuffer intData = col.data.asIntBuffer(); - intData.position(pos); - intData.put(values); - - incrementColOrStructFieldIdx(); + putIntArray(col, rowIdx, values, nullMask); + moveToNextColumn(); return this; } @@ -779,33 +635,14 @@ public DuckDBAppender append(int[][] values) throws SQLException { } public DuckDBAppender append(int[][] values, boolean[][] nullMask) throws SQLException { - Column arrayCol = currentArrayInnerColumn(DUCKDB_TYPE_ARRAY); + Column col = currentColumn(DUCKDB_TYPE_ARRAY); + Column inner = arrayInnerColumn(col, DUCKDB_TYPE_ARRAY); + arrayInnerColumn(inner, int32Types); if (values == null) { return appendNull(); } - checkArrayLength(arrayCol, values.length); - - Column col = currentNestedArrayInnerColumn(int32Types); - - for (int i = 0; i < values.length; i++) { - int[] childValues = values[i]; - - if (childValues == null) { - arrayCol.setNullOnArrayIdx(rowIdx, i); - continue; - } - checkArrayLength(col, childValues.length); - if (nullMask != null) { - setArrayNullMask(col, nullMask[i], childValues.length, i); - } - - IntBuffer intData = col.data.asIntBuffer(); - int pos = (int) ((rowIdx * arrayCol.arraySize + i) * col.arraySize); - intData.position(pos); - intData.put(childValues); - } - - incrementColOrStructFieldIdx(); + putIntArray2D(col, rowIdx, values, nullMask); + moveToNextColumn(); return this; } @@ -814,20 +651,13 @@ public DuckDBAppender append(long[] values) throws SQLException { } public DuckDBAppender append(long[] values, boolean[] nullMask) throws SQLException { - Column col = currentArrayInnerColumn(int64Types); + Column col = currentColumn(collectionTypes); + arrayInnerColumn(col, int64Types); if (values == null) { return appendNull(); } - - checkArrayLength(col, values.length); - int pos = prepareListColumn(col, values.length); - setNullMask(col, nullMask, values.length); - - LongBuffer longData = col.data.asLongBuffer(); - longData.position(pos); - longData.put(values); - - incrementColOrStructFieldIdx(); + putLongArray(col, rowIdx, values, nullMask); + moveToNextColumn(); return this; } @@ -836,33 +666,14 @@ public DuckDBAppender append(long[][] values) throws SQLException { } public DuckDBAppender append(long[][] values, boolean[][] nullMask) throws SQLException { - Column arrayCol = currentArrayInnerColumn(DUCKDB_TYPE_ARRAY); + Column col = currentColumn(DUCKDB_TYPE_ARRAY); + Column inner = arrayInnerColumn(col, DUCKDB_TYPE_ARRAY); + arrayInnerColumn(inner, int64Types); if (values == null) { return appendNull(); } - checkArrayLength(arrayCol, values.length); - - Column col = currentNestedArrayInnerColumn(int64Types); - - for (int i = 0; i < values.length; i++) { - long[] childValues = values[i]; - - if (childValues == null) { - arrayCol.setNullOnArrayIdx(rowIdx, i); - continue; - } - checkArrayLength(col, childValues.length); - if (nullMask != null) { - setArrayNullMask(col, nullMask[i], childValues.length, i); - } - - LongBuffer longData = col.data.asLongBuffer(); - int pos = (int) ((rowIdx * arrayCol.arraySize + i) * col.arraySize); - longData.position(pos); - longData.put(childValues); - } - - incrementColOrStructFieldIdx(); + putLongArray2D(col, rowIdx, values, nullMask); + moveToNextColumn(); return this; } @@ -871,20 +682,13 @@ public DuckDBAppender append(float[] values) throws SQLException { } public DuckDBAppender append(float[] values, boolean[] nullMask) throws SQLException { - Column col = currentArrayInnerColumn(DUCKDB_TYPE_FLOAT); + Column col = currentColumn(collectionTypes); + arrayInnerColumn(col, DUCKDB_TYPE_FLOAT); if (values == null) { return appendNull(); } - - checkArrayLength(col, values.length); - int pos = prepareListColumn(col, values.length); - setNullMask(col, nullMask, values.length); - - FloatBuffer floatData = col.data.asFloatBuffer(); - floatData.position(pos); - floatData.put(values); - - incrementColOrStructFieldIdx(); + putFloatArray(col, rowIdx, values, nullMask); + moveToNextColumn(); return this; } @@ -893,33 +697,14 @@ public DuckDBAppender append(float[][] values) throws SQLException { } public DuckDBAppender append(float[][] values, boolean[][] nullMask) throws SQLException { - Column arrayCol = currentArrayInnerColumn(DUCKDB_TYPE_ARRAY); + Column col = currentColumn(DUCKDB_TYPE_ARRAY); + Column inner = arrayInnerColumn(col, DUCKDB_TYPE_ARRAY); + arrayInnerColumn(inner, DUCKDB_TYPE_FLOAT); if (values == null) { return appendNull(); } - checkArrayLength(arrayCol, values.length); - - Column col = currentNestedArrayInnerColumn(DUCKDB_TYPE_FLOAT); - - for (int i = 0; i < values.length; i++) { - float[] childValues = values[i]; - - if (childValues == null) { - arrayCol.setNullOnArrayIdx(rowIdx, i); - continue; - } - checkArrayLength(col, childValues.length); - if (nullMask != null) { - setArrayNullMask(col, nullMask[i], childValues.length, i); - } - - FloatBuffer floatData = col.data.asFloatBuffer(); - int pos = (int) ((rowIdx * arrayCol.arraySize + i) * col.arraySize); - floatData.position(pos); - floatData.put(childValues); - } - - incrementColOrStructFieldIdx(); + putFloatArray2D(col, rowIdx, values, nullMask); + moveToNextColumn(); return this; } @@ -928,20 +713,13 @@ public DuckDBAppender append(double[] values) throws SQLException { } public DuckDBAppender append(double[] values, boolean[] nullMask) throws SQLException { - Column col = currentArrayInnerColumn(DUCKDB_TYPE_DOUBLE); + Column col = currentColumn(collectionTypes); + arrayInnerColumn(col, DUCKDB_TYPE_DOUBLE); if (values == null) { return appendNull(); } - - checkArrayLength(col, values.length); - int pos = prepareListColumn(col, values.length); - setNullMask(col, nullMask, values.length); - - DoubleBuffer doubleData = col.data.asDoubleBuffer(); - doubleData.position(pos); - doubleData.put(values); - - incrementColOrStructFieldIdx(); + putDoubleArray(col, rowIdx, values, nullMask); + moveToNextColumn(); return this; } @@ -950,59 +728,41 @@ public DuckDBAppender append(double[][] values) throws SQLException { } public DuckDBAppender append(double[][] values, boolean[][] nullMask) throws SQLException { - Column arrayCol = currentArrayInnerColumn(DUCKDB_TYPE_ARRAY); + Column col = currentColumn(DUCKDB_TYPE_ARRAY); + Column inner = arrayInnerColumn(col, DUCKDB_TYPE_ARRAY); + arrayInnerColumn(inner, DUCKDB_TYPE_DOUBLE); if (values == null) { return appendNull(); } - checkArrayLength(arrayCol, values.length); - - Column col = currentNestedArrayInnerColumn(DUCKDB_TYPE_DOUBLE); - - for (int i = 0; i < values.length; i++) { - double[] childValues = values[i]; - - if (childValues == null) { - arrayCol.setNullOnArrayIdx(rowIdx, i); - continue; - } - checkArrayLength(col, childValues.length); - if (nullMask != null) { - setArrayNullMask(col, nullMask[i], childValues.length, i); - } - - DoubleBuffer doubleBuffer = col.data.asDoubleBuffer(); - int pos = (int) ((rowIdx * arrayCol.arraySize + i) * col.arraySize); - doubleBuffer.position(pos); - doubleBuffer.put(childValues); - } - - incrementColOrStructFieldIdx(); + putDoubleArray2D(col, rowIdx, values, nullMask); + moveToNextColumn(); return this; } // append objects public DuckDBAppender append(String value) throws SQLException { - checkCurrentColumnType(DUCKDB_TYPE_VARCHAR); + Column col = currentColumn(DUCKDB_TYPE_VARCHAR); if (value == null) { return appendNull(); } byte[] bytes = value.getBytes(UTF_8); - return appendStringOrBlobInternal(DUCKDB_TYPE_VARCHAR, bytes); + putStringOrBlob(col, rowIdx, bytes); + + moveToNextColumn(); + return this; } public DuckDBAppender appendUUID(long mostSigBits, long leastSigBits) throws SQLException { - Column col = currentColumnWithRowPos(DUCKDB_TYPE_UUID); - col.data.putLong(leastSigBits); - mostSigBits ^= Long.MIN_VALUE; - col.data.putLong(mostSigBits); - incrementColOrStructFieldIdx(); + Column col = currentColumn(DUCKDB_TYPE_UUID); + putUUID(col, rowIdx, mostSigBits, leastSigBits); + moveToNextColumn(); return this; } public DuckDBAppender append(UUID value) throws SQLException { - checkCurrentColumnType(DUCKDB_TYPE_UUID); + currentColumn(DUCKDB_TYPE_UUID); if (value == null) { return appendNull(); } @@ -1013,148 +773,153 @@ public DuckDBAppender append(UUID value) throws SQLException { } public DuckDBAppender appendEpochDays(int days) throws SQLException { - Column col = currentColumnWithRowPos(DUCKDB_TYPE_DATE); - col.data.putInt(days); - incrementColOrStructFieldIdx(); + Column col = currentColumn(DUCKDB_TYPE_DATE); + putEpochDays(col, rowIdx, days); + moveToNextColumn(); return this; } public DuckDBAppender append(LocalDate value) throws SQLException { + Column col = currentColumn(DUCKDB_TYPE_DATE); if (value == null) { return appendNull(); } - long days = value.toEpochDay(); - if (days < Integer.MIN_VALUE || days > Integer.MAX_VALUE) { - throw new SQLException(createErrMsg("unsupported number of days: " + days + ", must fit into 'int32_t'")); - } - return appendEpochDays((int) days); + putLocalDate(col, rowIdx, value); + moveToNextColumn(); + return this; } public DuckDBAppender appendDayMicros(long micros) throws SQLException { - Column col = currentColumnWithRowPos(DUCKDB_TYPE_TIME); - col.data.putLong(micros); - incrementColOrStructFieldIdx(); + Column col = currentColumn(DUCKDB_TYPE_TIME); + putDayMicros(col, rowIdx, micros); + moveToNextColumn(); return this; } public DuckDBAppender append(LocalTime value) throws SQLException { - checkCurrentColumnType(DUCKDB_TYPE_TIME); + Column col = currentColumn(DUCKDB_TYPE_TIME); if (value == null) { return appendNull(); } - long micros = value.toNanoOfDay() / 1000; - return appendDayMicros(micros); + putLocalTime(col, rowIdx, value); + moveToNextColumn(); + return this; } public DuckDBAppender appendDayMicros(long micros, int offset) throws SQLException { - Column col = currentColumnWithRowPos(DUCKDB_TYPE_TIME_TZ); - long packed = ((micros & 0xFFFFFFFFFFL) << 24) | (long) (offset & 0xFFFFFF); - col.data.putLong(packed); - incrementColOrStructFieldIdx(); + Column col = currentColumn(DUCKDB_TYPE_TIME_TZ); + putDayMicros(col, rowIdx, micros, offset); + moveToNextColumn(); return this; } public DuckDBAppender append(OffsetTime value) throws SQLException { - checkCurrentColumnType(DUCKDB_TYPE_TIME_TZ); + Column col = currentColumn(DUCKDB_TYPE_TIME_TZ); if (value == null) { return appendNull(); } - int offset = value.getOffset().getTotalSeconds(); - long micros = value.toLocalTime().toNanoOfDay() / 1000; - return appendDayMicros(micros, offset); + putOffsetTime(col, rowIdx, value); + moveToNextColumn(); + return this; } public DuckDBAppender appendEpochSeconds(long seconds) throws SQLException { - Column col = currentColumnWithRowPos(DUCKDB_TYPE_TIMESTAMP_S); - col.data.putLong(seconds); - incrementColOrStructFieldIdx(); + Column col = currentColumn(DUCKDB_TYPE_TIMESTAMP_S); + putEpochMoment(col, rowIdx, seconds); + moveToNextColumn(); return this; } public DuckDBAppender appendEpochMillis(long millis) throws SQLException { - Column col = currentColumnWithRowPos(DUCKDB_TYPE_TIMESTAMP_MS); - col.data.putLong(millis); - incrementColOrStructFieldIdx(); + Column col = currentColumn(DUCKDB_TYPE_TIMESTAMP_MS); + putEpochMoment(col, rowIdx, millis); + moveToNextColumn(); return this; } public DuckDBAppender appendEpochMicros(long micros) throws SQLException { - Column col = currentColumnWithRowPos(timestampMicrosTypes); - col.data.putLong(micros); - incrementColOrStructFieldIdx(); + Column col = currentColumn(timestampMicrosTypes); + putEpochMoment(col, rowIdx, micros); + moveToNextColumn(); return this; } public DuckDBAppender appendEpochNanos(long nanos) throws SQLException { - Column col = currentColumnWithRowPos(DUCKDB_TYPE_TIMESTAMP_NS); - col.data.putLong(nanos); - incrementColOrStructFieldIdx(); + Column col = currentColumn(DUCKDB_TYPE_TIMESTAMP_NS); + putEpochMoment(col, rowIdx, nanos); + moveToNextColumn(); return this; } public DuckDBAppender append(LocalDateTime value) throws SQLException { - Column col = currentColumn(); - checkCurrentColumnType(timestampLocalTypes); + Column col = currentColumn(timestampLocalTypes); if (value == null) { return appendNull(); } - switch (col.colType) { - case DUCKDB_TYPE_TIMESTAMP_S: - long seconds = EPOCH_DATE_TIME.until(value, SECONDS); - return appendEpochSeconds(seconds); - case DUCKDB_TYPE_TIMESTAMP_MS: { - long millis = EPOCH_DATE_TIME.until(value, MILLIS); - return appendEpochMillis(millis); - } - case DUCKDB_TYPE_TIMESTAMP: { - long micros = EPOCH_DATE_TIME.until(value, MICROS); - return appendEpochMicros(micros); - } - case DUCKDB_TYPE_TIMESTAMP_NS: { - long nanos = EPOCH_DATE_TIME.until(value, NANOS); - return appendEpochNanos(nanos); - } - default: - throw new SQLException(createErrMsg("invalid column type: " + col.colType)); - } + putLocalDateTime(col, rowIdx, value); + moveToNextColumn(); + return this; } public DuckDBAppender append(java.util.Date value) throws SQLException { - Column col = currentColumn(); - checkCurrentColumnType(timestampLocalTypes); + Column col = currentColumn(timestampLocalTypes); if (value == null) { return appendNull(); } - switch (col.colType) { - case DUCKDB_TYPE_TIMESTAMP_S: - long seconds = value.getTime() / 1000; - return appendEpochSeconds(seconds); - case DUCKDB_TYPE_TIMESTAMP_MS: { - long millis = value.getTime(); - return appendEpochMillis(millis); + putDate(col, rowIdx, value); + moveToNextColumn(); + return this; + } + + public DuckDBAppender append(OffsetDateTime value) throws SQLException { + Column col = currentColumn(DUCKDB_TYPE_TIMESTAMP_TZ); + if (value == null) { + return appendNull(); } - case DUCKDB_TYPE_TIMESTAMP: { - long micros = Math.multiplyExact(value.getTime(), 1000); - return appendEpochMicros(micros); + putOffsetDateTime(col, rowIdx, value); + moveToNextColumn(); + return this; + } + + public DuckDBAppender append(Collection collection) throws SQLException { + currentColumn(collectionTypes); + if (collection == null) { + return appendNull(); } - case DUCKDB_TYPE_TIMESTAMP_NS: { - long nanos = Math.multiplyExact(value.getTime(), 1000000); - return appendEpochNanos(nanos); + return append(collection, collection.size()); + } + + public DuckDBAppender append(Iterable iter, int count) throws SQLException { + currentColumn(collectionTypes); + if (iter == null) { + return appendNull(); } - default: - throw new SQLException(createErrMsg("invalid column type: " + col.colType)); + return append(iter.iterator(), count); + } + + public DuckDBAppender append(Iterator iter, int count) throws SQLException { + Column parentCol = currentColumn(collectionTypes); + if (parentCol.colType != DUCKDB_TYPE_ARRAY && parentCol.colType != DUCKDB_TYPE_LIST) { + throw new SQLException(createErrMsg("invalid array/list column type: '" + parentCol.colType + "'")); + } + if (iter == null) { + return appendNull(); } + Column col = parentCol.children.get(0); + putObjectArrayOrList(col, rowIdx, iter, count); + moveToNextColumn(); + return this; } - public DuckDBAppender append(OffsetDateTime value) throws SQLException { - checkCurrentColumnType(DUCKDB_TYPE_TIMESTAMP_TZ); - if (value == null) { + public DuckDBAppender append(Map map) throws SQLException { + Column parentCol = currentColumn(DUCKDB_TYPE_MAP); + if (map == null) { return appendNull(); } - ZonedDateTime zdt = value.atZoneSameInstant(ZoneOffset.UTC); - LocalDateTime ldt = zdt.toLocalDateTime(); - long micros = EPOCH_DATE_TIME.until(ldt, MICROS); - return appendEpochMicros(micros); + Column col = parentCol.children.get(0); + putMap(col, rowIdx, map); + moveToNextColumn(); + return this; } // append special @@ -1162,7 +927,7 @@ public DuckDBAppender append(OffsetDateTime value) throws SQLException { public DuckDBAppender appendNull() throws SQLException { Column col = currentColumn(); col.setNull(rowIdx); - incrementColOrStructFieldIdx(); + moveToNextColumn(); return this; } @@ -1175,7 +940,7 @@ public DuckDBAppender appendDefault() throws SQLException { } finally { appenderRefLock.unlock(); } - incrementColOrStructFieldIdx(); + moveToNextColumn(); return this; } @@ -1198,11 +963,7 @@ private String createErrMsg(String error) { + ", message: " + (null != error ? error : "N/A"); } - private void checkOpen() throws SQLException { - if (isClosed()) { - throw new SQLException(createErrMsg("appender was closed")); - } - } + // next column private Column nextColumn(Column curCol) { if (null == curCol) { @@ -1228,7 +989,7 @@ private Column nextColumn(Column curCol) { } } - private void incrementColOrStructFieldIdx() throws SQLException { + private void moveToNextColumn() throws SQLException { Column col = currentColumn(); this.prevColumn = currentColumn; if (unionBegunInvariant()) { @@ -1238,247 +999,1069 @@ private void incrementColOrStructFieldIdx() throws SQLException { } } - private Column currentColumn() throws SQLException { - if (null == currentColumn) { - throw new SQLException(createErrMsg("current column not found, columns count: " + columns.size())); + // checks + + private void checkOpen() throws SQLException { + if (isClosed()) { + throw new SQLException(createErrMsg("appender was closed")); + } + } + + private void checkCurrentColumnType(CAPIType ctype) throws SQLException { + checkCurrentColumnType(ctype.typeArray); + } + + private void checkCurrentColumnType(CAPIType[] ctypes) throws SQLException { + Column col = currentColumn(); + checkColumnType(col, ctypes); + } + + private void checkColumnType(Column col, CAPIType ctype) throws SQLException { + checkColumnType(col, ctype.typeArray); + } + + private void checkColumnType(Column col, CAPIType[] ctypes) throws SQLException { + for (CAPIType ct : ctypes) { + if (col.colType == ct) { + return; + } + } + throw new SQLException(createErrMsg("invalid column type, expected one of: '" + Arrays.toString(ctypes) + + "', actual: '" + col.colType + "'")); + } + + private void checkArrayLength(Column col, long length) throws SQLException { + if (null == col.parent) { + throw new SQLException(createErrMsg("invalid array/list column specified")); + } + switch (col.parent.colType) { + case DUCKDB_TYPE_LIST: + return; + case DUCKDB_TYPE_ARRAY: + break; + default: + throw new SQLException(createErrMsg("invalid array/list column type: " + col.colType)); + } + if (col.arraySize != length) { + throw new SQLException( + createErrMsg("invalid array size, expected: " + col.arraySize + ", actual: " + length)); + } + } + + private void checkDecimalType(Column col, CAPIType decimalInternalType) throws SQLException { + if (col.decimalInternalType != decimalInternalType) { + throw new SQLException(createErrMsg("invalid decimal internal type, expected: '" + col.decimalInternalType + + "', actual: '" + decimalInternalType + "'")); + } + } + + private void checkDecimalPrecision(BigDecimal value, CAPIType decimalInternalType, int maxPrecision) + throws SQLException { + if (value.precision() > maxPrecision) { + throw new SQLException(createErrMsg("invalid decimal precision, value: " + value.precision() + + ", max value: " + maxPrecision + + ", decimal internal type: " + decimalInternalType)); + } + } + + // column helpers + + private Column currentColumn() throws SQLException { + checkOpen(); + + if (null == currentColumn) { + throw new SQLException(createErrMsg("current column not found, columns count: " + columns.size())); + } + + return currentColumn; + } + + private Column currentColumn(CAPIType ctype) throws SQLException { + return currentColumn(ctype.typeArray); + } + + private Column currentColumn(CAPIType[] ctypes) throws SQLException { + Column col = currentColumn(); + checkColumnType(col, ctypes); + return col; + } + + private Column arrayInnerColumn(Column arrayCol, CAPIType ctype) throws SQLException { + return arrayInnerColumn(arrayCol, ctype.typeArray); + } + + private Column arrayInnerColumn(Column arrayCol, CAPIType[] ctypes) throws SQLException { + if (arrayCol.colType != DUCKDB_TYPE_ARRAY && arrayCol.colType != DUCKDB_TYPE_LIST) { + throw new SQLException(createErrMsg("invalid array/list column type: '" + arrayCol.colType + "'")); + } + + Column col = arrayCol.children.get(0); + for (CAPIType ct : ctypes) { + if (col.colType == ct) { + return col; + } + } + throw new SQLException(createErrMsg("invalid array/list inner column type, expected one of: '" + + Arrays.toString(ctypes) + "', actual: '" + col.colType + "'")); + } + + private Column currentTopLevelColumn() { + if (null == currentColumn) { + return null; + } + Column col = currentColumn; + while (null != col.parent) { + col = col.parent; + } + return col; + } + + // null mask + + private void setNullMask(Column col, long vectorIdx, boolean[] nullMask, int elementsCount) throws SQLException { + if (null == col.parent) { + throw new SQLException(createErrMsg("invalid array/list column specified")); + } + switch (col.parent.colType) { + case DUCKDB_TYPE_ARRAY: + // While this should work for arrays nested inside lists, currently array null + // masks are only used for top-level arrays + setArrayNullMask(col, vectorIdx, nullMask, elementsCount, 0); + return; + case DUCKDB_TYPE_LIST: + setListNullMask(col, nullMask, elementsCount); + return; + default: + throw new SQLException(createErrMsg("invalid array/list column type: " + col.colType)); + } + } + + private void setArrayNullMask(Column col, long vectorIdx, boolean[] nullMask, int elementsCount, int parentArrayIdx) + throws SQLException { + if (null == nullMask) { + return; + } + if (nullMask.length != elementsCount) { + throw new SQLException( + createErrMsg("invalid null mask size, expected: " + elementsCount + ", actual: " + nullMask.length)); + } + for (int i = 0; i < nullMask.length; i++) { + if (nullMask[i]) { + col.setNullOnArrayIdx(vectorIdx, (int) (i + col.arraySize * parentArrayIdx)); + } + } + } + + private void setListNullMask(Column col, boolean[] nullMask, int elementsCount) throws SQLException { + if (null == nullMask) { + return; + } + if (nullMask.length != elementsCount) { + throw new SQLException( + createErrMsg("invalid null mask size, expected: " + elementsCount + ", actual: " + nullMask.length)); + } + if (col.listSize < elementsCount) { + throw new SQLException( + createErrMsg("invalid list state, list size: " + col.listSize + ", elements count: " + elementsCount)); + } + for (int i = 0; i < nullMask.length; i++) { + if (nullMask[i]) { + long vectorIdx = col.listSize - elementsCount + i; + col.setNull(vectorIdx); + } + } + } + + // put implementation + + private void putByte(Column col, long vectorIdx, byte value) throws SQLException { + int pos = (int) (vectorIdx * col.colType.widthBytes); + col.data.position(pos); + col.data.put(value); + } + + private void putShort(Column col, long vectorIdx, short value) throws SQLException { + int pos = (int) (vectorIdx * col.colType.widthBytes); + col.data.position(pos); + col.data.putShort(value); + } + + private void putInt(Column col, long vectorIdx, int value) throws SQLException { + int pos = (int) (vectorIdx * col.colType.widthBytes); + col.data.position(pos); + col.data.putInt(value); + } + + private void putLong(Column col, long vectorIdx, long value) throws SQLException { + int pos = (int) (vectorIdx * col.colType.widthBytes); + col.data.position(pos); + col.data.putLong(value); + } + + private void putFloat(Column col, long vectorIdx, float value) throws SQLException { + int pos = (int) (vectorIdx * col.colType.widthBytes); + col.data.position(pos); + col.data.putFloat(value); + } + + private void putDouble(Column col, long vectorIdx, double value) throws SQLException { + int pos = (int) (vectorIdx * col.colType.widthBytes); + col.data.position(pos); + col.data.putDouble(value); + } + + private void putHugeInt(Column col, long vectorIdx, long lower, long upper) throws SQLException { + int pos = (int) (vectorIdx * col.colType.widthBytes); + col.data.position(pos); + col.data.putLong(lower); + col.data.putLong(upper); + } + + private void putBigInteger(Column col, long vectorIdx, BigInteger value) throws SQLException { + if (value.compareTo(HUGE_INT_MIN) < 0 || value.compareTo(HUGE_INT_MAX) > 0) { + throw new SQLException("Specified BigInteger value is out of range for HUGEINT field"); + } + long lower = value.longValue(); + long upper = value.shiftRight(64).longValue(); + putHugeInt(col, vectorIdx, lower, upper); + } + + private void putDecimal(Column col, long vectorIdx, short value) throws SQLException { + int pos = (int) (vectorIdx * col.decimalInternalType.widthBytes); + col.data.position(pos); + col.data.putShort(value); + } + + private void putDecimal(Column col, long vectorIdx, int value) throws SQLException { + int pos = (int) (vectorIdx * col.decimalInternalType.widthBytes); + col.data.position(pos); + col.data.putInt(value); + } + + private void putDecimal(Column col, long vectorIdx, long value) throws SQLException { + int pos = (int) (vectorIdx * col.decimalInternalType.widthBytes); + col.data.position(pos); + col.data.putLong(value); + } + + private void putDecimal(Column col, long vectorIdx, long lower, long upper) throws SQLException { + int pos = (int) (vectorIdx * col.decimalInternalType.widthBytes); + col.data.position(pos); + col.data.putLong(lower); + col.data.putLong(upper); + } + + private void putDecimal(Column col, long vectorIdx, BigDecimal value) throws SQLException { + if (value.precision() > col.decimalPrecision) { + throw new SQLException(createErrMsg("invalid decimal precision, max expected: " + col.decimalPrecision + + ", actual: " + value.precision())); + } + if (col.decimalScale != value.scale()) { + throw new SQLException( + createErrMsg("invalid decimal scale, expected: " + col.decimalScale + ", actual: " + value.scale())); + } + + switch (col.decimalInternalType) { + case DUCKDB_TYPE_SMALLINT: { + checkDecimalPrecision(value, DUCKDB_TYPE_SMALLINT, 4); + short shortValue = value.unscaledValue().shortValueExact(); + putDecimal(col, vectorIdx, shortValue); + break; + } + case DUCKDB_TYPE_INTEGER: { + checkDecimalPrecision(value, DUCKDB_TYPE_INTEGER, 9); + int intValue = value.unscaledValue().intValueExact(); + putDecimal(col, vectorIdx, intValue); + break; + } + case DUCKDB_TYPE_BIGINT: { + checkDecimalPrecision(value, DUCKDB_TYPE_BIGINT, 18); + long longValue = value.unscaledValue().longValueExact(); + putDecimal(col, vectorIdx, longValue); + break; + } + case DUCKDB_TYPE_HUGEINT: { + checkDecimalPrecision(value, DUCKDB_TYPE_HUGEINT, 38); + BigInteger unscaledValue = value.unscaledValue(); + long lower = unscaledValue.longValue(); + long upper = unscaledValue.shiftRight(64).longValue(); + putDecimal(col, vectorIdx, lower, upper); + break; + } + default: + throw new SQLException(createErrMsg("invalid decimal internal type: '" + col.decimalInternalType + "'")); + } + } + + private void putStringOrBlob(Column col, long vectorIdx, byte[] bytes) throws SQLException { + if (writeInlinedStrings && bytes.length < STRING_MAX_INLINE_BYTES) { + int pos = (int) (vectorIdx * col.colType.widthBytes); + col.data.position(pos); + col.data.putInt(bytes.length); + if (bytes.length > 0) { + col.data.put(bytes); + } + } else { + appenderRefLock.lock(); + try { + checkOpen(); + duckdb_vector_assign_string_element_len(col.vectorRef, vectorIdx, bytes); + } finally { + appenderRefLock.unlock(); + } + } + } + + private void putUUID(Column col, long vectorIdx, long mostSigBits, long leastSigBits) throws SQLException { + int pos = (int) (vectorIdx * col.colType.widthBytes); + col.data.position(pos); + col.data.putLong(leastSigBits); + mostSigBits ^= Long.MIN_VALUE; + col.data.putLong(mostSigBits); + } + + private void putEpochDays(Column col, long vectorIdx, int days) throws SQLException { + int pos = (int) (vectorIdx * col.colType.widthBytes); + col.data.position(pos); + col.data.putInt(days); + } + + private void putLocalDate(Column col, long vectorIdx, LocalDate date) throws SQLException { + long days = date.toEpochDay(); + if (days < Integer.MIN_VALUE || days > Integer.MAX_VALUE) { + throw new SQLException(createErrMsg("unsupported number of days: " + days + ", must fit into 'int32_t'")); + } + putEpochDays(col, vectorIdx, (int) days); + } + + public void putDayMicros(Column col, long vectorIdx, long micros) throws SQLException { + int pos = (int) (vectorIdx * col.colType.widthBytes); + col.data.position(pos); + col.data.putLong(micros); + } + + public void putLocalTime(Column col, long vectorIdx, LocalTime value) throws SQLException { + long micros = value.toNanoOfDay() / 1000; + putDayMicros(col, vectorIdx, micros); + } + + public void putDayMicros(Column col, long vectorIdx, long micros, int offset) throws SQLException { + int maxOffset = 16 * 60 * 60 - 1; + long packed = ((micros & 0xFFFFFFFFFFL) << 24) | (long) ((maxOffset - offset) & 0xFFFFFF); + int pos = (int) (vectorIdx * col.colType.widthBytes); + col.data.position(pos); + col.data.putLong(packed); + } + + public void putOffsetTime(Column col, long vectorIdx, OffsetTime value) throws SQLException { + int offset = value.getOffset().getTotalSeconds(); + long micros = value.toLocalTime().toNanoOfDay() / 1000; + putDayMicros(col, vectorIdx, micros, offset); + } + + private void putEpochMoment(Column col, long vectorIdx, long moment) throws SQLException { + int pos = (int) (vectorIdx * col.colType.widthBytes); + col.data.position(pos); + col.data.putLong(moment); + } + + private void putLocalDateTime(Column col, long vectorIdx, LocalDateTime value) throws SQLException { + final long moment; + switch (col.colType) { + case DUCKDB_TYPE_TIMESTAMP_S: + moment = EPOCH_DATE_TIME.until(value, SECONDS); + break; + case DUCKDB_TYPE_TIMESTAMP_MS: + moment = EPOCH_DATE_TIME.until(value, MILLIS); + break; + case DUCKDB_TYPE_TIMESTAMP: + moment = EPOCH_DATE_TIME.until(value, MICROS); + break; + case DUCKDB_TYPE_TIMESTAMP_NS: + moment = EPOCH_DATE_TIME.until(value, NANOS); + break; + default: + throw new SQLException(createErrMsg("invalid column type: " + col.colType)); + } + putEpochMoment(col, vectorIdx, moment); + } + + private void putDate(Column col, long vectorIdx, java.util.Date value) throws SQLException { + final long moment; + switch (col.colType) { + case DUCKDB_TYPE_TIMESTAMP_S: + moment = value.getTime() / 1000; + break; + case DUCKDB_TYPE_TIMESTAMP_MS: { + moment = value.getTime(); + break; + } + case DUCKDB_TYPE_TIMESTAMP: { + moment = Math.multiplyExact(value.getTime(), 1000L); + break; + } + case DUCKDB_TYPE_TIMESTAMP_NS: { + moment = Math.multiplyExact(value.getTime(), 1000000L); + break; + } + default: + throw new SQLException(createErrMsg("invalid column type: " + col.colType)); + } + putEpochMoment(col, vectorIdx, moment); + } + + private void putOffsetDateTime(Column col, long vectorIdx, OffsetDateTime value) throws SQLException { + ZonedDateTime zdt = value.atZoneSameInstant(ZoneOffset.UTC); + LocalDateTime ldt = zdt.toLocalDateTime(); + long micros = EPOCH_DATE_TIME.until(ldt, MICROS); + putEpochMoment(col, vectorIdx, micros); + } + + private void putBoolArray(Column arrayCol, long vectorIdx, boolean[] values) throws SQLException { + putBoolArray(arrayCol, vectorIdx, values, null); + } + + private void putBoolArray(Column arrayCol, long vectorIdx, boolean[] values, boolean[] nullMask) + throws SQLException { + Column col = arrayInnerColumn(arrayCol, DUCKDB_TYPE_BOOLEAN); + + byte[] bytes = new byte[values.length]; + for (int i = 0; i < values.length; i++) { + bytes[i] = (byte) (values[i] ? 1 : 0); + } + + checkArrayLength(col, values.length); + int pos = prepareListColumn(col, vectorIdx, values.length); + setNullMask(col, vectorIdx, nullMask, values.length); + + col.data.position(pos); + col.data.put(bytes); + } + + private void putByteArray(Column arrayCol, long vectorIdx, byte[] values) throws SQLException { + putByteArray(arrayCol, vectorIdx, values, null); + } + + private void putByteArray(Column arrayCol, long vectorIdx, byte[] values, boolean[] nullMask) throws SQLException { + Column col = arrayInnerColumn(arrayCol, int8Types); + + checkArrayLength(col, values.length); + int pos = prepareListColumn(col, vectorIdx, values.length); + setNullMask(col, vectorIdx, nullMask, values.length); + + col.data.position(pos); + col.data.put(values); + } + + private void putShortArray(Column arrayCol, long vectorIdx, short[] values) throws SQLException { + putShortArray(arrayCol, vectorIdx, values, null); + } + + private void putShortArray(Column arrayCol, long vectorIdx, short[] values, boolean[] nullMask) + throws SQLException { + Column col = arrayInnerColumn(arrayCol, int16Types); + + checkArrayLength(col, values.length); + int pos = prepareListColumn(col, vectorIdx, values.length); + setNullMask(col, vectorIdx, nullMask, values.length); + + ShortBuffer shortData = col.data.asShortBuffer(); + shortData.position(pos); + shortData.put(values); + } + + private void putIntArray(Column arrayCol, long vectorIdx, int[] values) throws SQLException { + putIntArray(arrayCol, vectorIdx, values, null); + } + + private void putIntArray(Column arrayCol, long vectorIdx, int[] values, boolean[] nullMask) throws SQLException { + Column col = arrayInnerColumn(arrayCol, int32Types); + + checkArrayLength(col, values.length); + int pos = prepareListColumn(col, vectorIdx, values.length); + setNullMask(col, vectorIdx, nullMask, values.length); + + IntBuffer intData = col.data.asIntBuffer(); + intData.position(pos); + intData.put(values); + } + + private void putLongArray(Column arrayCol, long vectorIdx, long[] values) throws SQLException { + putLongArray(arrayCol, vectorIdx, values, null); + } + + private void putLongArray(Column arrayCol, long vectorIdx, long[] values, boolean[] nullMask) throws SQLException { + Column col = arrayInnerColumn(arrayCol, int64Types); + + checkArrayLength(col, values.length); + int pos = prepareListColumn(col, vectorIdx, values.length); + setNullMask(col, vectorIdx, nullMask, values.length); + + LongBuffer longData = col.data.asLongBuffer(); + longData.position(pos); + longData.put(values); + } + + private void putFloatArray(Column arrayCol, long vectorIdx, float[] values) throws SQLException { + putFloatArray(arrayCol, vectorIdx, values, null); + } + + private void putFloatArray(Column arrayCol, long vectorIdx, float[] values, boolean[] nullMask) + throws SQLException { + Column col = arrayInnerColumn(arrayCol, DUCKDB_TYPE_FLOAT); + + checkArrayLength(col, values.length); + int pos = prepareListColumn(col, vectorIdx, values.length); + setNullMask(col, vectorIdx, nullMask, values.length); + + FloatBuffer floatData = col.data.asFloatBuffer(); + floatData.position(pos); + floatData.put(values); + } + + private void putDoubleArray(Column arrayCol, long vectorIdx, double[] values) throws SQLException { + putDoubleArray(arrayCol, vectorIdx, values, null); + } + + private void putDoubleArray(Column arrayCol, long vectorIdx, double[] values, boolean[] nullMask) + throws SQLException { + Column col = arrayInnerColumn(arrayCol, DUCKDB_TYPE_DOUBLE); + + checkArrayLength(col, values.length); + int pos = prepareListColumn(col, vectorIdx, values.length); + setNullMask(col, vectorIdx, nullMask, values.length); + + DoubleBuffer doubleData = col.data.asDoubleBuffer(); + doubleData.position(pos); + doubleData.put(values); + } + + private void putBoolArray2D(Column col, long vectorIdx, boolean[][] values) throws SQLException { + putBoolArray2D(col, vectorIdx, values, null); + } + + private void putBoolArray2D(Column outerCol, long vectorIdx, boolean[][] values, boolean[][] nullMask) + throws SQLException { + Column innerCol = arrayInnerColumn(outerCol, DUCKDB_TYPE_ARRAY); + checkArrayLength(innerCol, values.length); + Column col = arrayInnerColumn(innerCol, DUCKDB_TYPE_BOOLEAN); + + byte[] buf = new byte[(int) col.arraySize]; + + for (int i = 0; i < values.length; i++) { + boolean[] childValues = values[i]; + + if (childValues == null) { + innerCol.setNullOnArrayIdx(vectorIdx, i); + continue; + } + checkArrayLength(col, childValues.length); + if (nullMask != null) { + setArrayNullMask(col, vectorIdx, nullMask[i], childValues.length, i); + } + + for (int j = 0; j < childValues.length; j++) { + buf[j] = (byte) (childValues[j] ? 1 : 0); + } + + int pos = (int) ((vectorIdx * innerCol.arraySize + i) * col.arraySize); + col.data.position(pos); + col.data.put(buf); + } + } + + private void putByteArray2D(Column outerCol, long vectorIdx, byte[][] values) throws SQLException { + putByteArray2D(outerCol, vectorIdx, values, null); + } + + private void putByteArray2D(Column outerCol, long vectorIdx, byte[][] values, boolean[][] nullMask) + throws SQLException { + Column innerCol = arrayInnerColumn(outerCol, DUCKDB_TYPE_ARRAY); + checkArrayLength(innerCol, values.length); + Column col = arrayInnerColumn(innerCol, int8Types); + + for (int i = 0; i < values.length; i++) { + byte[] childValues = values[i]; + + if (childValues == null) { + innerCol.setNullOnArrayIdx(vectorIdx, i); + continue; + } + checkArrayLength(col, childValues.length); + if (nullMask != null) { + setArrayNullMask(col, vectorIdx, nullMask[i], childValues.length, i); + } + + int pos = (int) ((vectorIdx * innerCol.arraySize + i) * col.arraySize); + col.data.position(pos); + col.data.put(childValues); + } + } + + private void putShortArray2D(Column outerCol, long vectorIdx, short[][] values) throws SQLException { + putShortArray2D(outerCol, vectorIdx, values, null); + } + + private void putShortArray2D(Column outerCol, long vectorIdx, short[][] values, boolean[][] nullMask) + throws SQLException { + Column innerCol = arrayInnerColumn(outerCol, DUCKDB_TYPE_ARRAY); + checkArrayLength(innerCol, values.length); + Column col = arrayInnerColumn(innerCol, int16Types); + + for (int i = 0; i < values.length; i++) { + short[] childValues = values[i]; + + if (childValues == null) { + innerCol.setNullOnArrayIdx(vectorIdx, i); + continue; + } + checkArrayLength(col, childValues.length); + if (nullMask != null) { + setArrayNullMask(col, vectorIdx, nullMask[i], childValues.length, i); + } + + ShortBuffer shortBuffer = col.data.asShortBuffer(); + int pos = (int) ((vectorIdx * innerCol.arraySize + i) * col.arraySize); + shortBuffer.position(pos); + shortBuffer.put(childValues); + } + } + + private void putIntArray2D(Column col, long vectorIdx, int[][] values) throws SQLException { + putIntArray2D(col, vectorIdx, values, null); + } + + private void putIntArray2D(Column outerCol, long vectorIdx, int[][] values, boolean[][] nullMask) + throws SQLException { + Column innerCol = arrayInnerColumn(outerCol, DUCKDB_TYPE_ARRAY); + checkArrayLength(innerCol, values.length); + Column col = arrayInnerColumn(innerCol, int32Types); + + for (int i = 0; i < values.length; i++) { + int[] childValues = values[i]; + + if (childValues == null) { + innerCol.setNullOnArrayIdx(rowIdx, i); + continue; + } + checkArrayLength(col, childValues.length); + if (nullMask != null) { + setArrayNullMask(col, vectorIdx, nullMask[i], childValues.length, i); + } + + IntBuffer intData = col.data.asIntBuffer(); + int pos = (int) ((vectorIdx * innerCol.arraySize + i) * col.arraySize); + intData.position(pos); + intData.put(childValues); + } + } + + private void putLongArray2D(Column outerCol, long vectorIdx, long[][] values) throws SQLException { + putLongArray2D(outerCol, vectorIdx, values, null); + } + + private void putLongArray2D(Column outerCol, long vectorIdx, long[][] values, boolean[][] nullMask) + throws SQLException { + Column innerCol = arrayInnerColumn(outerCol, DUCKDB_TYPE_ARRAY); + checkArrayLength(innerCol, values.length); + Column col = arrayInnerColumn(innerCol, int64Types); + + for (int i = 0; i < values.length; i++) { + long[] childValues = values[i]; + + if (childValues == null) { + innerCol.setNullOnArrayIdx(vectorIdx, i); + continue; + } + checkArrayLength(col, childValues.length); + if (nullMask != null) { + setArrayNullMask(col, vectorIdx, nullMask[i], childValues.length, i); + } + + LongBuffer longData = col.data.asLongBuffer(); + int pos = (int) ((vectorIdx * innerCol.arraySize + i) * col.arraySize); + longData.position(pos); + longData.put(childValues); } - - return currentColumn; } - private Column currentArrayInnerColumn(CAPIType ctype) throws SQLException { - return currentArrayInnerColumn(ctype.typeArray); + private void putFloatArray2D(Column outerCol, long vectorIdx, float[][] values) throws SQLException { + putFloatArray2D(outerCol, vectorIdx, values, null); } - private Column currentArrayInnerColumn(CAPIType[] ctypes) throws SQLException { - Column parentCol = currentColumn(); - if (parentCol.colType != DUCKDB_TYPE_ARRAY && parentCol.colType != DUCKDB_TYPE_LIST) { - throw new SQLException(createErrMsg("invalid array/list column type: '" + parentCol.colType + "'")); - } + private void putFloatArray2D(Column outerCol, long vectorIdx, float[][] values, boolean[][] nullMask) + throws SQLException { + Column innerCol = arrayInnerColumn(outerCol, DUCKDB_TYPE_ARRAY); + checkArrayLength(innerCol, values.length); + Column col = arrayInnerColumn(innerCol, DUCKDB_TYPE_FLOAT); - Column col = parentCol.children.get(0); - for (CAPIType ct : ctypes) { - if (col.colType == ct) { - return col; + for (int i = 0; i < values.length; i++) { + float[] childValues = values[i]; + + if (childValues == null) { + innerCol.setNullOnArrayIdx(vectorIdx, i); + continue; + } + checkArrayLength(col, childValues.length); + if (nullMask != null) { + setArrayNullMask(col, vectorIdx, nullMask[i], childValues.length, i); } + + FloatBuffer floatData = col.data.asFloatBuffer(); + int pos = (int) ((vectorIdx * innerCol.arraySize + i) * col.arraySize); + floatData.position(pos); + floatData.put(childValues); } - throw new SQLException(createErrMsg("invalid array/list inner column type, expected one of: '" + - Arrays.toString(ctypes) + "', actual: '" + col.colType + "'")); } - private Column currentNestedArrayInnerColumn(CAPIType ctype) throws SQLException { - return currentNestedArrayInnerColumn(ctype.typeArray); + private void putDoubleArray2D(Column outerCol, long vectorIdx, double[][] values) throws SQLException { + putDoubleArray2D(outerCol, vectorIdx, values, null); } - private Column currentNestedArrayInnerColumn(CAPIType[] ctypes) throws SQLException { - Column parentCol = currentColumn(); - if (parentCol.colType != DUCKDB_TYPE_ARRAY) { - throw new SQLException(createErrMsg("invalid array column type: '" + parentCol.colType + "'")); - } + private void putDoubleArray2D(Column outerCol, long vectorIdx, double[][] values, boolean[][] nullMask) + throws SQLException { + Column innerCol = arrayInnerColumn(outerCol, DUCKDB_TYPE_ARRAY); + checkArrayLength(innerCol, values.length); + Column col = arrayInnerColumn(innerCol, DUCKDB_TYPE_DOUBLE); - Column arrayCol = parentCol.children.get(0); - if (arrayCol.colType != DUCKDB_TYPE_ARRAY) { - throw new SQLException(createErrMsg("invalid nested array column type: '" + arrayCol.colType + "'")); - } + for (int i = 0; i < values.length; i++) { + double[] childValues = values[i]; - Column col = arrayCol.children.get(0); - for (CAPIType ct : ctypes) { - if (col.colType == ct) { - return col; + if (childValues == null) { + innerCol.setNullOnArrayIdx(vectorIdx, i); + continue; + } + checkArrayLength(col, childValues.length); + if (nullMask != null) { + setArrayNullMask(col, vectorIdx, nullMask[i], childValues.length, i); } - } - throw new SQLException(createErrMsg("invalid nested array inner column type, expected one of: '" + - Arrays.toString(ctypes) + "', actual: '" + col.colType + "'")); - } - private void checkCurrentColumnType(CAPIType ctype) throws SQLException { - checkCurrentColumnType(ctype.typeArray); + DoubleBuffer doubleBuffer = col.data.asDoubleBuffer(); + int pos = (int) ((vectorIdx * innerCol.arraySize + i) * col.arraySize); + doubleBuffer.position(pos); + doubleBuffer.put(childValues); + } } - private void checkCurrentColumnType(CAPIType[] ctypes) throws SQLException { - Column col = currentColumn(); - checkColumnType(col, ctypes); - } + private void putObjectArrayOrList(Column col, long vectorIdx, Iterator iter, int count) throws SQLException { + checkArrayLength(col, count); + prepareListColumn(col, vectorIdx, count); - private void checkColumnType(Column col, CAPIType ctype) throws SQLException { - checkColumnType(col, ctype.typeArray); - } + final long offset; + if (col.parent.colType == DUCKDB_TYPE_LIST) { + offset = col.listSize - count; + } else { + offset = vectorIdx * col.arraySize; + } - private void checkColumnType(Column col, CAPIType[] ctypes) throws SQLException { - for (CAPIType ct : ctypes) { - if (col.colType == ct) { - return; + for (long i = 0; i < count; i++) { + if (!iter.hasNext()) { + throw new SQLException( + createErrMsg("invalid iterator elements count, expected: " + count + ", actual" + i)); } + + Object value = iter.next(); + long innerVectorIdx = offset + i; + + putCompositeElement(col, innerVectorIdx, value); } - throw new SQLException(createErrMsg("invalid column type, expected one of: '" + Arrays.toString(ctypes) + - "', actual: '" + col.colType + "'")); } - private void checkArrayLength(Column col, int length) throws SQLException { - if (null == col.parent) { - throw new SQLException(createErrMsg("invalid array/list column specified")); + private void putMap(Column col, long vectorIdx, Map map) throws SQLException { + prepareListColumn(col, vectorIdx, map.size()); + long offset = col.listSize - map.size(); + + long i = 0; + List values = new ArrayList<>(2); + values.add(null); + values.add(null); + for (Map.Entry en : map.entrySet()) { + values.set(0, en.getKey()); + values.set(1, en.getValue()); + long innerVectorIdx = offset + i; + putCompositeElementStruct(col, innerVectorIdx, values); + i += 1; } - switch (col.parent.colType) { - case DUCKDB_TYPE_LIST: + } + + private void putCompositeElement(Column col, long vectorIdx, Object value) throws SQLException { + if (null == value) { + col.setNull(vectorIdx); return; - case DUCKDB_TYPE_ARRAY: + } + switch (col.colType) { + // primitive wrappers are intended to be used only + // for struct/union fields + case DUCKDB_TYPE_BOOLEAN: { + Boolean bool = (Boolean) value; + byte num = (byte) (bool ? 1 : 0); + putByte(col, vectorIdx, num); break; - default: - throw new SQLException(createErrMsg("invalid array/list column type: " + col.colType)); } - if (col.arraySize != length) { - throw new SQLException( - createErrMsg("invalid array size, expected: " + col.arraySize + ", actual: " + length)); + case DUCKDB_TYPE_TINYINT: { + Byte num = (Byte) value; + putByte(col, vectorIdx, num); + break; } - } - - private void setNullMask(Column col, boolean[] nullMask, int elementsCount) throws SQLException { - if (null == col.parent) { - throw new SQLException(createErrMsg("invalid array/list column specified")); + case DUCKDB_TYPE_SMALLINT: { + Short num = (Short) value; + putShort(col, vectorIdx, num); + break; + } + case DUCKDB_TYPE_INTEGER: { + Integer num = (Integer) value; + putInt(col, vectorIdx, num); + break; + } + case DUCKDB_TYPE_BIGINT: { + Long num = (Long) value; + putLong(col, vectorIdx, num); + break; + } + case DUCKDB_TYPE_HUGEINT: { + BigInteger num = (BigInteger) value; + putBigInteger(col, vectorIdx, num); + break; + } + case DUCKDB_TYPE_FLOAT: { + Float num = (Float) value; + putFloat(col, vectorIdx, num); + break; + } + case DUCKDB_TYPE_DOUBLE: { + Double num = (Double) value; + putDouble(col, vectorIdx, num); + break; + } + case DUCKDB_TYPE_DECIMAL: { + BigDecimal num = (BigDecimal) value; + putDecimal(col, vectorIdx, num); + break; + } + case DUCKDB_TYPE_VARCHAR: { + String st = (String) value; + byte[] bytes = utf8(st); + putStringOrBlob(col, vectorIdx, bytes); + break; + } + case DUCKDB_TYPE_UUID: { + UUID uid = (UUID) value; + long mostSigBits = uid.getMostSignificantBits(); + long leastSigBits = uid.getLeastSignificantBits(); + putUUID(col, vectorIdx, mostSigBits, leastSigBits); + break; + } + case DUCKDB_TYPE_DATE: { + LocalDate ld = (LocalDate) value; + putLocalDate(col, vectorIdx, ld); + break; + } + case DUCKDB_TYPE_TIME: { + LocalTime lt = (LocalTime) value; + putLocalTime(col, vectorIdx, lt); + break; + } + case DUCKDB_TYPE_TIME_TZ: { + OffsetTime ot = (OffsetTime) value; + putOffsetTime(col, vectorIdx, ot); + break; + } + case DUCKDB_TYPE_TIMESTAMP: + case DUCKDB_TYPE_TIMESTAMP_MS: + case DUCKDB_TYPE_TIMESTAMP_NS: + case DUCKDB_TYPE_TIMESTAMP_S: + if (value instanceof LocalDateTime) { + LocalDateTime ldt = (LocalDateTime) value; + putLocalDateTime(col, vectorIdx, ldt); + } else if (value instanceof java.util.Date) { + Date dt = (Date) value; + putDate(col, vectorIdx, dt); + } else { + throw new SQLException(createErrMsg("invalid object type for timestamp column, expected one of: [" + + LocalDateTime.class.getName() + ", " + Date.class.getName() + + "], actual: [" + value.getClass().getName() + "]")); + } + break; + case DUCKDB_TYPE_TIMESTAMP_TZ: { + OffsetDateTime odt = (OffsetDateTime) value; + putOffsetDateTime(col, vectorIdx, odt); + break; } - switch (col.parent.colType) { case DUCKDB_TYPE_ARRAY: - setArrayNullMask(col, nullMask, elementsCount, 0); - return; - case DUCKDB_TYPE_LIST: - setListNullMask(col, nullMask, elementsCount); - return; + putCompositeElementArray(col, vectorIdx, value); + break; + case DUCKDB_TYPE_LIST: { + Collection collection = (Collection) value; + if (col.children.size() != 1) { + throw new SQLException(createErrMsg("invalid list column")); + } + Column innerCol = col.children.get(0); + putObjectArrayOrList(innerCol, vectorIdx, collection.iterator(), collection.size()); + break; + } + case DUCKDB_TYPE_MAP: { + Map map = (Map) value; + if (col.children.size() != 1) { + throw new SQLException(createErrMsg("invalid map column")); + } + Column innerCol = col.children.get(0); + putMap(innerCol, vectorIdx, map); + break; + } + case DUCKDB_TYPE_STRUCT: { + putCompositeElementStruct(col, vectorIdx, value); + break; + } + case DUCKDB_TYPE_UNION: { + putCompositeElementUnion(col, vectorIdx, value); + break; + } default: - throw new SQLException(createErrMsg("invalid array/list column type: " + col.colType)); + throw new SQLException(createErrMsg("unsupported composite column, inner type: " + col.colType)); } } - private void setArrayNullMask(Column col, boolean[] nullMask, int elementsCount, int parentArrayIdx) - throws SQLException { - if (null == nullMask) { - return; + private void putCompositeElementArray(Column col, long vectorIdx, Object value) throws SQLException { + if (col.children.size() != 1) { + throw new SQLException(createErrMsg("invalid array column")); } - if (nullMask.length != elementsCount) { - throw new SQLException( - createErrMsg("invalid null mask size, expected: " + elementsCount + ", actual: " + nullMask.length)); + Column innerCol = col.children.get(0); + switch (innerCol.colType) { + case DUCKDB_TYPE_BOOLEAN: { + boolean[] arr = (boolean[]) value; + putBoolArray(col, vectorIdx, arr); + break; } - for (int i = 0; i < nullMask.length; i++) { - if (nullMask[i]) { - col.setNullOnArrayIdx(rowIdx, (int) (i + col.arraySize * parentArrayIdx)); - } + case DUCKDB_TYPE_TINYINT: { + byte[] arr = (byte[]) value; + putByteArray(col, vectorIdx, arr); + break; } - } - - private void setListNullMask(Column col, boolean[] nullMask, int elementsCount) throws SQLException { - if (null == nullMask) { - return; + case DUCKDB_TYPE_SMALLINT: { + short[] arr = (short[]) value; + putShortArray(col, vectorIdx, arr); + break; } - if (nullMask.length != elementsCount) { - throw new SQLException( - createErrMsg("invalid null mask size, expected: " + elementsCount + ", actual: " + nullMask.length)); + case DUCKDB_TYPE_INTEGER: { + int[] arr = (int[]) value; + putIntArray(col, vectorIdx, arr); + break; } - if (col.listSize < elementsCount) { - throw new SQLException( - createErrMsg("invalid list state, list size: " + col.listSize + ", elements count: " + elementsCount)); + case DUCKDB_TYPE_BIGINT: { + long[] arr = (long[]) value; + putLongArray(col, vectorIdx, arr); + break; } - for (int i = 0; i < nullMask.length; i++) { - if (nullMask[i]) { - long vectorPos = col.listSize - elementsCount + i; - col.setNullOnPos(vectorPos); - } + case DUCKDB_TYPE_FLOAT: { + float[] arr = (float[]) value; + putFloatArray(col, vectorIdx, arr); + break; } - } - - private Column currentDecimalColumnWithRowPos(CAPIType decimalInternalType) throws SQLException { - Column col = currentColumnWithRowPos(DUCKDB_TYPE_DECIMAL); - if (col.decimalInternalType != decimalInternalType) { - throw new SQLException(createErrMsg("invalid decimal internal type, expected: '" + col.decimalInternalType + - "', actual: '" + decimalInternalType + "'")); + case DUCKDB_TYPE_DOUBLE: { + double[] arr = (double[]) value; + putDoubleArray(col, vectorIdx, arr); + break; } - setRowPos(col, col.decimalInternalType.widthBytes); - return col; - } - - private Column currentColumnWithRowPos(CAPIType ctype) throws SQLException { - return currentColumnWithRowPos(ctype.typeArray); - } - - private void setRowPos(Column col, long widthBytes) throws SQLException { - long pos = rowIdx * widthBytes; - if (pos >= col.data.capacity()) { - throw new SQLException( - createErrMsg("invalid calculated position: " + pos + ", type: '" + col.colType + "'")); + case DUCKDB_TYPE_ARRAY: { + if (value instanceof boolean[][]) { + boolean[][] arr = (boolean[][]) value; + putBoolArray2D(col, vectorIdx, arr); + } else if (value instanceof byte[][]) { + byte[][] arr = (byte[][]) value; + putByteArray2D(col, vectorIdx, arr); + } else if (value instanceof short[][]) { + short[][] arr = (short[][]) value; + putShortArray2D(col, vectorIdx, arr); + } else if (value instanceof int[][]) { + int[][] arr = (int[][]) value; + putIntArray2D(col, vectorIdx, arr); + } else if (value instanceof long[][]) { + long[][] arr = (long[][]) value; + putLongArray2D(col, vectorIdx, arr); + } else if (value instanceof float[][]) { + float[][] arr = (float[][]) value; + putFloatArray2D(col, vectorIdx, arr); + } else if (value instanceof double[][]) { + double[][] arr = (double[][]) value; + putDoubleArray2D(col, vectorIdx, arr); + } else { + throw new SQLException(createErrMsg("unsupported 2D array type: " + value.getClass().getName())); + } + break; + } + default: + throw new SQLException(createErrMsg("unsupported array type: " + innerCol.colType)); } - col.data.position((int) (pos)); } - private Column currentColumnWithRowPos(CAPIType[] ctypes) throws SQLException { - Column col = currentColumn(); - - boolean typeMatches = false; - for (CAPIType ct : ctypes) { - if (col.colType.typeId == ct.typeId) { - typeMatches = true; - } - if (col.colType.widthBytes != ct.widthBytes) { - throw new SQLException( - createErrMsg("invalid columns type width, expected: '" + ct + "', actual: '" + col.colType + "'")); - } - } - if (!typeMatches) { - String[] typeStrs = new String[ctypes.length]; - for (int i = 0; i < ctypes.length; i++) { - typeStrs[i] = String.valueOf(ctypes[i]); + private void putCompositeElementStruct(Column structCol, long vectorIdx, Object structValue) throws SQLException { + final Collection collection; + if (structValue instanceof Map) { + if (structValue instanceof LinkedHashMap) { + LinkedHashMap map = (LinkedHashMap) structValue; + collection = map.values(); + } else { + throw new SQLException(createErrMsg( + "struct values must be specified as an instance of a 'java.util.LinkedHashMap' or as a collection of objects, actual class: " + + structValue.getClass().getName())); } - throw new SQLException(createErrMsg("invalid columns type, expected one of: '" + Arrays.toString(typeStrs) + - "', actual: '" + col.colType + "'")); + } else { + collection = (Collection) structValue; } - if (col.colType.widthBytes > 0) { - setRowPos(col, col.colType.widthBytes); + if (structCol.children.size() != collection.size()) { + throw new SQLException(createErrMsg("invalid struct object specified, expected fields count: " + + structCol.children.size() + ", actual: " + collection.size())); } - return col; - } - - private Column currentTopLevelColumn() { - if (null == currentColumn) { - return null; - } - Column col = currentColumn; - while (null != col.parent) { - col = col.parent; + int i = 0; + for (Object value : collection) { + Column col = structCol.children.get(i); + putCompositeElement(col, vectorIdx, value); + i += 1; } - return col; } - private void checkDecimalPrecision(BigDecimal value, CAPIType decimalInternalType, int maxPrecision) - throws SQLException { - if (value.precision() > maxPrecision) { - throw new SQLException(createErrMsg("invalid decimal precision, value: " + value.precision() + - ", max value: " + maxPrecision + - ", decimal internal type: " + decimalInternalType)); + private void putCompositeElementUnion(Column unionCol, long vectorIdx, Object unionValue) throws SQLException { + if (!(unionValue instanceof AbstractMap.SimpleEntry)) { + throw new SQLException(createErrMsg( + "union values must be specified as an instance of 'java.util.AbstractMap.SimpleEntry', actual type: " + + unionValue.getClass().getName())); } + AbstractMap.SimpleEntry entry = (AbstractMap.SimpleEntry) unionValue; + String tag = String.valueOf(entry.getKey()); + Column col = putUnionTag(unionCol, vectorIdx, tag); + putCompositeElement(col, vectorIdx, entry.getValue()); } - private DuckDBAppender appendStringOrBlobInternal(CAPIType ctype, byte[] bytes) throws SQLException { - if (writeInlinedStrings && bytes.length < STRING_MAX_INLINE_BYTES) { - Column col = currentColumnWithRowPos(ctype); - col.data.putInt(bytes.length); - if (bytes.length > 0) { - col.data.put(bytes); - } - } else { - Column col = currentColumn(); - checkColumnType(col, ctype); - appenderRefLock.lock(); - try { - checkOpen(); - duckdb_vector_assign_string_element_len(col.vectorRef, rowIdx, bytes); - } finally { - appenderRefLock.unlock(); + private Column putUnionTag(Column col, long vectorIdx, String tag) throws SQLException { + int fieldWithTag = 0; + for (int i = 1; i < col.children.size(); i++) { + Column childCol = col.children.get(i); + if (childCol.structFieldName.equals(tag)) { + fieldWithTag = i; } } + if (0 == fieldWithTag) { + throw new SQLException(createErrMsg("specified union field not found, value: '" + tag + "'")); + } - incrementColOrStructFieldIdx(); - return this; + // set tag + Column tagCol = col.children.get(0); + putByte(tagCol, vectorIdx, (byte) (fieldWithTag - 1)); + // set other fields to NULL + for (int i = 1; i < col.children.size(); i++) { + if (i == fieldWithTag) { + continue; + } + Column childCol = col.children.get(i); + childCol.setNull(vectorIdx); + } + return col.children.get(fieldWithTag); } + // state invariants + private boolean rowBegunInvariant() { return null != currentColumn; } @@ -1510,15 +2093,18 @@ private boolean readyForANewRowInvariant() { return null == currentColumn && null == prevColumn; } - private int prepareListColumn(Column innerCol, long listElementsCount) throws SQLException { + // list helpers + + private int prepareListColumn(Column innerCol, long vectorIdx, long listElementsCount) throws SQLException { if (null == innerCol.parent) { throw new SQLException(createErrMsg("invalid array/list column specified")); } Column col = innerCol.parent; switch (col.colType) { case DUCKDB_TYPE_ARRAY: - return (int) (rowIdx * innerCol.arraySize); + return (int) (vectorIdx * innerCol.arraySize); case DUCKDB_TYPE_LIST: + case DUCKDB_TYPE_MAP: break; default: throw new SQLException(createErrMsg("invalid array/list column type: " + col.colType)); @@ -1528,7 +2114,7 @@ private int prepareListColumn(Column innerCol, long listElementsCount) throws SQ checkOpen(); long offset = duckdb_list_vector_get_size(col.vectorRef); LongBuffer longBuffer = col.data.asLongBuffer(); - int pos = (int) (rowIdx * DUCKDB_TYPE_LIST.widthBytes / Long.BYTES); + int pos = (int) (vectorIdx * DUCKDB_TYPE_LIST.widthBytes / Long.BYTES); longBuffer.position(pos); longBuffer.put(offset); longBuffer.put(listElementsCount); @@ -1550,6 +2136,8 @@ private int prepareListColumn(Column innerCol, long listElementsCount) throws SQ } } + // string helpers + private static byte[] utf8(String str) { if (null == str) { return null; @@ -1564,6 +2152,8 @@ private static String strFromUTF8(byte[] utf8) { return new String(utf8, UTF_8); } + // static methods + private static ByteBuffer createAppender(DuckDBConnection conn, String catalog, String schema, String table) throws SQLException { conn.checkOpen(); @@ -1755,7 +2345,7 @@ private Column(Column parent, int idx, ByteBuffer colTypeRef, ByteBuffer vector, } void reset(long listSize) throws SQLException { - if (null == parent || parent.colType != DUCKDB_TYPE_LIST) { + if (null == parent || !(parent.colType == DUCKDB_TYPE_LIST || parent.colType == DUCKDB_TYPE_MAP)) { throw new SQLException("invalid list column"); } this.listSize = listSize; @@ -1792,32 +2382,36 @@ void destroy() { } } - void setNull(long rowIdx) throws SQLException { - if (1 != arraySize) { - throw new SQLException("Invalid API usage for array, size: " + arraySize); - } - setNullOnArrayIdx(rowIdx, 0); - if (colType == DUCKDB_TYPE_LIST) { - return; - } - for (Column col : children) { - for (int i = 0; i < col.arraySize; i++) { - col.setNullOnArrayIdx(rowIdx, i); + void setNull(long vectorIdx) throws SQLException { + if (colType == DUCKDB_TYPE_ARRAY) { + setNullOnArrayIdx(vectorIdx, 0); + for (Column col : children) { + for (int i = 0; i < col.arraySize; i++) { + col.setNullOnArrayIdx(vectorIdx, i); + } + } + } else { + setNullOnVectorIdx(vectorIdx); + if (colType == DUCKDB_TYPE_LIST || colType == DUCKDB_TYPE_MAP) { + return; + } + for (Column col : children) { + col.setNull(vectorIdx); } } } void setNullOnArrayIdx(long rowIdx, int arrayIdx) { - long vectorPos = rowIdx * arraySize * parentArraySize() + arrayIdx; - setNullOnPos(vectorPos); + long vectorIdx = rowIdx * arraySize * parentArraySize() + arrayIdx; + setNullOnVectorIdx(vectorIdx); } - void setNullOnPos(long vectorPos) { - long validityPos = vectorPos / 64; + void setNullOnVectorIdx(long vectorIdx) { + long validityPos = vectorIdx / 64; LongBuffer entries = this.validity.asLongBuffer(); entries.position((int) validityPos); long mask = entries.get(); - long idxInEntry = vectorPos % 64; + long idxInEntry = vectorIdx % 64; mask &= ~(1L << idxInEntry); entries.position((int) validityPos); entries.put(mask); @@ -1839,7 +2433,7 @@ long parentArraySize() { } long vectorSize() { - if (null != parent && parent.colType == DUCKDB_TYPE_LIST) { + if (null != parent && (parent.colType == DUCKDB_TYPE_LIST || parent.colType == DUCKDB_TYPE_MAP)) { return listSize * widthBytes(); } else { return duckdb_vector_size() * widthBytes() * arraySize * parentArraySize(); diff --git a/src/main/java/org/duckdb/DuckDBBindings.java b/src/main/java/org/duckdb/DuckDBBindings.java index efe37df0f..a8174d5a9 100644 --- a/src/main/java/org/duckdb/DuckDBBindings.java +++ b/src/main/java/org/duckdb/DuckDBBindings.java @@ -169,7 +169,7 @@ enum CAPIType { // struct type, only useful as logical type DUCKDB_TYPE_STRUCT(25, 0), // map type, only useful as logical type - DUCKDB_TYPE_MAP(26), + DUCKDB_TYPE_MAP(26, 16), // duckdb_array, only useful as logical type DUCKDB_TYPE_ARRAY(33, 0), // duckdb_hugeint diff --git a/src/main/java/org/duckdb/DuckDBResultSetMetaData.java b/src/main/java/org/duckdb/DuckDBResultSetMetaData.java index 9bc233be5..2461dfa83 100644 --- a/src/main/java/org/duckdb/DuckDBResultSetMetaData.java +++ b/src/main/java/org/duckdb/DuckDBResultSetMetaData.java @@ -12,7 +12,7 @@ import java.time.OffsetDateTime; import java.time.OffsetTime; import java.util.ArrayList; -import java.util.HashMap; +import java.util.LinkedHashMap; import java.util.UUID; public class DuckDBResultSetMetaData implements ResultSetMetaData { @@ -234,7 +234,7 @@ protected static String type_to_javaString(DuckDBColumnType type) { case ARRAY: return DuckDBArray.class.getName(); case MAP: - return HashMap.class.getName(); + return LinkedHashMap.class.getName(); case STRUCT: return DuckDBStruct.class.getName(); default: diff --git a/src/main/java/org/duckdb/DuckDBVector.java b/src/main/java/org/duckdb/DuckDBVector.java index 8187f5d8c..7e4b923b5 100644 --- a/src/main/java/org/duckdb/DuckDBVector.java +++ b/src/main/java/org/duckdb/DuckDBVector.java @@ -286,7 +286,7 @@ Map getMap(int idx) throws SQLException { } Object[] entries = (Object[]) (((Array) varlen_data[idx]).getArray()); - Map result = new HashMap<>(); + Map result = new LinkedHashMap<>(); for (Object entry : entries) { Object[] entry_val = ((Struct) entry).getAttributes(); diff --git a/src/test/java/org/duckdb/TestAppender.java b/src/test/java/org/duckdb/TestAppender.java index 21828357f..598cceee1 100644 --- a/src/test/java/org/duckdb/TestAppender.java +++ b/src/test/java/org/duckdb/TestAppender.java @@ -144,8 +144,8 @@ public static void test_appender_uuid() throws Exception { Statement stmt = conn.createStatement()) { stmt.execute("CREATE TABLE tab1(col1 INT, col2 UUID)"); - UUID uuid1 = UUID.randomUUID(); - UUID uuid2 = UUID.randomUUID(); + UUID uuid1 = UUID.fromString("777dfbdb-83e7-40f5-ae1b-e12215bdd798"); + UUID uuid2 = UUID.fromString("b8708825-3b58-45a1-9a6e-dab053c3f387"); try (DuckDBAppender appender = conn.createAppender("tab1")) { appender.beginRow().append(1).append(uuid1).endRow(); appender.beginRow().append(2).append(uuid2).endRow(); diff --git a/src/test/java/org/duckdb/TestAppenderCollection.java b/src/test/java/org/duckdb/TestAppenderCollection.java index 6002cbb2d..58f13459b 100644 --- a/src/test/java/org/duckdb/TestAppenderCollection.java +++ b/src/test/java/org/duckdb/TestAppenderCollection.java @@ -1,12 +1,15 @@ package org.duckdb; +import static java.util.Arrays.asList; import static org.duckdb.TestDuckDBJDBC.JDBC_URL; import static org.duckdb.test.Assertions.*; import static org.duckdb.test.Assertions.assertFalse; +import static org.duckdb.test.Helpers.createMap; -import java.sql.DriverManager; -import java.sql.ResultSet; -import java.sql.Statement; +import java.sql.*; +import java.time.*; +import java.util.*; +import java.util.Date; public class TestAppenderCollection { public static void test_appender_array_basic() throws Exception { @@ -99,54 +102,6 @@ public static void test_appender_array_bool() throws Exception { } } - public static void test_appender_nested_array_bool() throws Exception { - try (DuckDBConnection conn = DriverManager.getConnection(JDBC_URL).unwrap(DuckDBConnection.class); - Statement stmt = conn.createStatement()) { - - int count = 1 << 11; // auto flush - int tail = 17; // flushed on close - int arrayLen = (1 << 6) + 5; // increase this for stress tests - int childLen = (1 << 6) + 7; - - stmt.execute("CREATE TABLE tab1(col1 INT, col2 BOOLEAN[" + childLen + "][" + arrayLen + "])"); - - try (DuckDBAppender appender = conn.createAppender("tab1")) { - for (int i = 0; i < count + tail; i++) { - boolean[][] arr = new boolean[arrayLen][childLen]; - for (int j = 0; j < arrayLen; j++) { - for (int k = 0; k < childLen; k++) { - arr[j][k] = 0 == (i + j + k) % 2; - } - } - appender.beginRow().append(i).append(arr).endRow(); - } - } - - try (ResultSet rs = stmt.executeQuery("SELECT col1, col2[" + (arrayLen - 1) + "][" + (childLen - 1) + - "] FROM tab1 ORDER BY col1")) { - for (int i = 0; i < count + tail; i++) { - assertTrue(rs.next()); - assertEquals(rs.getInt(1), i); - assertEquals(rs.getBoolean(2), 0 == (i + arrayLen + childLen - 4) % 2); - } - assertFalse(rs.next()); - } - - int row = count - 2; - try (Statement stmt2 = conn.createStatement(); - ResultSet rs2 = stmt2.executeQuery( - "SELECT unnest(ucol2) FROM (SELECT unnest(col2) as ucol2 FROM tab1 WHERE col1 = " + row + ")")) { - for (int j = 0; j < arrayLen; j++) { - for (int k = 0; k < childLen; k++) { - assertTrue(rs2.next()); - assertEquals(rs2.getBoolean(1), 0 == (row + j + k) % 2); - } - } - assertFalse(rs2.next()); - } - } - } - public static void test_appender_array_tinyint() throws Exception { int count = 1 << 11; // auto flush int tail = 16; // flushed on close @@ -192,54 +147,6 @@ public static void test_appender_array_tinyint() throws Exception { } } - public static void test_appender_nested_array_tinyint() throws Exception { - try (DuckDBConnection conn = DriverManager.getConnection(JDBC_URL).unwrap(DuckDBConnection.class); - Statement stmt = conn.createStatement()) { - - int count = 1 << 11; // auto flush - int tail = 17; // flushed on close - int arrayLen = (1 << 6) + 5; // increase this for stress tests - int childLen = (1 << 6) + 7; - - stmt.execute("CREATE TABLE tab1(col1 INT, col2 TINYINT[" + childLen + "][" + arrayLen + "])"); - - try (DuckDBAppender appender = conn.createAppender("tab1")) { - for (int i = 0; i < count + tail; i++) { - byte[][] arr = new byte[arrayLen][childLen]; - for (int j = 0; j < arrayLen; j++) { - for (int k = 0; k < childLen; k++) { - arr[j][k] = (byte) (i + j + k); - } - } - appender.beginRow().append(i).appendByteArray(arr).endRow(); - } - } - - try (ResultSet rs = stmt.executeQuery("SELECT col1, col2[" + (arrayLen - 1) + "][" + (childLen - 1) + - "] FROM tab1 ORDER BY col1")) { - for (int i = 0; i < count + tail; i++) { - assertTrue(rs.next()); - assertEquals(rs.getInt(1), i); - assertEquals(rs.getByte(2), (byte) (i + arrayLen + childLen - 4)); - } - assertFalse(rs.next()); - } - - int row = count - 2; - try (Statement stmt2 = conn.createStatement(); - ResultSet rs2 = stmt2.executeQuery( - "SELECT unnest(ucol2) FROM (SELECT unnest(col2) as ucol2 FROM tab1 WHERE col1 = " + row + ")")) { - for (int j = 0; j < arrayLen; j++) { - for (int k = 0; k < childLen; k++) { - assertTrue(rs2.next()); - assertEquals(rs2.getByte(1), (byte) (row + j + k)); - } - } - assertFalse(rs2.next()); - } - } - } - public static void test_appender_array_smallint() throws Exception { int count = 1 << 11; // auto flush int tail = 16; // flushed on close @@ -285,54 +192,6 @@ public static void test_appender_array_smallint() throws Exception { } } - public static void test_appender_nested_array_smallint() throws Exception { - try (DuckDBConnection conn = DriverManager.getConnection(JDBC_URL).unwrap(DuckDBConnection.class); - Statement stmt = conn.createStatement()) { - - int count = 1 << 11; // auto flush - int tail = 17; // flushed on close - int arrayLen = (1 << 6) + 5; // increase this for stress tests - int childLen = (1 << 6) + 7; - - stmt.execute("CREATE TABLE tab1(col1 INT, col2 SMALLINT[" + childLen + "][" + arrayLen + "])"); - - try (DuckDBAppender appender = conn.createAppender("tab1")) { - for (int i = 0; i < count + tail; i++) { - short[][] arr = new short[arrayLen][childLen]; - for (int j = 0; j < arrayLen; j++) { - for (int k = 0; k < childLen; k++) { - arr[j][k] = (short) (i + j + k); - } - } - appender.beginRow().append(i).append(arr).endRow(); - } - } - - try (ResultSet rs = stmt.executeQuery("SELECT col1, col2[" + (arrayLen - 1) + "][" + (childLen - 1) + - "] FROM tab1 ORDER BY col1")) { - for (int i = 0; i < count + tail; i++) { - assertTrue(rs.next()); - assertEquals(rs.getInt(1), i); - assertEquals(rs.getShort(2), (short) (i + arrayLen + childLen - 4)); - } - assertFalse(rs.next()); - } - - int row = count - 2; - try (Statement stmt2 = conn.createStatement(); - ResultSet rs2 = stmt2.executeQuery( - "SELECT unnest(ucol2) FROM (SELECT unnest(col2) as ucol2 FROM tab1 WHERE col1 = " + row + ")")) { - for (int j = 0; j < arrayLen; j++) { - for (int k = 0; k < childLen; k++) { - assertTrue(rs2.next()); - assertEquals(rs2.getShort(1), (short) (row + j + k)); - } - } - assertFalse(rs2.next()); - } - } - } - public static void test_appender_array_integer() throws Exception { int count = 1 << 11; // auto flush int tail = 16; // flushed on close @@ -378,144 +237,6 @@ public static void test_appender_array_integer() throws Exception { } } - public static void test_appender_nested_array_basic_integer() throws Exception { - try (DuckDBConnection conn = DriverManager.getConnection(JDBC_URL).unwrap(DuckDBConnection.class); - Statement stmt = conn.createStatement()) { - - stmt.execute("CREATE TABLE tab1(col1 INT, col2 INTEGER[2][3])"); - - try (DuckDBAppender appender = conn.createAppender("tab1")) { - appender.beginRow().append(41).append(new int[][] {{42, 43}, {44, 45}, {46, 47}}).endRow(); - appender.beginRow().append(48).append(new int[][] {{49, 50}, null, {53, 54}}).endRow(); - appender.beginRow() - .append(55) - .append(new int[][] {{56, 57}, {58, 59}, {60, 61}}, - new boolean[][] {{false, true}, {false, false}, {true, false}}) - .endRow(); - } - - try (ResultSet rs = stmt.executeQuery("SELECT col1, unnest(col2) FROM tab1 WHERE col1 = 41")) { - assertTrue(rs.next()); - assertEquals(rs.getInt(1), 41); - Object[] array1 = (Object[]) rs.getArray(2).getArray(); - assertEquals(array1.length, 2); - assertEquals(array1[0], 42); - assertEquals(array1[1], 43); - - assertTrue(rs.next()); - assertEquals(rs.getInt(1), 41); - Object[] array2 = (Object[]) rs.getArray(2).getArray(); - assertEquals(array2.length, 2); - assertEquals(array2[0], 44); - assertEquals(array2[1], 45); - - assertTrue(rs.next()); - assertEquals(rs.getInt(1), 41); - Object[] array3 = (Object[]) rs.getArray(2).getArray(); - assertEquals(array3.length, 2); - assertEquals(array3[0], 46); - assertEquals(array3[1], 47); - - assertFalse(rs.next()); - } - - try (ResultSet rs = stmt.executeQuery("SELECT col1, unnest(col2) FROM tab1 WHERE col1 = 48")) { - assertTrue(rs.next()); - assertEquals(rs.getInt(1), 48); - Object[] array1 = (Object[]) rs.getArray(2).getArray(); - assertEquals(array1.length, 2); - assertEquals(array1[0], 49); - assertEquals(array1[1], 50); - - assertTrue(rs.next()); - assertEquals(rs.getInt(1), 48); - assertNull(rs.getObject(2)); - - assertTrue(rs.next()); - assertEquals(rs.getInt(1), 48); - Object[] array3 = (Object[]) rs.getArray(2).getArray(); - assertEquals(array3.length, 2); - assertEquals(array3[0], 53); - assertEquals(array3[1], 54); - - assertFalse(rs.next()); - } - - try (ResultSet rs = stmt.executeQuery("SELECT col1, unnest(col2) FROM tab1 WHERE col1 = 55")) { - assertTrue(rs.next()); - assertEquals(rs.getInt(1), 55); - Object[] array1 = (Object[]) rs.getArray(2).getArray(); - assertEquals(array1.length, 2); - assertEquals(array1[0], 56); - assertNull(array1[1]); - - assertTrue(rs.next()); - assertEquals(rs.getInt(1), 55); - Object[] array2 = (Object[]) rs.getArray(2).getArray(); - assertEquals(array2.length, 2); - assertEquals(array2[0], 58); - assertEquals(array2[1], 59); - - assertTrue(rs.next()); - assertEquals(rs.getInt(1), 55); - Object[] array3 = (Object[]) rs.getArray(2).getArray(); - assertEquals(array3.length, 2); - assertNull(array3[0]); - assertEquals(array3[1], 61); - - assertFalse(rs.next()); - } - } - } - - public static void test_appender_nested_array_integer() throws Exception { - try (DuckDBConnection conn = DriverManager.getConnection(JDBC_URL).unwrap(DuckDBConnection.class); - Statement stmt = conn.createStatement()) { - - int count = 1 << 12; // auto flush twice - int tail = 17; // flushed on close - int arrayLen = (1 << 6) + 5; // increase this for stress tests - int childLen = (1 << 6) + 7; - - stmt.execute("CREATE TABLE tab1(col1 INT, col2 INTEGER[" + childLen + "][" + arrayLen + "])"); - - try (DuckDBAppender appender = conn.createAppender("tab1")) { - for (int i = 0; i < count + tail; i++) { - int[][] arr = new int[arrayLen][childLen]; - for (int j = 0; j < arrayLen; j++) { - for (int k = 0; k < childLen; k++) { - arr[j][k] = (i + 1) * (j + 1) * (k + 1); - } - } - appender.beginRow().append(i).append(arr).endRow(); - } - } - - try (ResultSet rs = stmt.executeQuery("SELECT col1, col2[" + (arrayLen - 1) + "][" + (childLen - 1) + - "] FROM tab1 ORDER BY col1")) { - for (int i = 0; i < count + tail; i++) { - assertTrue(rs.next()); - assertEquals(rs.getInt(1), i); - assertEquals(rs.getInt(2), (i + 1) * (arrayLen - 1) * (childLen - 1)); - } - assertFalse(rs.next()); - } - - int row = count - 2; - try (Statement stmt2 = conn.createStatement(); - ResultSet rs2 = stmt2.executeQuery( - "SELECT unnest(ucol2) FROM (SELECT unnest(col2) as ucol2 FROM tab1 WHERE col1 = " + row + ")")) { - for (int j = 0; j < arrayLen; j++) { - for (int k = 0; k < childLen; k++) { - assertTrue(rs2.next()); - assertEquals(rs2.getInt(1), (row + 1) * (j + 1) * (k + 1)); - } - } - assertFalse(rs2.next()); - } - } - } - public static void test_appender_array_bigint() throws Exception { int count = 1 << 11; // auto flush int tail = 16; // flushed on close @@ -561,54 +282,6 @@ public static void test_appender_array_bigint() throws Exception { } } - public static void test_appender_nested_array_bigint() throws Exception { - try (DuckDBConnection conn = DriverManager.getConnection(JDBC_URL).unwrap(DuckDBConnection.class); - Statement stmt = conn.createStatement()) { - - int count = 1 << 11; // auto flush - int tail = 17; // flushed on close - int arrayLen = (1 << 6) + 5; // increase this for stress tests - int childLen = (1 << 6) + 7; - - stmt.execute("CREATE TABLE tab1(col1 INT, col2 BIGINT[" + childLen + "][" + arrayLen + "])"); - - try (DuckDBAppender appender = conn.createAppender("tab1")) { - for (int i = 0; i < count + tail; i++) { - long[][] arr = new long[arrayLen][childLen]; - for (int j = 0; j < arrayLen; j++) { - for (int k = 0; k < childLen; k++) { - arr[j][k] = (long) (i + 1) * (j + 1) * (k + 1); - } - } - appender.beginRow().append(i).append(arr).endRow(); - } - } - - try (ResultSet rs = stmt.executeQuery("SELECT col1, col2[" + (arrayLen - 1) + "][" + (childLen - 1) + - "] FROM tab1 ORDER BY col1")) { - for (int i = 0; i < count + tail; i++) { - assertTrue(rs.next()); - assertEquals(rs.getInt(1), i); - assertEquals(rs.getLong(2), (long) ((i + 1) * (arrayLen - 1) * (childLen - 1))); - } - assertFalse(rs.next()); - } - - int row = count - 2; - try (Statement stmt2 = conn.createStatement(); - ResultSet rs2 = stmt2.executeQuery( - "SELECT unnest(ucol2) FROM (SELECT unnest(col2) as ucol2 FROM tab1 WHERE col1 = " + row + ")")) { - for (int j = 0; j < arrayLen; j++) { - for (int k = 0; k < childLen; k++) { - assertTrue(rs2.next()); - assertEquals(rs2.getLong(1), (long) (row + 1) * (j + 1) * (k + 1)); - } - } - assertFalse(rs2.next()); - } - } - } - public static void test_appender_array_float() throws Exception { int count = 1 << 11; // auto flush int tail = 16; // flushed on close @@ -654,54 +327,6 @@ public static void test_appender_array_float() throws Exception { } } - public static void test_appender_nested_array_float() throws Exception { - try (DuckDBConnection conn = DriverManager.getConnection(JDBC_URL).unwrap(DuckDBConnection.class); - Statement stmt = conn.createStatement()) { - - int count = 1 << 12; // auto flush twice - int tail = 17; // flushed on close - int arrayLen = (1 << 6) + 5; // increase this for stress tests - int childLen = (1 << 6) + 7; - - stmt.execute("CREATE TABLE tab1(col1 INT, col2 FLOAT[" + childLen + "][" + arrayLen + "])"); - - try (DuckDBAppender appender = conn.createAppender("tab1")) { - for (int i = 0; i < count + tail; i++) { - float[][] arr = new float[arrayLen][childLen]; - for (int j = 0; j < arrayLen; j++) { - for (int k = 0; k < childLen; k++) { - arr[j][k] = (i + 1) * (j + 1) * (k + 1) + 0.001f; - } - } - appender.beginRow().append(i).append(arr).endRow(); - } - } - - try (ResultSet rs = stmt.executeQuery("SELECT col1, col2[" + (arrayLen - 1) + "][" + (childLen - 1) + - "] FROM tab1 ORDER BY col1")) { - for (int i = 0; i < count + tail; i++) { - assertTrue(rs.next()); - assertEquals(rs.getInt(1), i); - assertEquals(rs.getFloat(2), (i + 1) * (arrayLen - 1) * (childLen - 1) + 0.001f); - } - assertFalse(rs.next()); - } - - int row = count - 2; - try (Statement stmt2 = conn.createStatement(); - ResultSet rs2 = stmt2.executeQuery( - "SELECT unnest(ucol2) FROM (SELECT unnest(col2) as ucol2 FROM tab1 WHERE col1 = " + row + ")")) { - for (int j = 0; j < arrayLen; j++) { - for (int k = 0; k < childLen; k++) { - assertTrue(rs2.next()); - assertEquals(rs2.getFloat(1), (row + 1) * (j + 1) * (k + 1) + 0.001f); - } - } - assertFalse(rs2.next()); - } - } - } - public static void test_appender_array_double() throws Exception { int count = 1 << 11; // auto flush int tail = 16; // flushed on close @@ -747,54 +372,6 @@ public static void test_appender_array_double() throws Exception { } } - public static void test_appender_nested_array_double() throws Exception { - try (DuckDBConnection conn = DriverManager.getConnection(JDBC_URL).unwrap(DuckDBConnection.class); - Statement stmt = conn.createStatement()) { - - int count = 1 << 11; // auto flush - int tail = 17; // flushed on close - int arrayLen = (1 << 6) + 5; // increase this for stress tests - int childLen = (1 << 6) + 7; - - stmt.execute("CREATE TABLE tab1(col1 INT, col2 DOUBLE[" + childLen + "][" + arrayLen + "])"); - - try (DuckDBAppender appender = conn.createAppender("tab1")) { - for (int i = 0; i < count + tail; i++) { - double[][] arr = new double[arrayLen][childLen]; - for (int j = 0; j < arrayLen; j++) { - for (int k = 0; k < childLen; k++) { - arr[j][k] = (i + 1) * (j + 1) * (k + 1) + 0.001; - } - } - appender.beginRow().append(i).append(arr).endRow(); - } - } - - try (ResultSet rs = stmt.executeQuery("SELECT col1, col2[" + (arrayLen - 1) + "][" + (childLen - 1) + - "] FROM tab1 ORDER BY col1")) { - for (int i = 0; i < count + tail; i++) { - assertTrue(rs.next()); - assertEquals(rs.getInt(1), i); - assertEquals(rs.getDouble(2), (i + 1) * (arrayLen - 1) * (childLen - 1) + 0.001); - } - assertFalse(rs.next()); - } - - int row = count - 2; - try (Statement stmt2 = conn.createStatement(); - ResultSet rs2 = stmt2.executeQuery( - "SELECT unnest(ucol2) FROM (SELECT unnest(col2) as ucol2 FROM tab1 WHERE col1 = " + row + ")")) { - for (int j = 0; j < arrayLen; j++) { - for (int k = 0; k < childLen; k++) { - assertTrue(rs2.next()); - assertEquals(rs2.getDouble(1), (row + 1) * (j + 1) * (k + 1) + 0.001); - } - } - assertFalse(rs2.next()); - } - } - } - public static void test_appender_list_basic() throws Exception { try (DuckDBConnection conn = DriverManager.getConnection(JDBC_URL).unwrap(DuckDBConnection.class); Statement stmt = conn.createStatement()) { @@ -853,4 +430,1074 @@ public static void test_appender_list_basic() throws Exception { } } } + + public static void test_appender_array_basic_varchar() throws Exception { + try (DuckDBConnection conn = DriverManager.getConnection(JDBC_URL).unwrap(DuckDBConnection.class); + Statement stmt = conn.createStatement()) { + stmt.execute("CREATE TABLE tab1(col1 INT, col2 VARCHAR[2])"); + try (DuckDBAppender appender = conn.createAppender("tab1")) { + appender.beginRow() + .append(42) + .append(asList("foo", "barbazboo0123456789")) + .endRow() + .beginRow() + .append(43) + .append((List) null) + .endRow() + .beginRow() + .append(44) + .append(asList(null, "bar")) + .endRow() + .flush(); + } + try (ResultSet rs = stmt.executeQuery("SELECT unnest(col2) from tab1 WHERE col1 = 42")) { + assertTrue(rs.next()); + assertEquals(rs.getString(1), "foo"); + assertTrue(rs.next()); + assertEquals(rs.getString(1), "barbazboo0123456789"); + assertFalse(rs.next()); + } + try (ResultSet rs = stmt.executeQuery("SELECT col2 from tab1 WHERE col1 = 43")) { + assertTrue(rs.next()); + assertNull(rs.getObject(1)); + assertTrue(rs.wasNull()); + assertFalse(rs.next()); + } + try (ResultSet rs = stmt.executeQuery("SELECT unnest(col2) from tab1 WHERE col1 = 44")) { + assertTrue(rs.next()); + assertNull(rs.getObject(1)); + assertTrue(rs.wasNull()); + assertTrue(rs.next()); + assertEquals(rs.getString(1), "bar"); + assertFalse(rs.next()); + } + } + } + + public static void test_appender_list_basic_varchar() throws Exception { + try (DuckDBConnection conn = DriverManager.getConnection(JDBC_URL).unwrap(DuckDBConnection.class); + Statement stmt = conn.createStatement()) { + stmt.execute("CREATE TABLE tab1(col1 INT, col2 VARCHAR[])"); + try (DuckDBAppender appender = conn.createAppender("tab1")) { + appender.beginRow() + .append(42) + .append(asList("foo", "barbazboo0123456789", "bar")) + .endRow() + .beginRow() + .append(43) + .append((List) null) + .endRow() + .beginRow() + .append(44) + .append(asList(null, "boo")) + .endRow() + .flush(); + } + + try (ResultSet rs = stmt.executeQuery("SELECT unnest(col2) from tab1 WHERE col1 = 42")) { + assertTrue(rs.next()); + assertEquals(rs.getString(1), "foo"); + assertTrue(rs.next()); + assertEquals(rs.getString(1), "barbazboo0123456789"); + assertTrue(rs.next()); + assertEquals(rs.getString(1), "bar"); + assertFalse(rs.next()); + } + try (ResultSet rs = stmt.executeQuery("SELECT col2 from tab1 WHERE col1 = 43")) { + assertTrue(rs.next()); + assertNull(rs.getObject(1)); + assertTrue(rs.wasNull()); + assertFalse(rs.next()); + } + try (ResultSet rs = stmt.executeQuery("SELECT unnest(col2) from tab1 WHERE col1 = 44")) { + assertTrue(rs.next()); + assertNull(rs.getObject(1)); + assertTrue(rs.wasNull()); + assertTrue(rs.next()); + assertEquals(rs.getString(1), "boo"); + assertFalse(rs.next()); + } + } + } + + public static void test_appender_array_varchar() throws Exception { + int count = 1 << 11; // auto flush + int tail = 16; // flushed on close + int arrayLen = (1 << 6) + 7; // increase this for stress tests + + for (String ddl : new String[] {"CREATE TABLE tab1(col1 INT, col2 VARCHAR[" + arrayLen + "])", + "CREATE TABLE tab1(col1 INT, col2 VARCHAR[])"}) { + for (boolean inlined : new boolean[] {true, false}) { + try (DuckDBConnection conn = DriverManager.getConnection(JDBC_URL).unwrap(DuckDBConnection.class); + Statement stmt = conn.createStatement()) { + + stmt.execute(ddl); + + try (DuckDBAppender appender = conn.createAppender("tab1")) { + appender.setWriteInlinedStrings(inlined); + for (int i = 0; i < count + tail; i++) { + List list = new ArrayList<>(); + for (int j = 0; j < arrayLen; j++) { + list.add(String.valueOf(i + j)); + } + appender.beginRow().append(i).append(list).endRow(); + } + } + + try (ResultSet rs = + stmt.executeQuery("SELECT col1, col2[" + (arrayLen - 1) + "] FROM tab1 ORDER BY col1")) { + for (int i = 0; i < count + tail; i++) { + assertTrue(rs.next()); + assertEquals(rs.getInt(1), i); + assertEquals(rs.getString(2), String.valueOf(i + arrayLen - 2)); + } + assertFalse(rs.next()); + } + + int row = count - 2; + try (Statement stmt2 = conn.createStatement(); + ResultSet rs2 = stmt2.executeQuery("SELECT unnest(col2) FROM tab1 WHERE col1 = " + row)) { + for (int j = 0; j < arrayLen; j++) { + assertTrue(rs2.next()); + assertEquals(rs2.getString(1), String.valueOf(row + j)); + } + assertFalse(rs2.next()); + } + } + } + } + } + + public static void test_appender_list_basic_uuid() throws Exception { + UUID uid1 = UUID.fromString("6b9ec4d3-a5e1-45e5-b696-1e599a0e2058"); + UUID uid2 = UUID.fromString("7bf979fc-0c09-4864-bf40-233e7b1fdea1"); + UUID uid3 = UUID.fromString("2f2ac3eb-9105-4864-a2e5-e6d9a7311ba6"); + UUID uid4 = UUID.fromString("3b56451f-691a-4a8d-90ca-1e03479fc38a"); + try (DuckDBConnection conn = DriverManager.getConnection(JDBC_URL).unwrap(DuckDBConnection.class); + Statement stmt = conn.createStatement()) { + stmt.execute("CREATE TABLE tab1(col1 INT, col2 UUID[])"); + try (DuckDBAppender appender = conn.createAppender("tab1")) { + appender.beginRow() + .append(42) + .append(asList(uid1, uid2, uid3)) + .endRow() + .beginRow() + .append(43) + .append((List) null) + .endRow() + .beginRow() + .append(44) + .append(asList(null, uid4)) + .endRow() + .flush(); + } + + try (ResultSet rs = stmt.executeQuery("SELECT unnest(col2) from tab1 WHERE col1 = 42")) { + assertTrue(rs.next()); + assertEquals(rs.getObject(1), uid1); + assertTrue(rs.next()); + assertEquals(rs.getObject(1), uid2); + assertTrue(rs.next()); + assertEquals(rs.getObject(1), uid3); + assertFalse(rs.next()); + } + try (ResultSet rs = stmt.executeQuery("SELECT col2 from tab1 WHERE col1 = 43")) { + assertTrue(rs.next()); + assertNull(rs.getObject(1)); + assertTrue(rs.wasNull()); + assertFalse(rs.next()); + } + try (ResultSet rs = stmt.executeQuery("SELECT unnest(col2) from tab1 WHERE col1 = 44")) { + assertTrue(rs.next()); + assertNull(rs.getObject(1)); + assertTrue(rs.wasNull()); + assertTrue(rs.next()); + assertEquals(rs.getObject(1), uid4); + assertFalse(rs.next()); + } + } + } + + public static void test_appender_list_basic_date() throws Exception { + LocalDate ld1 = LocalDate.of(2020, 12, 1); + LocalDate ld2 = LocalDate.of(2020, 12, 2); + LocalDate ld3 = LocalDate.of(2020, 12, 3); + LocalDate ld4 = LocalDate.of(2020, 12, 4); + try (DuckDBConnection conn = DriverManager.getConnection(JDBC_URL).unwrap(DuckDBConnection.class); + Statement stmt = conn.createStatement()) { + stmt.execute("CREATE TABLE tab1(col1 INT, col2 DATE[])"); + try (DuckDBAppender appender = conn.createAppender("tab1")) { + appender.beginRow() + .append(42) + .append(asList(ld1, ld2, ld3)) + .endRow() + .beginRow() + .append(43) + .append((List) null) + .endRow() + .beginRow() + .append(44) + .append(asList(null, ld4)) + .endRow() + .flush(); + } + + try (ResultSet rs = stmt.executeQuery("SELECT unnest(col2) from tab1 WHERE col1 = 42")) { + assertTrue(rs.next()); + assertEquals(rs.getObject(1), ld1); + assertTrue(rs.next()); + assertEquals(rs.getObject(1), ld2); + assertTrue(rs.next()); + assertEquals(rs.getObject(1), ld3); + assertFalse(rs.next()); + } + try (ResultSet rs = stmt.executeQuery("SELECT col2 from tab1 WHERE col1 = 43")) { + assertTrue(rs.next()); + assertNull(rs.getObject(1)); + assertTrue(rs.wasNull()); + assertFalse(rs.next()); + } + try (ResultSet rs = stmt.executeQuery("SELECT unnest(col2) from tab1 WHERE col1 = 44")) { + assertTrue(rs.next()); + assertNull(rs.getObject(1)); + assertTrue(rs.wasNull()); + assertTrue(rs.next()); + assertEquals(rs.getObject(1), ld4); + assertFalse(rs.next()); + } + } + } + + public static void test_appender_list_basic_time() throws Exception { + LocalTime lt1 = LocalTime.of(23, 59, 1); + LocalTime lt2 = LocalTime.of(23, 59, 2); + LocalTime lt3 = LocalTime.of(23, 59, 3); + LocalTime lt4 = LocalTime.of(23, 59, 4); + try (DuckDBConnection conn = DriverManager.getConnection(JDBC_URL).unwrap(DuckDBConnection.class); + Statement stmt = conn.createStatement()) { + stmt.execute("CREATE TABLE tab1(col1 INT, col2 TIME[])"); + try (DuckDBAppender appender = conn.createAppender("tab1")) { + appender.beginRow() + .append(42) + .append(asList(lt1, lt2, lt3)) + .endRow() + .beginRow() + .append(43) + .append((List) null) + .endRow() + .beginRow() + .append(44) + .append(asList(null, lt4)) + .endRow() + .flush(); + } + + try (ResultSet rs = stmt.executeQuery("SELECT unnest(col2) from tab1 WHERE col1 = 42")) { + assertTrue(rs.next()); + assertEquals(rs.getObject(1), lt1); + assertTrue(rs.next()); + assertEquals(rs.getObject(1), lt2); + assertTrue(rs.next()); + assertEquals(rs.getObject(1), lt3); + assertFalse(rs.next()); + } + try (ResultSet rs = stmt.executeQuery("SELECT col2 from tab1 WHERE col1 = 43")) { + assertTrue(rs.next()); + assertNull(rs.getObject(1)); + assertTrue(rs.wasNull()); + assertFalse(rs.next()); + } + try (ResultSet rs = stmt.executeQuery("SELECT unnest(col2) from tab1 WHERE col1 = 44")) { + assertTrue(rs.next()); + assertNull(rs.getObject(1)); + assertTrue(rs.wasNull()); + assertTrue(rs.next()); + assertEquals(rs.getObject(1), lt4); + assertFalse(rs.next()); + } + } + } + + public static void test_appender_list_basic_tztime() throws Exception { + TimeZone defaultTimeZone = TimeZone.getDefault(); + TimeZone activeTimeZone = TimeZone.getTimeZone("Europe/Sofia"); + TimeZone.setDefault(activeTimeZone); + try (DuckDBConnection conn = DriverManager.getConnection(JDBC_URL).unwrap(DuckDBConnection.class); + Statement stmt = conn.createStatement()) { + + OffsetTime ot1 = LocalTime.of(23, 59, 1).atOffset(ZoneOffset.ofHours(1)); + OffsetTime ot2 = LocalTime.of(23, 59, 2).atOffset(ZoneOffset.ofHours(2)); + OffsetTime ot3 = LocalTime.of(23, 59, 3).atOffset(ZoneOffset.ofHours(3)); + OffsetTime ot4 = LocalTime.of(23, 59, 4).atOffset(ZoneOffset.ofHours(4)); + + stmt.execute("CREATE TABLE tab1(col1 INT, col2 TIME WITH TIME ZONE[])"); + try (DuckDBAppender appender = conn.createAppender("tab1")) { + appender.beginRow() + .append(42) + .append(asList(ot1, ot2, ot3)) + .endRow() + .beginRow() + .append(43) + .append((List) null) + .endRow() + .beginRow() + .append(44) + .append(asList(null, ot4)) + .endRow() + .flush(); + } + + try (ResultSet rs = stmt.executeQuery("SELECT unnest(col2) from tab1 WHERE col1 = 42")) { + assertTrue(rs.next()); + assertEquals(rs.getObject(1), ot1); + assertTrue(rs.next()); + assertEquals(rs.getObject(1), ot2); + assertTrue(rs.next()); + assertEquals(rs.getObject(1), ot3); + assertFalse(rs.next()); + } + try (ResultSet rs = stmt.executeQuery("SELECT col2 from tab1 WHERE col1 = 43")) { + assertTrue(rs.next()); + assertNull(rs.getObject(1)); + assertTrue(rs.wasNull()); + assertFalse(rs.next()); + } + try (ResultSet rs = stmt.executeQuery("SELECT unnest(col2) from tab1 WHERE col1 = 44")) { + assertTrue(rs.next()); + assertNull(rs.getObject(1)); + assertTrue(rs.wasNull()); + assertTrue(rs.next()); + assertEquals(rs.getObject(1), ot4); + assertFalse(rs.next()); + } + } finally { + TimeZone.setDefault(defaultTimeZone); + } + } + + public static void test_appender_list_basic_local_date_time() throws Exception { + LocalDateTime ldt1 = LocalDateTime.of(2020, 12, 31, 23, 59, 1); + LocalDateTime ldt2 = LocalDateTime.of(2020, 12, 31, 23, 59, 2); + LocalDateTime ldt3 = LocalDateTime.of(2020, 12, 31, 23, 59, 3); + LocalDateTime ldt4 = LocalDateTime.of(2020, 12, 31, 23, 59, 4); + + for (String ddl : new String[] {"CREATE TABLE tab1(col1 INT, col2 TIMESTAMP[])", + "CREATE TABLE tab1(col1 INT, col2 TIMESTAMP_S[])", + "CREATE TABLE tab1(col1 INT, col2 TIMESTAMP_MS[])", + "CREATE TABLE tab1(col1 INT, col2 TIMESTAMP_NS[])"}) { + try (DuckDBConnection conn = DriverManager.getConnection(JDBC_URL).unwrap(DuckDBConnection.class); + Statement stmt = conn.createStatement()) { + stmt.execute(ddl); + try (DuckDBAppender appender = conn.createAppender("tab1")) { + appender.beginRow() + .append(42) + .append(asList(ldt1, ldt2, ldt3)) + .endRow() + .beginRow() + .append(43) + .append((List) null) + .endRow() + .beginRow() + .append(44) + .append(asList(null, ldt4)) + .endRow() + .flush(); + } + + try (ResultSet rs = stmt.executeQuery("SELECT unnest(col2) from tab1 WHERE col1 = 42")) { + assertTrue(rs.next()); + assertEquals(rs.getObject(1, LocalDateTime.class), ldt1); + assertTrue(rs.next()); + assertEquals(rs.getObject(1, LocalDateTime.class), ldt2); + assertTrue(rs.next()); + assertEquals(rs.getObject(1, LocalDateTime.class), ldt3); + assertFalse(rs.next()); + } + try (ResultSet rs = stmt.executeQuery("SELECT col2 from tab1 WHERE col1 = 43")) { + assertTrue(rs.next()); + assertNull(rs.getObject(1, LocalDateTime.class)); + assertTrue(rs.wasNull()); + assertFalse(rs.next()); + } + try (ResultSet rs = stmt.executeQuery("SELECT unnest(col2) from tab1 WHERE col1 = 44")) { + assertTrue(rs.next()); + assertNull(rs.getObject(1, LocalDateTime.class)); + assertTrue(rs.wasNull()); + assertTrue(rs.next()); + assertEquals(rs.getObject(1, LocalDateTime.class), ldt4); + assertFalse(rs.next()); + } + } + } + } + + public static void test_appender_list_basic_local_date_time_date() throws Exception { + TimeZone defaultTimeZone = TimeZone.getDefault(); + TimeZone activeTimeZone = TimeZone.getTimeZone("Europe/Sofia"); + TimeZone.setDefault(activeTimeZone); + try { + java.util.Date dt1 = Date.from(LocalDateTime.of(2020, 12, 31, 23, 59, 1).toInstant(ZoneOffset.UTC)); + java.util.Date dt2 = Date.from(LocalDateTime.of(2020, 12, 31, 23, 59, 2).toInstant(ZoneOffset.UTC)); + java.util.Date dt3 = Date.from(LocalDateTime.of(2020, 12, 31, 23, 59, 3).toInstant(ZoneOffset.UTC)); + java.util.Date dt4 = Date.from(LocalDateTime.of(2020, 12, 31, 23, 59, 4).toInstant(ZoneOffset.UTC)); + + for (String ddl : new String[] {"CREATE TABLE tab1(col1 INT, col2 TIMESTAMP[])", + "CREATE TABLE tab1(col1 INT, col2 TIMESTAMP_S[])", + "CREATE TABLE tab1(col1 INT, col2 TIMESTAMP_MS[])", + "CREATE TABLE tab1(col1 INT, col2 TIMESTAMP_NS[])"}) { + try (DuckDBConnection conn = DriverManager.getConnection(JDBC_URL).unwrap(DuckDBConnection.class); + Statement stmt = conn.createStatement()) { + stmt.execute(ddl); + try (DuckDBAppender appender = conn.createAppender("tab1")) { + appender.beginRow() + .append(42) + .append(asList(dt1, dt2, dt3)) + .endRow() + .beginRow() + .append(43) + .append((List) null) + .endRow() + .beginRow() + .append(44) + .append(asList(null, dt4)) + .endRow() + .flush(); + } + + try (ResultSet rs = stmt.executeQuery("SELECT unnest(col2) from tab1 WHERE col1 = 42")) { + assertTrue(rs.next()); + assertEquals(Date.from(rs.getObject(1, LocalDateTime.class).toInstant(ZoneOffset.UTC)), dt1); + assertTrue(rs.next()); + assertEquals(Date.from(rs.getObject(1, LocalDateTime.class).toInstant(ZoneOffset.UTC)), dt2); + assertTrue(rs.next()); + assertEquals(Date.from(rs.getObject(1, LocalDateTime.class).toInstant(ZoneOffset.UTC)), dt3); + assertFalse(rs.next()); + } + try (ResultSet rs = stmt.executeQuery("SELECT col2 from tab1 WHERE col1 = 43")) { + assertTrue(rs.next()); + assertNull(rs.getObject(1, LocalDateTime.class)); + assertTrue(rs.wasNull()); + assertFalse(rs.next()); + } + try (ResultSet rs = stmt.executeQuery("SELECT unnest(col2) from tab1 WHERE col1 = 44")) { + assertTrue(rs.next()); + assertNull(rs.getObject(1, LocalDateTime.class)); + assertTrue(rs.wasNull()); + assertTrue(rs.next()); + assertEquals(Date.from(rs.getObject(1, LocalDateTime.class).toInstant(ZoneOffset.UTC)), dt4); + assertFalse(rs.next()); + } + } + } + } finally { + TimeZone.setDefault(defaultTimeZone); + } + } + + public static void test_appender_list_basic_offset_date_time() throws Exception { + try (DuckDBConnection conn = DriverManager.getConnection(JDBC_URL).unwrap(DuckDBConnection.class); + Statement stmt = conn.createStatement()) { + + OffsetDateTime odt1 = LocalDateTime.of(2020, 12, 31, 23, 59, 1).atOffset(ZoneOffset.UTC); + OffsetDateTime odt2 = LocalDateTime.of(2020, 12, 31, 23, 59, 2).atOffset(ZoneOffset.UTC); + OffsetDateTime odt3 = LocalDateTime.of(2020, 12, 31, 23, 59, 3).atOffset(ZoneOffset.UTC); + OffsetDateTime odt4 = LocalDateTime.of(2020, 12, 31, 23, 59, 4).atOffset(ZoneOffset.UTC); + + stmt.execute("CREATE TABLE tab1(col1 INT, col2 TIMESTAMP WITH TIME ZONE[])"); + try (DuckDBAppender appender = conn.createAppender("tab1")) { + appender.beginRow() + .append(42) + .append(asList(odt1, odt2, odt3)) + .endRow() + .beginRow() + .append(43) + .append((List) null) + .endRow() + .beginRow() + .append(44) + .append(asList(null, odt4)) + .endRow() + .flush(); + } + + try (ResultSet rs = stmt.executeQuery("SELECT unnest(col2) from tab1 WHERE col1 = 42")) { + assertTrue(rs.next()); + assertEquals(rs.getObject(1, LocalDateTime.class).getSecond(), odt1.getSecond()); + assertTrue(rs.next()); + assertEquals(rs.getObject(1, LocalDateTime.class).getSecond(), odt2.getSecond()); + assertTrue(rs.next()); + assertEquals(rs.getObject(1, LocalDateTime.class).getSecond(), odt3.getSecond()); + assertFalse(rs.next()); + } + try (ResultSet rs = stmt.executeQuery("SELECT col2 from tab1 WHERE col1 = 43")) { + assertTrue(rs.next()); + assertNull(rs.getObject(1)); + assertTrue(rs.wasNull()); + assertFalse(rs.next()); + } + try (ResultSet rs = stmt.executeQuery("SELECT unnest(col2) from tab1 WHERE col1 = 44")) { + assertTrue(rs.next()); + assertNull(rs.getObject(1)); + assertTrue(rs.wasNull()); + assertTrue(rs.next()); + assertEquals(rs.getObject(1, LocalDateTime.class).getSecond(), odt4.getSecond()); + assertFalse(rs.next()); + } + } + } + + private static void assertFetchedArrayEquals(Array dba, boolean[] arr) throws Exception { + Object[] objArr = (Object[]) dba.getArray(); + assertEquals(objArr.length, arr.length); + for (int i = 0; i < objArr.length; i++) { + assertEquals(objArr[i], arr[i]); + } + } + + public static void test_appender_list_basic_array_bool() throws Exception { + boolean[] arr1 = new boolean[] {true, false, true}; + boolean[] arr2 = new boolean[] {false, true, false}; + boolean[] arr3 = new boolean[] {false, false, true}; + boolean[] arr4 = new boolean[] {true, true, false}; + try (DuckDBConnection conn = DriverManager.getConnection(JDBC_URL).unwrap(DuckDBConnection.class); + Statement stmt = conn.createStatement()) { + stmt.execute("CREATE TABLE tab1(col1 INT, col2 BOOL[3][])"); + try (DuckDBAppender appender = conn.createAppender("tab1")) { + appender.beginRow() + .append(42) + .append(asList(arr1, arr2, arr3)) + .endRow() + .beginRow() + .append(43) + .append((List) null) + .endRow() + .beginRow() + .append(44) + .append(asList(null, arr4)) + .endRow() + .flush(); + } + + try (ResultSet rs = stmt.executeQuery("SELECT unnest(col2) from tab1 WHERE col1 = 42")) { + assertTrue(rs.next()); + assertFetchedArrayEquals(rs.getArray(1), arr1); + assertTrue(rs.next()); + assertFetchedArrayEquals(rs.getArray(1), arr2); + assertTrue(rs.next()); + assertFetchedArrayEquals(rs.getArray(1), arr3); + assertFalse(rs.next()); + } + try (ResultSet rs = stmt.executeQuery("SELECT col2 from tab1 WHERE col1 = 43")) { + assertTrue(rs.next()); + assertNull(rs.getObject(1)); + assertTrue(rs.wasNull()); + assertFalse(rs.next()); + } + try (ResultSet rs = stmt.executeQuery("SELECT unnest(col2) from tab1 WHERE col1 = 44")) { + assertTrue(rs.next()); + assertNull(rs.getObject(1)); + assertTrue(rs.wasNull()); + assertTrue(rs.next()); + assertFetchedArrayEquals(rs.getArray(1), arr4); + assertFalse(rs.next()); + } + } + } + + private static void assertFetchedArrayEquals(Array dba, byte[] arr) throws Exception { + Object[] objArr = (Object[]) dba.getArray(); + assertEquals(objArr.length, arr.length); + for (int i = 0; i < objArr.length; i++) { + assertEquals(objArr[i], arr[i]); + } + } + + public static void test_appender_list_basic_array_tinyint() throws Exception { + byte[] arr1 = new byte[] {41, 42, 43}; + byte[] arr2 = new byte[] {45, 46, 47}; + byte[] arr3 = new byte[] {48, 49, 50}; + byte[] arr4 = new byte[] {51, 52, 53}; + try (DuckDBConnection conn = DriverManager.getConnection(JDBC_URL).unwrap(DuckDBConnection.class); + Statement stmt = conn.createStatement()) { + stmt.execute("CREATE TABLE tab1(col1 INT, col2 TINYINT[3][])"); + try (DuckDBAppender appender = conn.createAppender("tab1")) { + appender.beginRow() + .append(42) + .append(asList(arr1, arr2, arr3)) + .endRow() + .beginRow() + .append(43) + .append((List) null) + .endRow() + .beginRow() + .append(44) + .append(asList(null, arr4)) + .endRow() + .flush(); + } + + try (ResultSet rs = stmt.executeQuery("SELECT unnest(col2) from tab1 WHERE col1 = 42")) { + assertTrue(rs.next()); + assertFetchedArrayEquals(rs.getArray(1), arr1); + assertTrue(rs.next()); + assertFetchedArrayEquals(rs.getArray(1), arr2); + assertTrue(rs.next()); + assertFetchedArrayEquals(rs.getArray(1), arr3); + assertFalse(rs.next()); + } + try (ResultSet rs = stmt.executeQuery("SELECT col2 from tab1 WHERE col1 = 43")) { + assertTrue(rs.next()); + assertNull(rs.getObject(1)); + assertTrue(rs.wasNull()); + assertFalse(rs.next()); + } + try (ResultSet rs = stmt.executeQuery("SELECT unnest(col2) from tab1 WHERE col1 = 44")) { + assertTrue(rs.next()); + assertNull(rs.getObject(1)); + assertTrue(rs.wasNull()); + assertTrue(rs.next()); + assertFetchedArrayEquals(rs.getArray(1), arr4); + assertFalse(rs.next()); + } + } + } + + private static void assertFetchedArrayEquals(Array dba, short[] arr) throws Exception { + Object[] objArr = (Object[]) dba.getArray(); + assertEquals(objArr.length, arr.length); + for (int i = 0; i < objArr.length; i++) { + assertEquals(objArr[i], arr[i]); + } + } + + public static void test_appender_list_basic_array_smallint() throws Exception { + short[] arr1 = new short[] {41, 42, 43}; + short[] arr2 = new short[] {45, 46, 47}; + short[] arr3 = new short[] {48, 49, 50}; + short[] arr4 = new short[] {51, 52, 53}; + try (DuckDBConnection conn = DriverManager.getConnection(JDBC_URL).unwrap(DuckDBConnection.class); + Statement stmt = conn.createStatement()) { + stmt.execute("CREATE TABLE tab1(col1 INT, col2 SMALLINT[3][])"); + try (DuckDBAppender appender = conn.createAppender("tab1")) { + appender.beginRow() + .append(42) + .append(asList(arr1, arr2, arr3)) + .endRow() + .beginRow() + .append(43) + .append((List) null) + .endRow() + .beginRow() + .append(44) + .append(asList(null, arr4)) + .endRow() + .flush(); + } + + try (ResultSet rs = stmt.executeQuery("SELECT unnest(col2) from tab1 WHERE col1 = 42")) { + assertTrue(rs.next()); + assertFetchedArrayEquals(rs.getArray(1), arr1); + assertTrue(rs.next()); + assertFetchedArrayEquals(rs.getArray(1), arr2); + assertTrue(rs.next()); + assertFetchedArrayEquals(rs.getArray(1), arr3); + assertFalse(rs.next()); + } + try (ResultSet rs = stmt.executeQuery("SELECT col2 from tab1 WHERE col1 = 43")) { + assertTrue(rs.next()); + assertNull(rs.getObject(1)); + assertTrue(rs.wasNull()); + assertFalse(rs.next()); + } + try (ResultSet rs = stmt.executeQuery("SELECT unnest(col2) from tab1 WHERE col1 = 44")) { + assertTrue(rs.next()); + assertNull(rs.getObject(1)); + assertTrue(rs.wasNull()); + assertTrue(rs.next()); + assertFetchedArrayEquals(rs.getArray(1), arr4); + assertFalse(rs.next()); + } + } + } + + private static void assertFetchedArrayEquals(Array dba, int[] arr) throws Exception { + Object[] objArr = (Object[]) dba.getArray(); + assertEquals(objArr.length, arr.length); + for (int i = 0; i < objArr.length; i++) { + assertEquals(objArr[i], arr[i]); + } + } + + public static void test_appender_list_basic_array_int() throws Exception { + int[] arr1 = new int[] {41, 42, 43}; + int[] arr2 = new int[] {45, 46, 47}; + int[] arr3 = new int[] {48, 49, 50}; + int[] arr4 = new int[] {51, 52, 53}; + try (DuckDBConnection conn = DriverManager.getConnection(JDBC_URL).unwrap(DuckDBConnection.class); + Statement stmt = conn.createStatement()) { + stmt.execute("CREATE TABLE tab1(col1 INT, col2 INTEGER[3][])"); + try (DuckDBAppender appender = conn.createAppender("tab1")) { + appender.beginRow() + .append(42) + .append(asList(arr1, arr2, arr3)) + .endRow() + .beginRow() + .append(43) + .append((List) null) + .endRow() + .beginRow() + .append(44) + .append(asList(null, arr4)) + .endRow() + .flush(); + } + + try (ResultSet rs = stmt.executeQuery("SELECT unnest(col2) from tab1 WHERE col1 = 42")) { + assertTrue(rs.next()); + assertFetchedArrayEquals(rs.getArray(1), arr1); + assertTrue(rs.next()); + assertFetchedArrayEquals(rs.getArray(1), arr2); + assertTrue(rs.next()); + assertFetchedArrayEquals(rs.getArray(1), arr3); + assertFalse(rs.next()); + } + try (ResultSet rs = stmt.executeQuery("SELECT col2 from tab1 WHERE col1 = 43")) { + assertTrue(rs.next()); + assertNull(rs.getObject(1)); + assertTrue(rs.wasNull()); + assertFalse(rs.next()); + } + try (ResultSet rs = stmt.executeQuery("SELECT unnest(col2) from tab1 WHERE col1 = 44")) { + assertTrue(rs.next()); + assertNull(rs.getObject(1)); + assertTrue(rs.wasNull()); + assertTrue(rs.next()); + assertFetchedArrayEquals(rs.getArray(1), arr4); + assertFalse(rs.next()); + } + } + } + + private static void assertFetchedArrayEquals(Array dba, long[] arr) throws Exception { + Object[] objArr = (Object[]) dba.getArray(); + assertEquals(objArr.length, arr.length); + for (int i = 0; i < objArr.length; i++) { + assertEquals(objArr[i], arr[i]); + } + } + + public static void test_appender_list_basic_array_bigint() throws Exception { + long[] arr1 = new long[] {41, 42, 43}; + long[] arr2 = new long[] {45, 46, 47}; + long[] arr3 = new long[] {48, 49, 50}; + long[] arr4 = new long[] {51, 52, 53}; + try (DuckDBConnection conn = DriverManager.getConnection(JDBC_URL).unwrap(DuckDBConnection.class); + Statement stmt = conn.createStatement()) { + stmt.execute("CREATE TABLE tab1(col1 INT, col2 BIGINT[3][])"); + try (DuckDBAppender appender = conn.createAppender("tab1")) { + appender.beginRow() + .append(42) + .append(asList(arr1, arr2, arr3)) + .endRow() + .beginRow() + .append(43) + .append((List) null) + .endRow() + .beginRow() + .append(44) + .append(asList(null, arr4)) + .endRow() + .flush(); + } + + try (ResultSet rs = stmt.executeQuery("SELECT unnest(col2) from tab1 WHERE col1 = 42")) { + assertTrue(rs.next()); + assertFetchedArrayEquals(rs.getArray(1), arr1); + assertTrue(rs.next()); + assertFetchedArrayEquals(rs.getArray(1), arr2); + assertTrue(rs.next()); + assertFetchedArrayEquals(rs.getArray(1), arr3); + assertFalse(rs.next()); + } + try (ResultSet rs = stmt.executeQuery("SELECT col2 from tab1 WHERE col1 = 43")) { + assertTrue(rs.next()); + assertNull(rs.getObject(1)); + assertTrue(rs.wasNull()); + assertFalse(rs.next()); + } + try (ResultSet rs = stmt.executeQuery("SELECT unnest(col2) from tab1 WHERE col1 = 44")) { + assertTrue(rs.next()); + assertNull(rs.getObject(1)); + assertTrue(rs.wasNull()); + assertTrue(rs.next()); + assertFetchedArrayEquals(rs.getArray(1), arr4); + assertFalse(rs.next()); + } + } + } + + private static void assertFetchedArrayEquals(Array dba, float[] arr) throws Exception { + Object[] objArr = (Object[]) dba.getArray(); + assertEquals(objArr.length, arr.length); + for (int i = 0; i < objArr.length; i++) { + assertEquals(objArr[i], arr[i]); + } + } + + public static void test_appender_list_basic_array_float() throws Exception { + float[] arr1 = new float[] {41, 42, 43}; + float[] arr2 = new float[] {45, 46, 47}; + float[] arr3 = new float[] {48, 49, 50}; + float[] arr4 = new float[] {51, 52, 53}; + try (DuckDBConnection conn = DriverManager.getConnection(JDBC_URL).unwrap(DuckDBConnection.class); + Statement stmt = conn.createStatement()) { + stmt.execute("CREATE TABLE tab1(col1 INT, col2 FLOAT[3][])"); + try (DuckDBAppender appender = conn.createAppender("tab1")) { + appender.beginRow() + .append(42) + .append(asList(arr1, arr2, arr3)) + .endRow() + .beginRow() + .append(43) + .append((List) null) + .endRow() + .beginRow() + .append(44) + .append(asList(null, arr4)) + .endRow() + .flush(); + } + + try (ResultSet rs = stmt.executeQuery("SELECT unnest(col2) from tab1 WHERE col1 = 42")) { + assertTrue(rs.next()); + assertFetchedArrayEquals(rs.getArray(1), arr1); + assertTrue(rs.next()); + assertFetchedArrayEquals(rs.getArray(1), arr2); + assertTrue(rs.next()); + assertFetchedArrayEquals(rs.getArray(1), arr3); + assertFalse(rs.next()); + } + try (ResultSet rs = stmt.executeQuery("SELECT col2 from tab1 WHERE col1 = 43")) { + assertTrue(rs.next()); + assertNull(rs.getObject(1)); + assertTrue(rs.wasNull()); + assertFalse(rs.next()); + } + try (ResultSet rs = stmt.executeQuery("SELECT unnest(col2) from tab1 WHERE col1 = 44")) { + assertTrue(rs.next()); + assertNull(rs.getObject(1)); + assertTrue(rs.wasNull()); + assertTrue(rs.next()); + assertFetchedArrayEquals(rs.getArray(1), arr4); + assertFalse(rs.next()); + } + } + } + + private static void assertFetchedArrayEquals(Array dba, double[] arr) throws Exception { + Object[] objArr = (Object[]) dba.getArray(); + assertEquals(objArr.length, arr.length); + for (int i = 0; i < objArr.length; i++) { + assertEquals(objArr[i], arr[i]); + } + } + + public static void test_appender_list_basic_array_double() throws Exception { + double[] arr1 = new double[] {41, 42, 43}; + double[] arr2 = new double[] {45, 46, 47}; + double[] arr3 = new double[] {48, 49, 50}; + double[] arr4 = new double[] {51, 52, 53}; + try (DuckDBConnection conn = DriverManager.getConnection(JDBC_URL).unwrap(DuckDBConnection.class); + Statement stmt = conn.createStatement()) { + stmt.execute("CREATE TABLE tab1(col1 INT, col2 DOUBLE[3][])"); + try (DuckDBAppender appender = conn.createAppender("tab1")) { + appender.beginRow() + .append(42) + .append(asList(arr1, arr2, arr3)) + .endRow() + .beginRow() + .append(43) + .append((List) null) + .endRow() + .beginRow() + .append(44) + .append(asList(null, arr4)) + .endRow() + .flush(); + } + + try (ResultSet rs = stmt.executeQuery("SELECT unnest(col2) from tab1 WHERE col1 = 42")) { + assertTrue(rs.next()); + assertFetchedArrayEquals(rs.getArray(1), arr1); + assertTrue(rs.next()); + assertFetchedArrayEquals(rs.getArray(1), arr2); + assertTrue(rs.next()); + assertFetchedArrayEquals(rs.getArray(1), arr3); + assertFalse(rs.next()); + } + try (ResultSet rs = stmt.executeQuery("SELECT col2 from tab1 WHERE col1 = 43")) { + assertTrue(rs.next()); + assertNull(rs.getObject(1)); + assertTrue(rs.wasNull()); + assertFalse(rs.next()); + } + try (ResultSet rs = stmt.executeQuery("SELECT unnest(col2) from tab1 WHERE col1 = 44")) { + assertTrue(rs.next()); + assertNull(rs.getObject(1)); + assertTrue(rs.wasNull()); + assertTrue(rs.next()); + assertFetchedArrayEquals(rs.getArray(1), arr4); + assertFalse(rs.next()); + } + } + } + + private static void assertFetchedListEquals(Array dba, List list) throws Exception { + Object[] objArr = (Object[]) dba.getArray(); + assertEquals(objArr.length, list.size()); + for (int i = 0; i < objArr.length; i++) { + assertEquals(objArr[i], list.get(i)); + } + } + + public static void test_appender_list_basic_nested_list() throws Exception { + List list1 = asList("foo1", "bar1", "baz1", "boo1"); + List list2 = asList("foo2", null, "bar2"); + List list3 = new ArrayList<>(); + List list4 = asList("foo4", "bar4"); + try (DuckDBConnection conn = DriverManager.getConnection(JDBC_URL).unwrap(DuckDBConnection.class); + Statement stmt = conn.createStatement()) { + stmt.execute("CREATE TABLE tab1(col1 INT, col2 VARCHAR[][])"); + try (DuckDBAppender appender = conn.createAppender("tab1")) { + appender.beginRow() + .append(42) + .append(asList(list1, list2, list3)) + .endRow() + .beginRow() + .append(43) + .append((List) null) + .endRow() + .beginRow() + .append(44) + .append(asList(null, list4)) + .endRow() + .flush(); + } + + try (ResultSet rs = stmt.executeQuery("SELECT unnest(col2) from tab1 WHERE col1 = 42")) { + assertTrue(rs.next()); + assertFetchedListEquals(rs.getArray(1), list1); + assertTrue(rs.next()); + assertFetchedListEquals(rs.getArray(1), list2); + assertTrue(rs.next()); + assertFetchedListEquals(rs.getArray(1), list3); + assertFalse(rs.next()); + } + try (ResultSet rs = stmt.executeQuery("SELECT col2 from tab1 WHERE col1 = 43")) { + assertTrue(rs.next()); + assertNull(rs.getObject(1)); + assertTrue(rs.wasNull()); + assertFalse(rs.next()); + } + try (ResultSet rs = stmt.executeQuery("SELECT unnest(col2) from tab1 WHERE col1 = 44")) { + assertTrue(rs.next()); + assertNull(rs.getObject(1)); + assertTrue(rs.wasNull()); + assertTrue(rs.next()); + assertFetchedListEquals(rs.getArray(1), list4); + assertFalse(rs.next()); + } + } + } + + private static void assertMapsEqual(Object obj1, Map map2) throws Exception { + Map map1 = (Map) obj1; + assertEquals(map1.size(), map2.size()); + List> list2 = new ArrayList<>(map2.entrySet()); + int i = 0; + for (Map.Entry en : map1.entrySet()) { + assertEquals(en.getKey(), list2.get(i).getKey()); + assertEquals(en.getValue(), list2.get(i).getValue()); + i += 1; + } + } + + public static void test_appender_map_basic() throws Exception { + Map map1 = createMap(41, "foo", 42, "bar"); + Map map2 = createMap(41, "foo", 42, null, 43, "baz"); + try (DuckDBConnection conn = DriverManager.getConnection(JDBC_URL).unwrap(DuckDBConnection.class); + Statement stmt = conn.createStatement()) { + stmt.execute("CREATE TABLE tab1(col1 INTEGER, col2 MAP(INTEGER, VARCHAR))"); + + try (DuckDBAppender appender = conn.createAppender("tab1")) { + appender.beginRow() + .append(41) + .append(map1) + .endRow() + .beginRow() + .append(42) + .append(map2) + .endRow() + .flush(); + } + + try (ResultSet rs = stmt.executeQuery("SELECT col2 FROM tab1 ORDER BY col1")) { + assertTrue(rs.next()); + assertMapsEqual(rs.getObject(1), map1); + assertTrue(rs.next()); + assertMapsEqual(rs.getObject(1), map2); + assertFalse(rs.next()); + } + } + } + + public static void test_appender_list_basic_map() throws Exception { + Map map1 = createMap(41, "foo1", 42, "bar1", 43, "baz1"); + Map map2 = createMap(44, null, 45, "bar2"); + Map map3 = new LinkedHashMap<>(); + Map map4 = createMap(46, "foo3"); + try (DuckDBConnection conn = DriverManager.getConnection(JDBC_URL).unwrap(DuckDBConnection.class); + Statement stmt = conn.createStatement()) { + stmt.execute("CREATE TABLE tab1(col1 INT, col2 MAP(INTEGER, VARCHAR)[])"); + try (DuckDBAppender appender = conn.createAppender("tab1")) { + appender.beginRow() + .append(42) + .append(asList(map1, map2, map3)) + .endRow() + .beginRow() + .append(43) + .append((List) null) + .endRow() + .beginRow() + .append(44) + .append(asList(null, map4)) + .endRow() + .flush(); + } + + try (ResultSet rs = stmt.executeQuery("SELECT unnest(col2) from tab1 WHERE col1 = 42")) { + assertTrue(rs.next()); + assertMapsEqual(rs.getObject(1), map1); + assertTrue(rs.next()); + assertMapsEqual(rs.getObject(1), map2); + assertTrue(rs.next()); + assertMapsEqual(rs.getObject(1), map3); + assertFalse(rs.next()); + } + try (ResultSet rs = stmt.executeQuery("SELECT col2 from tab1 WHERE col1 = 43")) { + assertTrue(rs.next()); + assertNull(rs.getObject(1)); + assertTrue(rs.wasNull()); + assertFalse(rs.next()); + } + try (ResultSet rs = stmt.executeQuery("SELECT unnest(col2) from tab1 WHERE col1 = 44")) { + assertTrue(rs.next()); + assertNull(rs.getObject(1)); + assertTrue(rs.wasNull()); + assertTrue(rs.next()); + assertMapsEqual(rs.getObject(1), map4); + assertFalse(rs.next()); + } + } + } } diff --git a/src/test/java/org/duckdb/TestAppenderCollection2D.java b/src/test/java/org/duckdb/TestAppenderCollection2D.java new file mode 100644 index 000000000..b7f46fa92 --- /dev/null +++ b/src/test/java/org/duckdb/TestAppenderCollection2D.java @@ -0,0 +1,875 @@ +package org.duckdb; + +import static java.util.Arrays.asList; +import static org.duckdb.TestDuckDBJDBC.JDBC_URL; +import static org.duckdb.test.Assertions.*; +import static org.duckdb.test.Assertions.assertFalse; + +import java.sql.Array; +import java.sql.DriverManager; +import java.sql.ResultSet; +import java.sql.Statement; +import java.util.List; + +public class TestAppenderCollection2D { + + public static void test_appender_nested_array_bool() throws Exception { + try (DuckDBConnection conn = DriverManager.getConnection(JDBC_URL).unwrap(DuckDBConnection.class); + Statement stmt = conn.createStatement()) { + + int count = 1 << 11; // auto flush + int tail = 17; // flushed on close + int arrayLen = (1 << 6) + 5; // increase this for stress tests + int childLen = (1 << 6) + 7; + + stmt.execute("CREATE TABLE tab1(col1 INT, col2 BOOLEAN[" + childLen + "][" + arrayLen + "])"); + + try (DuckDBAppender appender = conn.createAppender("tab1")) { + for (int i = 0; i < count + tail; i++) { + boolean[][] arr = new boolean[arrayLen][childLen]; + for (int j = 0; j < arrayLen; j++) { + for (int k = 0; k < childLen; k++) { + arr[j][k] = 0 == (i + j + k) % 2; + } + } + appender.beginRow().append(i).append(arr).endRow(); + } + } + + try (ResultSet rs = stmt.executeQuery("SELECT col1, col2[" + (arrayLen - 1) + "][" + (childLen - 1) + + "] FROM tab1 ORDER BY col1")) { + for (int i = 0; i < count + tail; i++) { + assertTrue(rs.next()); + assertEquals(rs.getInt(1), i); + assertEquals(rs.getBoolean(2), 0 == (i + arrayLen + childLen - 4) % 2); + } + assertFalse(rs.next()); + } + + int row = count - 2; + try (Statement stmt2 = conn.createStatement(); + ResultSet rs2 = stmt2.executeQuery( + "SELECT unnest(ucol2) FROM (SELECT unnest(col2) as ucol2 FROM tab1 WHERE col1 = " + row + ")")) { + for (int j = 0; j < arrayLen; j++) { + for (int k = 0; k < childLen; k++) { + assertTrue(rs2.next()); + assertEquals(rs2.getBoolean(1), 0 == (row + j + k) % 2); + } + } + assertFalse(rs2.next()); + } + } + } + + public static void test_appender_nested_array_tinyint() throws Exception { + try (DuckDBConnection conn = DriverManager.getConnection(JDBC_URL).unwrap(DuckDBConnection.class); + Statement stmt = conn.createStatement()) { + + int count = 1 << 11; // auto flush + int tail = 17; // flushed on close + int arrayLen = (1 << 6) + 5; // increase this for stress tests + int childLen = (1 << 6) + 7; + + stmt.execute("CREATE TABLE tab1(col1 INT, col2 TINYINT[" + childLen + "][" + arrayLen + "])"); + + try (DuckDBAppender appender = conn.createAppender("tab1")) { + for (int i = 0; i < count + tail; i++) { + byte[][] arr = new byte[arrayLen][childLen]; + for (int j = 0; j < arrayLen; j++) { + for (int k = 0; k < childLen; k++) { + arr[j][k] = (byte) (i + j + k); + } + } + appender.beginRow().append(i).appendByteArray(arr).endRow(); + } + } + + try (ResultSet rs = stmt.executeQuery("SELECT col1, col2[" + (arrayLen - 1) + "][" + (childLen - 1) + + "] FROM tab1 ORDER BY col1")) { + for (int i = 0; i < count + tail; i++) { + assertTrue(rs.next()); + assertEquals(rs.getInt(1), i); + assertEquals(rs.getByte(2), (byte) (i + arrayLen + childLen - 4)); + } + assertFalse(rs.next()); + } + + int row = count - 2; + try (Statement stmt2 = conn.createStatement(); + ResultSet rs2 = stmt2.executeQuery( + "SELECT unnest(ucol2) FROM (SELECT unnest(col2) as ucol2 FROM tab1 WHERE col1 = " + row + ")")) { + for (int j = 0; j < arrayLen; j++) { + for (int k = 0; k < childLen; k++) { + assertTrue(rs2.next()); + assertEquals(rs2.getByte(1), (byte) (row + j + k)); + } + } + assertFalse(rs2.next()); + } + } + } + + public static void test_appender_nested_array_smallint() throws Exception { + try (DuckDBConnection conn = DriverManager.getConnection(JDBC_URL).unwrap(DuckDBConnection.class); + Statement stmt = conn.createStatement()) { + + int count = 1 << 11; // auto flush + int tail = 17; // flushed on close + int arrayLen = (1 << 6) + 5; // increase this for stress tests + int childLen = (1 << 6) + 7; + + stmt.execute("CREATE TABLE tab1(col1 INT, col2 SMALLINT[" + childLen + "][" + arrayLen + "])"); + + try (DuckDBAppender appender = conn.createAppender("tab1")) { + for (int i = 0; i < count + tail; i++) { + short[][] arr = new short[arrayLen][childLen]; + for (int j = 0; j < arrayLen; j++) { + for (int k = 0; k < childLen; k++) { + arr[j][k] = (short) (i + j + k); + } + } + appender.beginRow().append(i).append(arr).endRow(); + } + } + + try (ResultSet rs = stmt.executeQuery("SELECT col1, col2[" + (arrayLen - 1) + "][" + (childLen - 1) + + "] FROM tab1 ORDER BY col1")) { + for (int i = 0; i < count + tail; i++) { + assertTrue(rs.next()); + assertEquals(rs.getInt(1), i); + assertEquals(rs.getShort(2), (short) (i + arrayLen + childLen - 4)); + } + assertFalse(rs.next()); + } + + int row = count - 2; + try (Statement stmt2 = conn.createStatement(); + ResultSet rs2 = stmt2.executeQuery( + "SELECT unnest(ucol2) FROM (SELECT unnest(col2) as ucol2 FROM tab1 WHERE col1 = " + row + ")")) { + for (int j = 0; j < arrayLen; j++) { + for (int k = 0; k < childLen; k++) { + assertTrue(rs2.next()); + assertEquals(rs2.getShort(1), (short) (row + j + k)); + } + } + assertFalse(rs2.next()); + } + } + } + + public static void test_appender_nested_array_basic_integer() throws Exception { + try (DuckDBConnection conn = DriverManager.getConnection(JDBC_URL).unwrap(DuckDBConnection.class); + Statement stmt = conn.createStatement()) { + + stmt.execute("CREATE TABLE tab1(col1 INT, col2 INTEGER[2][3])"); + + try (DuckDBAppender appender = conn.createAppender("tab1")) { + appender.beginRow().append(41).append(new int[][] {{42, 43}, {44, 45}, {46, 47}}).endRow(); + appender.beginRow().append(48).append(new int[][] {{49, 50}, null, {53, 54}}).endRow(); + appender.beginRow() + .append(55) + .append(new int[][] {{56, 57}, {58, 59}, {60, 61}}, + new boolean[][] {{false, true}, {false, false}, {true, false}}) + .endRow(); + } + + try (ResultSet rs = stmt.executeQuery("SELECT col1, unnest(col2) FROM tab1 WHERE col1 = 41")) { + assertTrue(rs.next()); + assertEquals(rs.getInt(1), 41); + Object[] array1 = (Object[]) rs.getArray(2).getArray(); + assertEquals(array1.length, 2); + assertEquals(array1[0], 42); + assertEquals(array1[1], 43); + + assertTrue(rs.next()); + assertEquals(rs.getInt(1), 41); + Object[] array2 = (Object[]) rs.getArray(2).getArray(); + assertEquals(array2.length, 2); + assertEquals(array2[0], 44); + assertEquals(array2[1], 45); + + assertTrue(rs.next()); + assertEquals(rs.getInt(1), 41); + Object[] array3 = (Object[]) rs.getArray(2).getArray(); + assertEquals(array3.length, 2); + assertEquals(array3[0], 46); + assertEquals(array3[1], 47); + + assertFalse(rs.next()); + } + + try (ResultSet rs = stmt.executeQuery("SELECT col1, unnest(col2) FROM tab1 WHERE col1 = 48")) { + assertTrue(rs.next()); + assertEquals(rs.getInt(1), 48); + Object[] array1 = (Object[]) rs.getArray(2).getArray(); + assertEquals(array1.length, 2); + assertEquals(array1[0], 49); + assertEquals(array1[1], 50); + + assertTrue(rs.next()); + assertEquals(rs.getInt(1), 48); + assertNull(rs.getObject(2)); + + assertTrue(rs.next()); + assertEquals(rs.getInt(1), 48); + Object[] array3 = (Object[]) rs.getArray(2).getArray(); + assertEquals(array3.length, 2); + assertEquals(array3[0], 53); + assertEquals(array3[1], 54); + + assertFalse(rs.next()); + } + + try (ResultSet rs = stmt.executeQuery("SELECT col1, unnest(col2) FROM tab1 WHERE col1 = 55")) { + assertTrue(rs.next()); + assertEquals(rs.getInt(1), 55); + Object[] array1 = (Object[]) rs.getArray(2).getArray(); + assertEquals(array1.length, 2); + assertEquals(array1[0], 56); + assertNull(array1[1]); + + assertTrue(rs.next()); + assertEquals(rs.getInt(1), 55); + Object[] array2 = (Object[]) rs.getArray(2).getArray(); + assertEquals(array2.length, 2); + assertEquals(array2[0], 58); + assertEquals(array2[1], 59); + + assertTrue(rs.next()); + assertEquals(rs.getInt(1), 55); + Object[] array3 = (Object[]) rs.getArray(2).getArray(); + assertEquals(array3.length, 2); + assertNull(array3[0]); + assertEquals(array3[1], 61); + + assertFalse(rs.next()); + } + } + } + + public static void test_appender_nested_array_integer() throws Exception { + try (DuckDBConnection conn = DriverManager.getConnection(JDBC_URL).unwrap(DuckDBConnection.class); + Statement stmt = conn.createStatement()) { + + int count = 1 << 12; // auto flush twice + int tail = 17; // flushed on close + int arrayLen = (1 << 6) + 5; // increase this for stress tests + int childLen = (1 << 6) + 7; + + stmt.execute("CREATE TABLE tab1(col1 INT, col2 INTEGER[" + childLen + "][" + arrayLen + "])"); + + try (DuckDBAppender appender = conn.createAppender("tab1")) { + for (int i = 0; i < count + tail; i++) { + int[][] arr = new int[arrayLen][childLen]; + for (int j = 0; j < arrayLen; j++) { + for (int k = 0; k < childLen; k++) { + arr[j][k] = (i + 1) * (j + 1) * (k + 1); + } + } + appender.beginRow().append(i).append(arr).endRow(); + } + } + + try (ResultSet rs = stmt.executeQuery("SELECT col1, col2[" + (arrayLen - 1) + "][" + (childLen - 1) + + "] FROM tab1 ORDER BY col1")) { + for (int i = 0; i < count + tail; i++) { + assertTrue(rs.next()); + assertEquals(rs.getInt(1), i); + assertEquals(rs.getInt(2), (i + 1) * (arrayLen - 1) * (childLen - 1)); + } + assertFalse(rs.next()); + } + + int row = count - 2; + try (Statement stmt2 = conn.createStatement(); + ResultSet rs2 = stmt2.executeQuery( + "SELECT unnest(ucol2) FROM (SELECT unnest(col2) as ucol2 FROM tab1 WHERE col1 = " + row + ")")) { + for (int j = 0; j < arrayLen; j++) { + for (int k = 0; k < childLen; k++) { + assertTrue(rs2.next()); + assertEquals(rs2.getInt(1), (row + 1) * (j + 1) * (k + 1)); + } + } + assertFalse(rs2.next()); + } + } + } + + public static void test_appender_nested_array_bigint() throws Exception { + try (DuckDBConnection conn = DriverManager.getConnection(JDBC_URL).unwrap(DuckDBConnection.class); + Statement stmt = conn.createStatement()) { + + int count = 1 << 11; // auto flush + int tail = 17; // flushed on close + int arrayLen = (1 << 6) + 5; // increase this for stress tests + int childLen = (1 << 6) + 7; + + stmt.execute("CREATE TABLE tab1(col1 INT, col2 BIGINT[" + childLen + "][" + arrayLen + "])"); + + try (DuckDBAppender appender = conn.createAppender("tab1")) { + for (int i = 0; i < count + tail; i++) { + long[][] arr = new long[arrayLen][childLen]; + for (int j = 0; j < arrayLen; j++) { + for (int k = 0; k < childLen; k++) { + arr[j][k] = (long) (i + 1) * (j + 1) * (k + 1); + } + } + appender.beginRow().append(i).append(arr).endRow(); + } + } + + try (ResultSet rs = stmt.executeQuery("SELECT col1, col2[" + (arrayLen - 1) + "][" + (childLen - 1) + + "] FROM tab1 ORDER BY col1")) { + for (int i = 0; i < count + tail; i++) { + assertTrue(rs.next()); + assertEquals(rs.getInt(1), i); + assertEquals(rs.getLong(2), (long) ((i + 1) * (arrayLen - 1) * (childLen - 1))); + } + assertFalse(rs.next()); + } + + int row = count - 2; + try (Statement stmt2 = conn.createStatement(); + ResultSet rs2 = stmt2.executeQuery( + "SELECT unnest(ucol2) FROM (SELECT unnest(col2) as ucol2 FROM tab1 WHERE col1 = " + row + ")")) { + for (int j = 0; j < arrayLen; j++) { + for (int k = 0; k < childLen; k++) { + assertTrue(rs2.next()); + assertEquals(rs2.getLong(1), (long) (row + 1) * (j + 1) * (k + 1)); + } + } + assertFalse(rs2.next()); + } + } + } + + public static void test_appender_nested_array_float() throws Exception { + try (DuckDBConnection conn = DriverManager.getConnection(JDBC_URL).unwrap(DuckDBConnection.class); + Statement stmt = conn.createStatement()) { + + int count = 1 << 12; // auto flush twice + int tail = 17; // flushed on close + int arrayLen = (1 << 6) + 5; // increase this for stress tests + int childLen = (1 << 6) + 7; + + stmt.execute("CREATE TABLE tab1(col1 INT, col2 FLOAT[" + childLen + "][" + arrayLen + "])"); + + try (DuckDBAppender appender = conn.createAppender("tab1")) { + for (int i = 0; i < count + tail; i++) { + float[][] arr = new float[arrayLen][childLen]; + for (int j = 0; j < arrayLen; j++) { + for (int k = 0; k < childLen; k++) { + arr[j][k] = (i + 1) * (j + 1) * (k + 1) + 0.001f; + } + } + appender.beginRow().append(i).append(arr).endRow(); + } + } + + try (ResultSet rs = stmt.executeQuery("SELECT col1, col2[" + (arrayLen - 1) + "][" + (childLen - 1) + + "] FROM tab1 ORDER BY col1")) { + for (int i = 0; i < count + tail; i++) { + assertTrue(rs.next()); + assertEquals(rs.getInt(1), i); + assertEquals(rs.getFloat(2), (i + 1) * (arrayLen - 1) * (childLen - 1) + 0.001f); + } + assertFalse(rs.next()); + } + + int row = count - 2; + try (Statement stmt2 = conn.createStatement(); + ResultSet rs2 = stmt2.executeQuery( + "SELECT unnest(ucol2) FROM (SELECT unnest(col2) as ucol2 FROM tab1 WHERE col1 = " + row + ")")) { + for (int j = 0; j < arrayLen; j++) { + for (int k = 0; k < childLen; k++) { + assertTrue(rs2.next()); + assertEquals(rs2.getFloat(1), (row + 1) * (j + 1) * (k + 1) + 0.001f); + } + } + assertFalse(rs2.next()); + } + } + } + + public static void test_appender_nested_array_double() throws Exception { + try (DuckDBConnection conn = DriverManager.getConnection(JDBC_URL).unwrap(DuckDBConnection.class); + Statement stmt = conn.createStatement()) { + + int count = 1 << 11; // auto flush + int tail = 17; // flushed on close + int arrayLen = (1 << 6) + 5; // increase this for stress tests + int childLen = (1 << 6) + 7; + + stmt.execute("CREATE TABLE tab1(col1 INT, col2 DOUBLE[" + childLen + "][" + arrayLen + "])"); + + try (DuckDBAppender appender = conn.createAppender("tab1")) { + for (int i = 0; i < count + tail; i++) { + double[][] arr = new double[arrayLen][childLen]; + for (int j = 0; j < arrayLen; j++) { + for (int k = 0; k < childLen; k++) { + arr[j][k] = (i + 1) * (j + 1) * (k + 1) + 0.001; + } + } + appender.beginRow().append(i).append(arr).endRow(); + } + } + + try (ResultSet rs = stmt.executeQuery("SELECT col1, col2[" + (arrayLen - 1) + "][" + (childLen - 1) + + "] FROM tab1 ORDER BY col1")) { + for (int i = 0; i < count + tail; i++) { + assertTrue(rs.next()); + assertEquals(rs.getInt(1), i); + assertEquals(rs.getDouble(2), (i + 1) * (arrayLen - 1) * (childLen - 1) + 0.001); + } + assertFalse(rs.next()); + } + + int row = count - 2; + try (Statement stmt2 = conn.createStatement(); + ResultSet rs2 = stmt2.executeQuery( + "SELECT unnest(ucol2) FROM (SELECT unnest(col2) as ucol2 FROM tab1 WHERE col1 = " + row + ")")) { + for (int j = 0; j < arrayLen; j++) { + for (int k = 0; k < childLen; k++) { + assertTrue(rs2.next()); + assertEquals(rs2.getDouble(1), (row + 1) * (j + 1) * (k + 1) + 0.001); + } + } + assertFalse(rs2.next()); + } + } + } + + private static void assertFetchedArrayEquals(Array dba, boolean[][] arr) throws Exception { + Object[] objArr = (Object[]) dba.getArray(); + assertEquals(objArr.length, arr.length); + for (int i = 0; i < objArr.length; i++) { + Array wrapper = (Array) objArr[i]; + Object[] objArrInner = (Object[]) wrapper.getArray(); + for (int j = 0; j < objArrInner.length; j++) { + assertEquals(objArrInner[j], arr[i][j]); + } + } + } + + public static void test_appender_list_basic_array_bool_2d() throws Exception { + boolean[][] arr1 = new boolean[][] {{true, false, true}, {false, true, false}}; + boolean[][] arr2 = new boolean[][] {{false, true, false}, {true, false, true}}; + boolean[][] arr3 = new boolean[][] {{true, true, false}, {false, true, true}}; + boolean[][] arr4 = new boolean[][] {{false, false, true}, {true, false, false}}; + try (DuckDBConnection conn = DriverManager.getConnection(JDBC_URL).unwrap(DuckDBConnection.class); + Statement stmt = conn.createStatement()) { + stmt.execute("CREATE TABLE tab1(col1 INT, col2 BOOL[3][2][])"); + try (DuckDBAppender appender = conn.createAppender("tab1")) { + appender.beginRow() + .append(42) + .append(asList(arr1, arr2, arr3)) + .endRow() + .beginRow() + .append(43) + .append((List) null) + .endRow() + .beginRow() + .append(44) + .append(asList(null, arr4)) + .endRow() + .flush(); + } + + try (ResultSet rs = stmt.executeQuery("SELECT unnest(col2) from tab1 WHERE col1 = 42")) { + assertTrue(rs.next()); + assertFetchedArrayEquals(rs.getArray(1), arr1); + assertTrue(rs.next()); + assertFetchedArrayEquals(rs.getArray(1), arr2); + assertTrue(rs.next()); + assertFetchedArrayEquals(rs.getArray(1), arr3); + assertFalse(rs.next()); + } + try (ResultSet rs = stmt.executeQuery("SELECT col2 from tab1 WHERE col1 = 43")) { + assertTrue(rs.next()); + assertNull(rs.getObject(1)); + assertTrue(rs.wasNull()); + assertFalse(rs.next()); + } + try (ResultSet rs = stmt.executeQuery("SELECT unnest(col2) from tab1 WHERE col1 = 44")) { + assertTrue(rs.next()); + assertNull(rs.getObject(1)); + assertTrue(rs.wasNull()); + assertTrue(rs.next()); + assertFetchedArrayEquals(rs.getArray(1), arr4); + assertFalse(rs.next()); + } + } + } + + private static void assertFetchedArrayEquals(Array dba, byte[][] arr) throws Exception { + Object[] objArr = (Object[]) dba.getArray(); + assertEquals(objArr.length, arr.length); + for (int i = 0; i < objArr.length; i++) { + Array wrapper = (Array) objArr[i]; + Object[] objArrInner = (Object[]) wrapper.getArray(); + for (int j = 0; j < objArrInner.length; j++) { + assertEquals(objArrInner[j], arr[i][j]); + } + } + } + + public static void test_appender_list_basic_array_tinyint_2d() throws Exception { + byte[][] arr1 = new byte[][] {{41, 42, 43}, {44, 45, 46}}; + byte[][] arr2 = new byte[][] {{51, 52, 53}, {54, 55, 56}}; + byte[][] arr3 = new byte[][] {{61, 62, 63}, {64, 65, 66}}; + byte[][] arr4 = new byte[][] {{71, 72, 73}, {74, 75, 76}}; + try (DuckDBConnection conn = DriverManager.getConnection(JDBC_URL).unwrap(DuckDBConnection.class); + Statement stmt = conn.createStatement()) { + stmt.execute("CREATE TABLE tab1(col1 INT, col2 TINYINT[3][2][])"); + try (DuckDBAppender appender = conn.createAppender("tab1")) { + appender.beginRow() + .append(42) + .append(asList(arr1, arr2, arr3)) + .endRow() + .beginRow() + .append(43) + .append((List) null) + .endRow() + .beginRow() + .append(44) + .append(asList(null, arr4)) + .endRow() + .flush(); + } + + try (ResultSet rs = stmt.executeQuery("SELECT unnest(col2) from tab1 WHERE col1 = 42")) { + assertTrue(rs.next()); + assertFetchedArrayEquals(rs.getArray(1), arr1); + assertTrue(rs.next()); + assertFetchedArrayEquals(rs.getArray(1), arr2); + assertTrue(rs.next()); + assertFetchedArrayEquals(rs.getArray(1), arr3); + assertFalse(rs.next()); + } + try (ResultSet rs = stmt.executeQuery("SELECT col2 from tab1 WHERE col1 = 43")) { + assertTrue(rs.next()); + assertNull(rs.getObject(1)); + assertTrue(rs.wasNull()); + assertFalse(rs.next()); + } + try (ResultSet rs = stmt.executeQuery("SELECT unnest(col2) from tab1 WHERE col1 = 44")) { + assertTrue(rs.next()); + assertNull(rs.getObject(1)); + assertTrue(rs.wasNull()); + assertTrue(rs.next()); + assertFetchedArrayEquals(rs.getArray(1), arr4); + assertFalse(rs.next()); + } + } + } + + private static void assertFetchedArrayEquals(Array dba, short[][] arr) throws Exception { + Object[] objArr = (Object[]) dba.getArray(); + assertEquals(objArr.length, arr.length); + for (int i = 0; i < objArr.length; i++) { + Array wrapper = (Array) objArr[i]; + Object[] objArrInner = (Object[]) wrapper.getArray(); + for (int j = 0; j < objArrInner.length; j++) { + assertEquals(objArrInner[j], arr[i][j]); + } + } + } + + public static void test_appender_list_basic_array_smallint_2d() throws Exception { + short[][] arr1 = new short[][] {{41, 42, 43}, {44, 45, 46}}; + short[][] arr2 = new short[][] {{51, 52, 53}, {54, 55, 56}}; + short[][] arr3 = new short[][] {{61, 62, 63}, {64, 65, 66}}; + short[][] arr4 = new short[][] {{71, 72, 73}, {74, 75, 76}}; + try (DuckDBConnection conn = DriverManager.getConnection(JDBC_URL).unwrap(DuckDBConnection.class); + Statement stmt = conn.createStatement()) { + stmt.execute("CREATE TABLE tab1(col1 INT, col2 SMALLINT[3][2][])"); + try (DuckDBAppender appender = conn.createAppender("tab1")) { + appender.beginRow() + .append(42) + .append(asList(arr1, arr2, arr3)) + .endRow() + .beginRow() + .append(43) + .append((List) null) + .endRow() + .beginRow() + .append(44) + .append(asList(null, arr4)) + .endRow() + .flush(); + } + + try (ResultSet rs = stmt.executeQuery("SELECT unnest(col2) from tab1 WHERE col1 = 42")) { + assertTrue(rs.next()); + assertFetchedArrayEquals(rs.getArray(1), arr1); + assertTrue(rs.next()); + assertFetchedArrayEquals(rs.getArray(1), arr2); + assertTrue(rs.next()); + assertFetchedArrayEquals(rs.getArray(1), arr3); + assertFalse(rs.next()); + } + try (ResultSet rs = stmt.executeQuery("SELECT col2 from tab1 WHERE col1 = 43")) { + assertTrue(rs.next()); + assertNull(rs.getObject(1)); + assertTrue(rs.wasNull()); + assertFalse(rs.next()); + } + try (ResultSet rs = stmt.executeQuery("SELECT unnest(col2) from tab1 WHERE col1 = 44")) { + assertTrue(rs.next()); + assertNull(rs.getObject(1)); + assertTrue(rs.wasNull()); + assertTrue(rs.next()); + assertFetchedArrayEquals(rs.getArray(1), arr4); + assertFalse(rs.next()); + } + } + } + + private static void assertFetchedArrayEquals(Array dba, int[][] arr) throws Exception { + Object[] objArr = (Object[]) dba.getArray(); + assertEquals(objArr.length, arr.length); + for (int i = 0; i < objArr.length; i++) { + Array wrapper = (Array) objArr[i]; + Object[] objArrInner = (Object[]) wrapper.getArray(); + for (int j = 0; j < objArrInner.length; j++) { + assertEquals(objArrInner[j], arr[i][j]); + } + } + } + + public static void test_appender_list_basic_array_int_2d() throws Exception { + int[][] arr1 = new int[][] {{41, 42, 43}, {44, 45, 46}}; + int[][] arr2 = new int[][] {{51, 52, 53}, {54, 55, 56}}; + int[][] arr3 = new int[][] {{61, 62, 63}, {64, 65, 66}}; + int[][] arr4 = new int[][] {{71, 72, 73}, {74, 75, 76}}; + try (DuckDBConnection conn = DriverManager.getConnection(JDBC_URL).unwrap(DuckDBConnection.class); + Statement stmt = conn.createStatement()) { + stmt.execute("CREATE TABLE tab1(col1 INT, col2 INTEGER[3][2][])"); + try (DuckDBAppender appender = conn.createAppender("tab1")) { + appender.beginRow() + .append(42) + .append(asList(arr1, arr2, arr3)) + .endRow() + .beginRow() + .append(43) + .append((List) null) + .endRow() + .beginRow() + .append(44) + .append(asList(null, arr4)) + .endRow() + .flush(); + } + + try (ResultSet rs = stmt.executeQuery("SELECT unnest(col2) from tab1 WHERE col1 = 42")) { + assertTrue(rs.next()); + assertFetchedArrayEquals(rs.getArray(1), arr1); + assertTrue(rs.next()); + assertFetchedArrayEquals(rs.getArray(1), arr2); + assertTrue(rs.next()); + assertFetchedArrayEquals(rs.getArray(1), arr3); + assertFalse(rs.next()); + } + try (ResultSet rs = stmt.executeQuery("SELECT col2 from tab1 WHERE col1 = 43")) { + assertTrue(rs.next()); + assertNull(rs.getObject(1)); + assertTrue(rs.wasNull()); + assertFalse(rs.next()); + } + try (ResultSet rs = stmt.executeQuery("SELECT unnest(col2) from tab1 WHERE col1 = 44")) { + assertTrue(rs.next()); + assertNull(rs.getObject(1)); + assertTrue(rs.wasNull()); + assertTrue(rs.next()); + assertFetchedArrayEquals(rs.getArray(1), arr4); + assertFalse(rs.next()); + } + } + } + + private static void assertFetchedArrayEquals(Array dba, long[][] arr) throws Exception { + Object[] objArr = (Object[]) dba.getArray(); + assertEquals(objArr.length, arr.length); + for (int i = 0; i < objArr.length; i++) { + Array wrapper = (Array) objArr[i]; + Object[] objArrInner = (Object[]) wrapper.getArray(); + for (int j = 0; j < objArrInner.length; j++) { + assertEquals(objArrInner[j], arr[i][j]); + } + } + } + + public static void test_appender_list_basic_array_bigint_2d() throws Exception { + long[][] arr1 = new long[][] {{41, 42, 43}, {44, 45, 46}}; + long[][] arr2 = new long[][] {{51, 52, 53}, {54, 55, 56}}; + long[][] arr3 = new long[][] {{61, 62, 63}, {64, 65, 66}}; + long[][] arr4 = new long[][] {{71, 72, 73}, {74, 75, 76}}; + try (DuckDBConnection conn = DriverManager.getConnection(JDBC_URL).unwrap(DuckDBConnection.class); + Statement stmt = conn.createStatement()) { + stmt.execute("CREATE TABLE tab1(col1 INT, col2 BIGINT[3][2][])"); + try (DuckDBAppender appender = conn.createAppender("tab1")) { + appender.beginRow() + .append(42) + .append(asList(arr1, arr2, arr3)) + .endRow() + .beginRow() + .append(43) + .append((List) null) + .endRow() + .beginRow() + .append(44) + .append(asList(null, arr4)) + .endRow() + .flush(); + } + + try (ResultSet rs = stmt.executeQuery("SELECT unnest(col2) from tab1 WHERE col1 = 42")) { + assertTrue(rs.next()); + assertFetchedArrayEquals(rs.getArray(1), arr1); + assertTrue(rs.next()); + assertFetchedArrayEquals(rs.getArray(1), arr2); + assertTrue(rs.next()); + assertFetchedArrayEquals(rs.getArray(1), arr3); + assertFalse(rs.next()); + } + try (ResultSet rs = stmt.executeQuery("SELECT col2 from tab1 WHERE col1 = 43")) { + assertTrue(rs.next()); + assertNull(rs.getObject(1)); + assertTrue(rs.wasNull()); + assertFalse(rs.next()); + } + try (ResultSet rs = stmt.executeQuery("SELECT unnest(col2) from tab1 WHERE col1 = 44")) { + assertTrue(rs.next()); + assertNull(rs.getObject(1)); + assertTrue(rs.wasNull()); + assertTrue(rs.next()); + assertFetchedArrayEquals(rs.getArray(1), arr4); + assertFalse(rs.next()); + } + } + } + + private static void assertFetchedArrayEquals(Array dba, float[][] arr) throws Exception { + Object[] objArr = (Object[]) dba.getArray(); + assertEquals(objArr.length, arr.length); + for (int i = 0; i < objArr.length; i++) { + Array wrapper = (Array) objArr[i]; + Object[] objArrInner = (Object[]) wrapper.getArray(); + for (int j = 0; j < objArrInner.length; j++) { + assertEquals(objArrInner[j], arr[i][j]); + } + } + } + + public static void test_appender_list_basic_array_float_2d() throws Exception { + float[][] arr1 = new float[][] {{41.1F, 42.1F, 43.1F}, {44.1F, 45.1F, 46.1F}}; + float[][] arr2 = new float[][] {{51.1F, 52.1F, 53.1F}, {54.1F, 55.1F, 56.1F}}; + float[][] arr3 = new float[][] {{61.1F, 62.1F, 63.1F}, {64.1F, 65.1F, 66.1F}}; + float[][] arr4 = new float[][] {{71.1F, 72.1F, 73.1F}, {74.1F, 75.1F, 76.1F}}; + try (DuckDBConnection conn = DriverManager.getConnection(JDBC_URL).unwrap(DuckDBConnection.class); + Statement stmt = conn.createStatement()) { + stmt.execute("CREATE TABLE tab1(col1 INT, col2 FLOAT[3][2][])"); + try (DuckDBAppender appender = conn.createAppender("tab1")) { + appender.beginRow() + .append(42) + .append(asList(arr1, arr2, arr3)) + .endRow() + .beginRow() + .append(43) + .append((List) null) + .endRow() + .beginRow() + .append(44) + .append(asList(null, arr4)) + .endRow() + .flush(); + } + + try (ResultSet rs = stmt.executeQuery("SELECT unnest(col2) from tab1 WHERE col1 = 42")) { + assertTrue(rs.next()); + assertFetchedArrayEquals(rs.getArray(1), arr1); + assertTrue(rs.next()); + assertFetchedArrayEquals(rs.getArray(1), arr2); + assertTrue(rs.next()); + assertFetchedArrayEquals(rs.getArray(1), arr3); + assertFalse(rs.next()); + } + try (ResultSet rs = stmt.executeQuery("SELECT col2 from tab1 WHERE col1 = 43")) { + assertTrue(rs.next()); + assertNull(rs.getObject(1)); + assertTrue(rs.wasNull()); + assertFalse(rs.next()); + } + try (ResultSet rs = stmt.executeQuery("SELECT unnest(col2) from tab1 WHERE col1 = 44")) { + assertTrue(rs.next()); + assertNull(rs.getObject(1)); + assertTrue(rs.wasNull()); + assertTrue(rs.next()); + assertFetchedArrayEquals(rs.getArray(1), arr4); + assertFalse(rs.next()); + } + } + } + + private static void assertFetchedArrayEquals(Array dba, double[][] arr) throws Exception { + Object[] objArr = (Object[]) dba.getArray(); + assertEquals(objArr.length, arr.length); + for (int i = 0; i < objArr.length; i++) { + Array wrapper = (Array) objArr[i]; + Object[] objArrInner = (Object[]) wrapper.getArray(); + for (int j = 0; j < objArrInner.length; j++) { + assertEquals(objArrInner[j], arr[i][j]); + } + } + } + + public static void test_appender_list_basic_array_double_2d() throws Exception { + double[][] arr1 = new double[][] {{41.1D, 42.1D, 43.1D}, {44.1D, 45.1D, 46.1D}}; + double[][] arr2 = new double[][] {{51.1D, 52.1D, 53.1D}, {54.1D, 55.1D, 56.1D}}; + double[][] arr3 = new double[][] {{61.1D, 62.1D, 63.1D}, {64.1D, 65.1D, 66.1D}}; + double[][] arr4 = new double[][] {{71.1D, 72.1D, 73.1D}, {74.1D, 75.1D, 76.1D}}; + try (DuckDBConnection conn = DriverManager.getConnection(JDBC_URL).unwrap(DuckDBConnection.class); + Statement stmt = conn.createStatement()) { + stmt.execute("CREATE TABLE tab1(col1 INT, col2 DOUBLE[3][2][])"); + try (DuckDBAppender appender = conn.createAppender("tab1")) { + appender.beginRow() + .append(42) + .append(asList(arr1, arr2, arr3)) + .endRow() + .beginRow() + .append(43) + .append((List) null) + .endRow() + .beginRow() + .append(44) + .append(asList(null, arr4)) + .endRow() + .flush(); + } + + try (ResultSet rs = stmt.executeQuery("SELECT unnest(col2) from tab1 WHERE col1 = 42")) { + assertTrue(rs.next()); + assertFetchedArrayEquals(rs.getArray(1), arr1); + assertTrue(rs.next()); + assertFetchedArrayEquals(rs.getArray(1), arr2); + assertTrue(rs.next()); + assertFetchedArrayEquals(rs.getArray(1), arr3); + assertFalse(rs.next()); + } + try (ResultSet rs = stmt.executeQuery("SELECT col2 from tab1 WHERE col1 = 43")) { + assertTrue(rs.next()); + assertNull(rs.getObject(1)); + assertTrue(rs.wasNull()); + assertFalse(rs.next()); + } + try (ResultSet rs = stmt.executeQuery("SELECT unnest(col2) from tab1 WHERE col1 = 44")) { + assertTrue(rs.next()); + assertNull(rs.getObject(1)); + assertTrue(rs.wasNull()); + assertTrue(rs.next()); + assertFetchedArrayEquals(rs.getArray(1), arr4); + assertFalse(rs.next()); + } + } + } +} diff --git a/src/test/java/org/duckdb/TestAppenderComposite.java b/src/test/java/org/duckdb/TestAppenderComposite.java index dd6d08b69..5fa0aedb6 100644 --- a/src/test/java/org/duckdb/TestAppenderComposite.java +++ b/src/test/java/org/duckdb/TestAppenderComposite.java @@ -1,13 +1,18 @@ package org.duckdb; +import static java.util.Arrays.asList; +import static java.util.Collections.singletonList; import static org.duckdb.TestDuckDBJDBC.JDBC_URL; import static org.duckdb.test.Assertions.*; +import static org.duckdb.test.Helpers.createMap; +import java.math.BigDecimal; +import java.math.BigInteger; import java.sql.DriverManager; import java.sql.ResultSet; import java.sql.SQLException; import java.sql.Statement; -import java.util.Map; +import java.util.*; public class TestAppenderComposite { @@ -474,4 +479,195 @@ public static void test_appender_union_nested() throws Exception { } } } + + private static void assertFetchedStructEquals(Object dbs, Collection struct) throws Exception { + DuckDBStruct dbStruct = (DuckDBStruct) dbs; + Map map = dbStruct.getMap(); + Collection fetched = map.values(); + assertEquals(fetched.size(), struct.size()); + List structList = new ArrayList<>(struct); + int i = 0; + for (Object f : fetched) { + assertEquals(f, structList.get(i)); + i++; + } + } + + public static void test_appender_list_basic_struct() throws Exception { + Collection struct1 = asList(42, "foo"); + Collection struct2 = asList(null, "bar"); + Collection struct3 = asList(43, null); + Collection struct4 = asList(44, "baz"); + try (DuckDBConnection conn = DriverManager.getConnection(JDBC_URL).unwrap(DuckDBConnection.class); + Statement stmt = conn.createStatement()) { + stmt.execute("CREATE TABLE tab1(col1 INT, col2 STRUCT(s1 INT, s2 VARCHAR)[])"); + try (DuckDBAppender appender = conn.createAppender("tab1")) { + appender.beginRow() + .append(42) + .append(asList(struct1, struct2, struct3)) + .endRow() + .beginRow() + .append(43) + .append((List) null) + .endRow() + .beginRow() + .append(44) + .append(asList(null, struct4)) + .endRow() + .flush(); + } + + try (ResultSet rs = stmt.executeQuery("SELECT unnest(col2) from tab1 WHERE col1 = 42")) { + assertTrue(rs.next()); + assertFetchedStructEquals(rs.getObject(1), struct1); + assertTrue(rs.next()); + assertFetchedStructEquals(rs.getObject(1), struct2); + assertTrue(rs.next()); + assertFetchedStructEquals(rs.getObject(1), struct3); + assertFalse(rs.next()); + } + try (ResultSet rs = stmt.executeQuery("SELECT col2 from tab1 WHERE col1 = 43")) { + assertTrue(rs.next()); + assertNull(rs.getObject(1)); + assertTrue(rs.wasNull()); + assertFalse(rs.next()); + } + try (ResultSet rs = stmt.executeQuery("SELECT unnest(col2) from tab1 WHERE col1 = 44")) { + assertTrue(rs.next()); + assertNull(rs.getObject(1)); + assertTrue(rs.wasNull()); + assertTrue(rs.next()); + assertFetchedStructEquals(rs.getObject(1), struct4); + assertFalse(rs.next()); + } + } + } + + public static void test_appender_list_basic_struct_as_map() throws Exception { + LinkedHashMap struct1 = createMap("key1", 42, "key2", "foo"); + LinkedHashMap struct2 = createMap("key1", null, "key2", "bar"); + LinkedHashMap struct3 = createMap("key1", 43, "key2", null); + LinkedHashMap struct4 = createMap("key1", 44, "key2", "baz"); + try (DuckDBConnection conn = DriverManager.getConnection(JDBC_URL).unwrap(DuckDBConnection.class); + Statement stmt = conn.createStatement()) { + stmt.execute("CREATE TABLE tab1(col1 INT, col2 STRUCT(s1 INT, s2 VARCHAR)[])"); + try (DuckDBAppender appender = conn.createAppender("tab1")) { + appender.beginRow() + .append(42) + .append(asList(struct1, struct2, struct3)) + .endRow() + .beginRow() + .append(43) + .append((List) null) + .endRow() + .beginRow() + .append(44) + .append(asList(null, struct4)) + .endRow() + .flush(); + } + + try (ResultSet rs = stmt.executeQuery("SELECT unnest(col2) from tab1 WHERE col1 = 42")) { + assertTrue(rs.next()); + assertFetchedStructEquals(rs.getObject(1), struct1.values()); + assertTrue(rs.next()); + assertFetchedStructEquals(rs.getObject(1), struct2.values()); + assertTrue(rs.next()); + assertFetchedStructEquals(rs.getObject(1), struct3.values()); + assertFalse(rs.next()); + } + try (ResultSet rs = stmt.executeQuery("SELECT col2 from tab1 WHERE col1 = 43")) { + assertTrue(rs.next()); + assertNull(rs.getObject(1)); + assertTrue(rs.wasNull()); + assertFalse(rs.next()); + } + try (ResultSet rs = stmt.executeQuery("SELECT unnest(col2) from tab1 WHERE col1 = 44")) { + assertTrue(rs.next()); + assertNull(rs.getObject(1)); + assertTrue(rs.wasNull()); + assertTrue(rs.next()); + assertFetchedStructEquals(rs.getObject(1), struct4.values()); + assertFalse(rs.next()); + } + } + } + + public static void test_appender_list_basic_struct_with_primitives() throws Exception { + Collection struct1 = asList(true, (byte) 42, (short) 43, 44, 45L, BigInteger.valueOf(46), 47.1F, 48.1D, + BigDecimal.valueOf(49.123)); + try (DuckDBConnection conn = DriverManager.getConnection(JDBC_URL).unwrap(DuckDBConnection.class); + Statement stmt = conn.createStatement()) { + stmt.execute("CREATE TABLE tab1(col1 INT, col2 STRUCT(" + + "s1 BOOL," + + "s2 TINYINT," + + "s3 SMALLINT," + + "s4 INTEGER," + + "s5 BIGINT," + + "s6 HUGEINT," + + "s7 FLOAT," + + "s8 DOUBLE," + + "s9 DECIMAL" + + ")[])"); + try (DuckDBAppender appender = conn.createAppender("tab1")) { + appender.beginRow().append(42).append(singletonList(struct1)).endRow().flush(); + } + + try (ResultSet rs = stmt.executeQuery("SELECT unnest(col2) from tab1 WHERE col1 = 42")) { + assertTrue(rs.next()); + assertFetchedStructEquals(rs.getObject(1), struct1); + assertFalse(rs.next()); + } + } + } + + public static void test_appender_list_basic_union() throws Exception { + Map.Entry union1 = new AbstractMap.SimpleEntry<>("u1", 42); + Map.Entry union2 = new AbstractMap.SimpleEntry<>("u2", "foo"); + Map.Entry union3 = new AbstractMap.SimpleEntry<>("u1", null); + Map.Entry union4 = new AbstractMap.SimpleEntry<>("u2", "bar"); + try (DuckDBConnection conn = DriverManager.getConnection(JDBC_URL).unwrap(DuckDBConnection.class); + Statement stmt = conn.createStatement()) { + stmt.execute("CREATE TABLE tab1(col1 INT, col2 UNION(u1 INT, u2 VARCHAR)[])"); + try (DuckDBAppender appender = conn.createAppender("tab1")) { + appender.beginRow() + .append(42) + .append(asList(union1, union2, union3)) + .endRow() + .beginRow() + .append(43) + .append((List) null) + .endRow() + .beginRow() + .append(44) + .append(asList(null, union4)) + .endRow() + .flush(); + } + + try (ResultSet rs = stmt.executeQuery("SELECT unnest(col2) from tab1 WHERE col1 = 42")) { + assertTrue(rs.next()); + assertEquals(rs.getObject(1), union1.getValue()); + assertTrue(rs.next()); + assertEquals(rs.getObject(1), union2.getValue()); + assertTrue(rs.next()); + assertEquals(rs.getObject(1), union3.getValue()); + assertFalse(rs.next()); + } + try (ResultSet rs = stmt.executeQuery("SELECT col2 from tab1 WHERE col1 = 43")) { + assertTrue(rs.next()); + assertNull(rs.getObject(1)); + assertTrue(rs.wasNull()); + assertFalse(rs.next()); + } + try (ResultSet rs = stmt.executeQuery("SELECT unnest(col2) from tab1 WHERE col1 = 44")) { + assertTrue(rs.next()); + assertNull(rs.getObject(1)); + assertTrue(rs.wasNull()); + assertTrue(rs.next()); + assertEquals(rs.getObject(1), union4.getValue()); + assertFalse(rs.next()); + } + } + } } diff --git a/src/test/java/org/duckdb/TestDuckDBJDBC.java b/src/test/java/org/duckdb/TestDuckDBJDBC.java index b99b2f819..674a90696 100644 --- a/src/test/java/org/duckdb/TestDuckDBJDBC.java +++ b/src/test/java/org/duckdb/TestDuckDBJDBC.java @@ -3156,10 +3156,10 @@ public static void main(String[] args) throws Exception { statusCode = runTests(new String[0], clazz); } else { statusCode = runTests(args, TestDuckDBJDBC.class, TestAppender.class, TestAppenderCollection.class, - TestAppenderComposite.class, TestSingleValueAppender.class, TestBatch.class, - TestBindings.class, TestClosure.class, TestExtensionTypes.class, TestSpatial.class, - TestParameterMetadata.class, TestPrepare.class, TestResults.class, - TestSessionInit.class, TestTimestamp.class); + TestAppenderCollection2D.class, TestAppenderComposite.class, + TestSingleValueAppender.class, TestBatch.class, TestBindings.class, TestClosure.class, + TestExtensionTypes.class, TestSpatial.class, TestParameterMetadata.class, + TestPrepare.class, TestResults.class, TestSessionInit.class, TestTimestamp.class); } System.exit(statusCode); } diff --git a/src/test/java/org/duckdb/TestParameterMetadata.java b/src/test/java/org/duckdb/TestParameterMetadata.java index 62721e48c..bd27e890c 100644 --- a/src/test/java/org/duckdb/TestParameterMetadata.java +++ b/src/test/java/org/duckdb/TestParameterMetadata.java @@ -6,7 +6,7 @@ import java.math.BigDecimal; import java.sql.*; -import java.util.HashMap; +import java.util.LinkedHashMap; public class TestParameterMetadata { @@ -117,7 +117,7 @@ public static void test_parameter_metadata_map() throws Exception { try (PreparedStatement ps = conn.prepareStatement("INSERT INTO metadata_test_map_1 VALUES(?)")) { ParameterMetaData meta = ps.getParameterMetaData(); assertEquals(meta.getParameterTypeName(1), "MAP(INTEGER, DOUBLE)"); - assertEquals(meta.getParameterClassName(1), HashMap.class.getName()); + assertEquals(meta.getParameterClassName(1), LinkedHashMap.class.getName()); assertEquals(meta.getPrecision(1), 0); assertEquals(meta.getScale(1), 0); } diff --git a/src/test/java/org/duckdb/test/Helpers.java b/src/test/java/org/duckdb/test/Helpers.java new file mode 100644 index 000000000..04a804beb --- /dev/null +++ b/src/test/java/org/duckdb/test/Helpers.java @@ -0,0 +1,15 @@ +package org.duckdb.test; + +import java.util.LinkedHashMap; + +public class Helpers { + + @SuppressWarnings("unchecked") + public static LinkedHashMap createMap(Object... entries) { + LinkedHashMap map = new LinkedHashMap<>(); + for (int i = 0; i < entries.length; i += 2) { + map.put((K) entries[i], (V) entries[i + 1]); + } + return map; + } +}