diff --git a/java/src/main/java/ai/rapids/cudf/ContiguousTable.java b/java/src/main/java/ai/rapids/cudf/ContiguousTable.java index 94e44fa9d79..87a3f5f0ddf 100644 --- a/java/src/main/java/ai/rapids/cudf/ContiguousTable.java +++ b/java/src/main/java/ai/rapids/cudf/ContiguousTable.java @@ -1,6 +1,6 @@ /* * - * Copyright (c) 2019-2020, NVIDIA CORPORATION. + * Copyright (c) 2019-2021, NVIDIA CORPORATION. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -18,63 +18,96 @@ package ai.rapids.cudf; -import java.util.Arrays; +import java.nio.ByteBuffer; /** * A table that is backed by a single contiguous device buffer. This makes transfers of the data * much simpler. */ public final class ContiguousTable implements AutoCloseable { - private Table table; + private long metadataHandle = 0; + private Table table = null; private DeviceMemoryBuffer buffer; + private ByteBuffer metadataBuffer = null; + private final long rowCount; - //Will be called from JNI - static ContiguousTable fromContiguousColumnViews(long[] columnViewAddresses, - long address, long lengthInBytes, long rmmBufferAddress) { - Table table = null; - ColumnVector[] vectors = new ColumnVector[columnViewAddresses.length]; - DeviceMemoryBuffer buffer = DeviceMemoryBuffer.fromRmm(address, lengthInBytes, rmmBufferAddress); - try { - for (int i = 0; i < vectors.length; i++) { - vectors[i] = ColumnVector.fromViewWithContiguousAllocation(columnViewAddresses[i], buffer); - } - table = new Table(vectors); - ContiguousTable ret = new ContiguousTable(table, buffer); - buffer = null; - table = null; - return ret; - } finally { - if (buffer != null) { - buffer.close(); - } - - for (int i = 0; i < vectors.length; i++) { - if (vectors[i] != null) { - vectors[i].close(); - } - } - - if (table != null) { - table.close(); - } - } + // This method is invoked by JNI + static ContiguousTable fromPackedTable(long metadataHandle, + long dataAddress, + long dataLength, + long rmmBufferAddress, + long rowCount) { + DeviceMemoryBuffer buffer = DeviceMemoryBuffer.fromRmm(dataAddress, dataLength, rmmBufferAddress); + return new ContiguousTable(metadataHandle, buffer, rowCount); } + /** Construct a contiguous table instance given a table and the device buffer backing it. */ ContiguousTable(Table table, DeviceMemoryBuffer buffer) { + this.metadataHandle = createPackedMetadata(table.getNativeView(), + buffer.getAddress(), buffer.getLength()); this.table = table; this.buffer = buffer; + this.rowCount = table.getRowCount(); } - public Table getTable() { + /** + * Construct a contiguous table + * @param metadataHandle address of the cudf packed_table host-based metadata instance + * @param buffer buffer containing the packed table data + * @param rowCount number of rows in the table + */ + ContiguousTable(long metadataHandle, DeviceMemoryBuffer buffer, long rowCount) { + this.metadataHandle = metadataHandle; + this.buffer = buffer; + this.rowCount = rowCount; + } + + /** + * Returns the number of rows in the table. This accessor avoids manifesting + * the Table instance if only the row count is needed. + */ + public long getRowCount() { + return rowCount; + } + + /** Get the table instance, reconstructing it from the metadata if necessary. */ + public synchronized Table getTable() { + if (table == null) { + table = Table.fromPackedTable(getMetadataDirectBuffer(), buffer); + } return table; } + /** Get the device buffer backing the contiguous table data. */ public DeviceMemoryBuffer getBuffer() { return buffer; } + /** + * Get the byte buffer containing the host metadata describing the schema and layout of the + * contiguous table. + *

+ * NOTE: This is a direct byte buffer that is backed by the underlying native metadata instance + * and therefore is only valid to be used while this contiguous table instance is valid. + * Attempts to cache and access the resulting buffer after this instance has been destroyed + * will result in undefined behavior including the possibility of segmentation faults + * or data corruption. + */ + public ByteBuffer getMetadataDirectBuffer() { + if (metadataBuffer == null) { + metadataBuffer = createMetadataDirectBuffer(metadataHandle); + } + return metadataBuffer.asReadOnlyBuffer(); + } + + /** Close the contiguous table instance and its underlying resources. */ @Override public void close() { + if (metadataHandle != 0) { + closeMetadata(metadataHandle); + metadataHandle = 0; + } + if (table != null) { table.close(); table = null; @@ -85,4 +118,13 @@ public void close() { buffer = null; } } + + // create packed metadata for a table backed by a single data buffer + private static native long createPackedMetadata(long tableView, long dataAddress, long dataSize); + + // create a DirectByteBuffer for the packed table metadata + private static native ByteBuffer createMetadataDirectBuffer(long metadataHandle); + + // release the native metadata resources for a packed table + private static native void closeMetadata(long metadataHandle); } diff --git a/java/src/main/java/ai/rapids/cudf/Table.java b/java/src/main/java/ai/rapids/cudf/Table.java index da4c446d9f7..0637ae6de1e 100644 --- a/java/src/main/java/ai/rapids/cudf/Table.java +++ b/java/src/main/java/ai/rapids/cudf/Table.java @@ -27,6 +27,7 @@ import java.io.File; import java.math.BigDecimal; import java.math.RoundingMode; +import java.nio.ByteBuffer; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; @@ -117,6 +118,11 @@ ColumnVector[] getColumns() { return columns; } + /** Return the native table view handle for this table */ + long getNativeView() { + return nativeHandle; + } + /** * Return the {@link ColumnVector} at the specified index. If you want to keep a reference to * the column around past the life time of the table, you will need to increment the reference @@ -503,7 +509,9 @@ private static native long[] repeatColumnCount(long tableHandle, private static native long[] explode(long tableHandle, int index); - private native long createCudfTableView(long[] nativeColumnViewHandles); + private static native long createCudfTableView(long[] nativeColumnViewHandles); + + private static native long[] columnViewsFromPacked(ByteBuffer metadata, long dataAddress); ///////////////////////////////////////////////////////////////////////////// // TABLE CREATION APIs @@ -1796,6 +1804,50 @@ public static Table convertFromRows(ColumnVector vec, DType ... schema) { return new Table(convertFromRows(vec.getNativeView(), types, scale)); } + /** + * Construct a table from a packed representation. + * @param metadata host-based metadata for the table + * @param data GPU data buffer for the table + * @return table which is zero-copy reconstructed from the packed-form + */ + public static Table fromPackedTable(ByteBuffer metadata, DeviceMemoryBuffer data) { + // Ensure the metadata buffer is direct so it can be passed to JNI + ByteBuffer directBuffer = metadata; + if (!directBuffer.isDirect()) { + directBuffer = ByteBuffer.allocateDirect(metadata.remaining()); + directBuffer.put(metadata); + directBuffer.flip(); + } + + long[] columnViewAddresses = columnViewsFromPacked(directBuffer, data.getAddress()); + ColumnVector[] columns = new ColumnVector[columnViewAddresses.length]; + Table result = null; + try { + for (int i = 0; i < columns.length; i++) { + columns[i] = ColumnVector.fromViewWithContiguousAllocation(columnViewAddresses[i], data); + columnViewAddresses[i] = 0; + } + result = new Table(columns); + } catch (Throwable t) { + for (int i = 0; i < columns.length; i++) { + if (columns[i] != null) { + columns[i].close(); + } + if (columnViewAddresses[i] != 0) { + ColumnView.deleteColumnView(columnViewAddresses[i]); + } + } + throw t; + } + + // close columns to leave the resulting table responsible for freeing underlying columns + for (ColumnVector column : columns) { + column.close(); + } + + return result; + } + ///////////////////////////////////////////////////////////////////////////// // HELPER CLASSES ///////////////////////////////////////////////////////////////////////////// diff --git a/java/src/main/native/CMakeLists.txt b/java/src/main/native/CMakeLists.txt index 6d658b6d80b..614ff155c44 100755 --- a/java/src/main/native/CMakeLists.txt +++ b/java/src/main/native/CMakeLists.txt @@ -308,6 +308,7 @@ set(SOURCE_FILES "src/CudaJni.cpp" "src/ColumnVectorJni.cpp" "src/ColumnViewJni.cpp" + "src/ContiguousTableJni.cpp" "src/HostMemoryBufferNativeUtilsJni.cpp" "src/NvcompJni.cpp" "src/NvtxRangeJni.cpp" diff --git a/java/src/main/native/src/ContiguousTableJni.cpp b/java/src/main/native/src/ContiguousTableJni.cpp new file mode 100644 index 00000000000..352256af450 --- /dev/null +++ b/java/src/main/native/src/ContiguousTableJni.cpp @@ -0,0 +1,122 @@ +/* + * Copyright (c) 2021, NVIDIA CORPORATION. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "cudf_jni_apis.hpp" + +namespace { + +#define CONTIGUOUS_TABLE_CLASS "ai/rapids/cudf/ContiguousTable" +#define CONTIGUOUS_TABLE_FACTORY_SIG(param_sig) "(" param_sig ")L" CONTIGUOUS_TABLE_CLASS ";" + +jclass Contiguous_table_jclass; +jmethodID From_packed_table_method; + +} // anonymous namespace + +namespace cudf { +namespace jni { + +bool cache_contiguous_table_jni(JNIEnv *env) { + jclass cls = env->FindClass(CONTIGUOUS_TABLE_CLASS); + if (cls == nullptr) { + return false; + } + + From_packed_table_method = + env->GetStaticMethodID(cls, "fromPackedTable", CONTIGUOUS_TABLE_FACTORY_SIG("JJJJJ")); + if (From_packed_table_method == nullptr) { + return false; + } + + // Convert local reference to global so it cannot be garbage collected. + Contiguous_table_jclass = static_cast(env->NewGlobalRef(cls)); + if (Contiguous_table_jclass == nullptr) { + return false; + } + return true; +} + +void release_contiguous_table_jni(JNIEnv *env) { + if (Contiguous_table_jclass != nullptr) { + env->DeleteGlobalRef(Contiguous_table_jclass); + Contiguous_table_jclass = nullptr; + } +} + +jobject contiguous_table_from(JNIEnv *env, cudf::packed_columns &split, long row_count) { + jlong metadata_address = reinterpret_cast(split.metadata_.get()); + jlong data_address = reinterpret_cast(split.gpu_data->data()); + jlong data_size = static_cast(split.gpu_data->size()); + jlong rmm_buffer_address = reinterpret_cast(split.gpu_data.get()); + + jobject contig_table_obj = env->CallStaticObjectMethod( + Contiguous_table_jclass, From_packed_table_method, metadata_address, data_address, data_size, + rmm_buffer_address, row_count); + + if (contig_table_obj != nullptr) { + split.metadata_.release(); + split.gpu_data.release(); + } + + return contig_table_obj; +} + +native_jobjectArray contiguous_table_array(JNIEnv *env, jsize length) { + return native_jobjectArray( + env, env->NewObjectArray(length, Contiguous_table_jclass, nullptr)); +} + +} // namespace jni +} // namespace cudf + +extern "C" { + +JNIEXPORT jlong JNICALL Java_ai_rapids_cudf_ContiguousTable_createPackedMetadata( + JNIEnv *env, jclass, jlong j_table, jlong j_buffer_addr, jlong j_buffer_length) { + JNI_NULL_CHECK(env, j_table, "input table is null", 0); + try { + cudf::jni::auto_set_device(env); + auto table = reinterpret_cast(j_table); + auto data_addr = reinterpret_cast(j_buffer_addr); + auto data_size = static_cast(j_buffer_length); + auto metadata_ptr = + new cudf::packed_columns::metadata(cudf::pack_metadata(*table, data_addr, data_size)); + return reinterpret_cast(metadata_ptr); + } + CATCH_STD(env, 0); +} + +JNIEXPORT jobject JNICALL Java_ai_rapids_cudf_ContiguousTable_createMetadataDirectBuffer( + JNIEnv *env, jclass, jlong j_metadata_ptr) { + JNI_NULL_CHECK(env, j_metadata_ptr, "metadata is null", nullptr); + try { + auto metadata = reinterpret_cast(j_metadata_ptr); + return env->NewDirectByteBuffer(const_cast(metadata->data()), metadata->size()); + } + CATCH_STD(env, nullptr); +} + +JNIEXPORT void JNICALL Java_ai_rapids_cudf_ContiguousTable_closeMetadata(JNIEnv *env, jclass, + jlong j_metadata_ptr) { + JNI_NULL_CHECK(env, j_metadata_ptr, "metadata is null", ); + try { + auto metadata = reinterpret_cast(j_metadata_ptr); + delete metadata; + } + CATCH_STD(env, ); +} + +} // extern "C" diff --git a/java/src/main/native/src/CudfJni.cpp b/java/src/main/native/src/CudfJni.cpp index 0c560833bb1..928e167c4da 100644 --- a/java/src/main/native/src/CudfJni.cpp +++ b/java/src/main/native/src/CudfJni.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019-2020, NVIDIA CORPORATION. + * Copyright (c) 2019-2021, NVIDIA CORPORATION. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -19,7 +19,7 @@ #include #include -#include "jni_utils.hpp" +#include "cudf_jni_apis.hpp" namespace { @@ -45,74 +45,6 @@ constexpr bool is_ptds_enabled{true}; constexpr bool is_ptds_enabled{false}; #endif -static jclass Contiguous_table_jclass; -static jmethodID From_contiguous_column_views; - -#define CONTIGUOUS_TABLE_CLASS "ai/rapids/cudf/ContiguousTable" -#define CONTIGUOUS_TABLE_FACTORY_SIG(param_sig) "(" param_sig ")L" CONTIGUOUS_TABLE_CLASS ";" - -static bool cache_contiguous_table_jni(JNIEnv *env) { - jclass cls = env->FindClass(CONTIGUOUS_TABLE_CLASS); - if (cls == nullptr) { - return false; - } - - From_contiguous_column_views = env->GetStaticMethodID(cls, "fromContiguousColumnViews", - CONTIGUOUS_TABLE_FACTORY_SIG("[JJJJ")); - if (From_contiguous_column_views == nullptr) { - return false; - } - - // Convert local reference to global so it cannot be garbage collected. - Contiguous_table_jclass = static_cast(env->NewGlobalRef(cls)); - if (Contiguous_table_jclass == nullptr) { - return false; - } - return true; -} - -static void release_contiguous_table_jni(JNIEnv *env) { - if (Contiguous_table_jclass != nullptr) { - env->DeleteGlobalRef(Contiguous_table_jclass); - Contiguous_table_jclass = nullptr; - } -} - -jobject contiguous_table_from(JNIEnv *env, cudf::contiguous_split_result &split) { - jlong address = reinterpret_cast(split.all_data->data()); - jlong size = static_cast(split.all_data->size()); - jlong buff_address = reinterpret_cast(split.all_data.get()); - int num_columns = split.table.num_columns(); - cudf::jni::native_jlongArray views(env, num_columns); - for (int i = 0; i < num_columns; i++) { - // TODO Exception handling is not ideal, if no exceptions are thrown ownership of the new cv - // is passed to java. If an exception is thrown we need to free it, but this needs to be - // coordinated with the java side because one column may have changed ownership while - // another may not have. We don't want to double free the view so for now we just let it - // leak because it should be a small amount of host memory. - // - // In the ideal case we would keep the view where it is at, and pass in a pointer to it - // That pointer would then be copied when java takes ownership of it, but that adds an - // extra JNI call that I would like to avoid for performance reasons. - cudf::column_view *cv = new cudf::column_view(split.table.column(i)); - views[i] = reinterpret_cast(cv); - } - - views.commit(); - jobject ret = env->CallStaticObjectMethod(Contiguous_table_jclass, From_contiguous_column_views, - views.get_jArray(), address, size, buff_address); - - if (ret != nullptr) { - split.all_data.release(); - } - return ret; -} - -native_jobjectArray contiguous_table_array(JNIEnv *env, jsize length) { - return native_jobjectArray( - env, env->NewObjectArray(length, Contiguous_table_jclass, nullptr)); -} - static jclass Host_memory_buffer_jclass; static jmethodID Host_buffer_allocate; static jfieldID Host_buffer_address; diff --git a/java/src/main/native/src/TableJni.cpp b/java/src/main/native/src/TableJni.cpp index 20afe12baf9..30222452804 100644 --- a/java/src/main/native/src/TableJni.cpp +++ b/java/src/main/native/src/TableJni.cpp @@ -606,6 +606,38 @@ JNIEXPORT void JNICALL Java_ai_rapids_cudf_Table_deleteCudfTable(JNIEnv *env, jc CATCH_STD(env, ); } +JNIEXPORT jlongArray JNICALL Java_ai_rapids_cudf_Table_columnViewsFromPacked(JNIEnv *env, jclass, + jobject buffer_obj, + jlong j_data_address) { + // The GPU data address can be null when the table is empty, so it is not null-checked here. + JNI_NULL_CHECK(env, buffer_obj, "metadata is null", nullptr); + try { + cudf::jni::auto_set_device(env); + void const *metadata_address = env->GetDirectBufferAddress(buffer_obj); + JNI_NULL_CHECK(env, metadata_address, "metadata buffer address is null", nullptr); + cudf::table_view table = cudf::unpack(static_cast(metadata_address), + reinterpret_cast(j_data_address)); + cudf::jni::native_jlongArray views(env, table.num_columns()); + for (int i = 0; i < table.num_columns(); i++) { + // TODO Exception handling is not ideal, if no exceptions are thrown ownership of the new cv + // is passed to Java. If an exception is thrown we need to free it, but this needs to be + // coordinated with the Java side because one column may have changed ownership while + // another may not have. We don't want to double free the view so for now we just let it + // leak because it should be a small amount of host memory. + // + // In the ideal case we would keep the view where it is at, and pass in a pointer to it + // That pointer would then be copied when Java takes ownership of it, but that adds an + // extra JNI call that I would like to avoid for performance reasons. + cudf::column_view *cv = new cudf::column_view(table.column(i)); + views[i] = reinterpret_cast(cv); + } + views.commit(); + + return views.get_jArray(); + } + CATCH_STD(env, nullptr); +} + JNIEXPORT jlongArray JNICALL Java_ai_rapids_cudf_Table_orderBy(JNIEnv *env, jclass j_class_object, jlong j_input_table, jlongArray j_sort_keys_columns, @@ -1817,11 +1849,12 @@ JNIEXPORT jobjectArray JNICALL Java_ai_rapids_cudf_Table_contiguousSplit(JNIEnv std::vector indices(n_split_indices.data(), n_split_indices.data() + n_split_indices.size()); - std::vector result = cudf::contiguous_split(*n_table, indices); + std::vector result = cudf::contiguous_split(*n_table, indices); cudf::jni::native_jobjectArray n_result = cudf::jni::contiguous_table_array(env, result.size()); for (int i = 0; i < result.size(); i++) { - n_result.set(i, cudf::jni::contiguous_table_from(env, result[i])); + n_result.set(i, cudf::jni::contiguous_table_from(env, result[i].data, + result[i].table.num_rows())); } return n_result.wrapped(); } diff --git a/java/src/main/native/src/cudf_jni_apis.hpp b/java/src/main/native/src/cudf_jni_apis.hpp index a7d955b2bbf..76c7e91d335 100644 --- a/java/src/main/native/src/cudf_jni_apis.hpp +++ b/java/src/main/native/src/cudf_jni_apis.hpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019-2020, NVIDIA CORPORATION. + * Copyright (c) 2019-2021, NVIDIA CORPORATION. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -23,12 +23,23 @@ namespace cudf { namespace jni { +jlongArray convert_table_for_return(JNIEnv *env, std::unique_ptr &table_result); + +// +// ContiguousTable APIs +// + +bool cache_contiguous_table_jni(JNIEnv *env); -jobject contiguous_table_from(JNIEnv *env, cudf::contiguous_split_result &split); +void release_contiguous_table_jni(JNIEnv *env); + +jobject contiguous_table_from(JNIEnv *env, cudf::packed_columns &split, long row_count); native_jobjectArray contiguous_table_array(JNIEnv *env, jsize length); -jlongArray convert_table_for_return(JNIEnv *env, std::unique_ptr &table_result); +// +// HostMemoryBuffer APIs +// /** * Allocate a HostMemoryBuffer diff --git a/java/src/test/java/ai/rapids/cudf/ColumnVectorTest.java b/java/src/test/java/ai/rapids/cudf/ColumnVectorTest.java index 7806bd1797b..cb1f792b99e 100644 --- a/java/src/test/java/ai/rapids/cudf/ColumnVectorTest.java +++ b/java/src/test/java/ai/rapids/cudf/ColumnVectorTest.java @@ -3737,6 +3737,12 @@ void testConcatListsOfLists() { void testContiguousSplitConstructor() { try (Table tmp = new Table.TestBuilder().column(1, 2).column(3, 4).build(); ContiguousTable ct = tmp.contiguousSplit()[0]) { + // table should not be referencing the device buffer yet + assertEquals(1, ct.getBuffer().getRefCount()); + + // get the table to force it to be instantiated + Table ignored = ct.getTable(); + // one reference for the device buffer itself, two more for the column using it assertEquals(3, ct.getBuffer().getRefCount()); } diff --git a/java/src/test/java/ai/rapids/cudf/TableTest.java b/java/src/test/java/ai/rapids/cudf/TableTest.java index 35be427d0c8..bb9e5e40cb9 100644 --- a/java/src/test/java/ai/rapids/cudf/TableTest.java +++ b/java/src/test/java/ai/rapids/cudf/TableTest.java @@ -35,6 +35,7 @@ import java.io.IOException; import java.math.BigDecimal; import java.math.RoundingMode; +import java.nio.ByteBuffer; import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.util.ArrayList; @@ -1672,9 +1673,13 @@ void testContiguousSplit() { .build()) { splits = t1.contiguousSplit(2, 5, 9); assertEquals(4, splits.length); + assertEquals(2, splits[0].getRowCount()); assertEquals(2, splits[0].getTable().getRowCount()); + assertEquals(3, splits[1].getRowCount()); assertEquals(3, splits[1].getTable().getRowCount()); + assertEquals(4, splits[2].getRowCount()); assertEquals(4, splits[2].getTable().getRowCount()); + assertEquals(1, splits[3].getRowCount()); assertEquals(1, splits[3].getTable().getRowCount()); } finally { if (splits != null) { @@ -1697,9 +1702,13 @@ void testContiguousSplitWithStrings() { .build()) { splits = t1.contiguousSplit(2, 5, 9); assertEquals(4, splits.length); + assertEquals(2, splits[0].getRowCount()); assertEquals(2, splits[0].getTable().getRowCount()); + assertEquals(3, splits[1].getRowCount()); assertEquals(3, splits[1].getTable().getRowCount()); + assertEquals(4, splits[2].getRowCount()); assertEquals(4, splits[2].getTable().getRowCount()); + assertEquals(1, splits[3].getRowCount()); assertEquals(1, splits[3].getTable().getRowCount()); } finally { if (splits != null) { @@ -2164,6 +2173,25 @@ void testSerializationRoundTripSliced() throws IOException { } } + @Test + void testSerializationReconstructFromMetadata() throws IOException { + try (Table t = buildTestTable()) { + ByteArrayOutputStream bout = new ByteArrayOutputStream(); + JCudfSerialization.writeToStream(t, bout, 0, t.getRowCount()); + ByteArrayInputStream bin = new ByteArrayInputStream(bout.toByteArray()); + try (JCudfSerialization.TableAndRowCountPair trcp = JCudfSerialization.readTableFrom(bin)) { + ContiguousTable contigTable = trcp.getContiguousTable(); + DeviceMemoryBuffer oldbuf = contigTable.getBuffer(); + try (DeviceMemoryBuffer newbuf = oldbuf.sliceWithCopy(0, oldbuf.getLength())) { + ByteBuffer metadata = contigTable.getMetadataDirectBuffer(); + try (Table newTable = Table.fromPackedTable(metadata, newbuf)) { + assertTablesAreEqual(t, newTable); + } + } + } + } + } + @Test void testValidityFill() { byte[] buff = new byte[2];