This is an automated email from the ASF dual-hosted git repository.
kevingurney pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/arrow.git
The following commit(s) were added to refs/heads/main by this push:
new da602af5e9 GH-37571: [MATLAB] Add `arrow.tabular.Table` MATLAB class
(#37620)
da602af5e9 is described below
commit da602af5e9a04b7aad63a2f6bb001b45201cb2ef
Author: Kevin Gurney <[email protected]>
AuthorDate: Thu Sep 7 17:10:16 2023 -0400
GH-37571: [MATLAB] Add `arrow.tabular.Table` MATLAB class (#37620)
### Rationale for this change
Following on from #37525, which adds `arrow.array.ChunkedArray` to the
MATLAB interface, this pull request adds support for a new
`arrow.tabular.Table` MATLAB class.
This pull request is intended to be an initial implementation of `Table`
support and does not include all methods or properties that may be useful on
`arrow.tabular.Table`.
### What changes are included in this PR?
1. Added new `arrow.tabular.Table` MATLAB class.
**Properties**
* `NumRows`
* `NumColumns`
* `ColumnNames`
* `Schema`
**Methods**
* `fromArrays(<array-1>, ..., <array-N>)`
* `column(<index>)`
* `table()`
* `toMATLAB()`
**Example of `arrow.tabular.Table.fromArrays(<array_1>, ..., <array-N>)`
static construction method**
```matlab
>> arrowTable = arrow.tabular.Table.fromArrays(arrow.array([1, 2, 3]),
arrow.array(["A", "B", "C"]), arrow.array([true, false, true]))
arrowTable =
Column1: double
Column2: string
Column3: bool
----
Column1:
[
[
1,
2,
3
]
]
Column2:
[
[
"A",
"B",
"C"
]
]
Column3:
[
[
true,
false,
true
]
]
>> matlabTable = table(arrowTable)
matlabTable =
3×3 table
Column1 Column2 Column3
_______ _______ _______
1 "A" true
2 "B" false
3 "C" true
```
2. Added a new `arrow.table(<matlab-table>)` construction function which
creates an `arrow.tabular.Table` from a MATLAB `table`.
**Example of `arrow.table(<matlab-table>)` construction function**
```matlab
>> matlabTable = table([1; 2; 3], ["A"; "B"; "C"], [true; false; true])
matlabTable =
3×3 table
Var1 Var2 Var3
____ ____ _____
1 "A" true
2 "B" false
3 "C" true
>> arrowTable = arrow.table(matlabTable)
arrowTable =
Var1: double
Var2: string
Var3: bool
----
Var1:
[
[
1,
2,
3
]
]
Var2:
[
[
"A",
"B",
"C"
]
]
Var3:
[
[
true,
false,
true
]
]
>> arrowTable.NumRows
ans =
int64
3
>> arrowTable.NumColumns
ans =
int32
3
>> arrowTable.ColumnNames
ans =
1×3 string array
"Var1" "Var2" "Var3"
>> arrowTable.Schema
ans =
Var1: double
Var2: string
Var3: bool
>> table(arrowTable)
ans =
3×3 table
Var1 Var2 Var3
____ ____ _____
1 "A" true
2 "B" false
3 "C" true
>> isequal(ans, matlabTable)
ans =
logical
1
```
### Are these changes tested?
Yes.
1. Added a new `tTable` test class for `arrow.tabular.Table` and
`arrow.table(<matlab-table>)` tests.
### Are there any user-facing changes?
Yes.
1. Users can now create `arrow.tabular.Table` objects using the
`fromArrays` static construction method or the `arrow.table(<matlab-table>)`
construction function.
### Future Directions
1. Create shared test infrastructure for common `RecordBatch` and `Table`
MATLAB tests.
2. Implement equality check (i.e. `isequal`) for `arrow.tabular.Table`
instances.
4. Add more static construction methods to `arrow.tabular.Table`. For
example: `fromChunkedArrays(<chunkedArray-1>, ..., <chunkedArray-N>)` and
`fromRecordBatches(<recordBatch-1>, ..., <recordBatch-N>)`.
### Notes
1. A lot of the code for `arrow.tabular.Table` is very similar to the code
for `arrow.tabular.RecordBatch`. It may make sense for us to try to share more
of the code using C++ templates or another approach.
2. Thank you @ sgilmore10 for your help with this pull request!
* Closes: #37571
Lead-authored-by: Kevin Gurney <[email protected]>
Co-authored-by: Sarah Gilmore <[email protected]>
Signed-off-by: Kevin Gurney <[email protected]>
---
matlab/src/cpp/arrow/matlab/error/error.h | 2 +
matlab/src/cpp/arrow/matlab/proxy/factory.cc | 2 +
matlab/src/cpp/arrow/matlab/tabular/proxy/table.cc | 215 +++++++
matlab/src/cpp/arrow/matlab/tabular/proxy/table.h | 48 ++
matlab/src/matlab/+arrow/+tabular/Table.m | 145 +++++
matlab/src/matlab/+arrow/table.m | 33 ++
matlab/test/arrow/tabular/tTable.m | 622 +++++++++++++++++++++
matlab/tools/cmake/BuildMatlabArrowInterface.cmake | 1 +
8 files changed, 1068 insertions(+)
diff --git a/matlab/src/cpp/arrow/matlab/error/error.h
b/matlab/src/cpp/arrow/matlab/error/error.h
index 4b64849bce..2b3009d51e 100644
--- a/matlab/src/cpp/arrow/matlab/error/error.h
+++ b/matlab/src/cpp/arrow/matlab/error/error.h
@@ -181,6 +181,8 @@ namespace arrow::matlab::error {
static const char* UNKNOWN_PROXY_FOR_ARRAY_TYPE =
"arrow:array:UnknownProxyForArrayType";
static const char* RECORD_BATCH_NUMERIC_INDEX_WITH_EMPTY_RECORD_BATCH =
"arrow:tabular:recordbatch:NumericIndexWithEmptyRecordBatch";
static const char* RECORD_BATCH_INVALID_NUMERIC_COLUMN_INDEX =
"arrow:tabular:recordbatch:InvalidNumericColumnIndex";
+ static const char* TABLE_NUMERIC_INDEX_WITH_EMPTY_TABLE =
"arrow:tabular:table:NumericIndexWithEmptyTable";
+ static const char* TABLE_INVALID_NUMERIC_COLUMN_INDEX =
"arrow:tabular:table:InvalidNumericColumnIndex";
static const char* FAILED_TO_OPEN_FILE_FOR_WRITE =
"arrow:io:FailedToOpenFileForWrite";
static const char* FAILED_TO_OPEN_FILE_FOR_READ =
"arrow:io:FailedToOpenFileForRead";
static const char* FEATHER_FAILED_TO_WRITE_TABLE =
"arrow:io:feather:FailedToWriteTable";
diff --git a/matlab/src/cpp/arrow/matlab/proxy/factory.cc
b/matlab/src/cpp/arrow/matlab/proxy/factory.cc
index 593e8ffbb6..4035725f2b 100644
--- a/matlab/src/cpp/arrow/matlab/proxy/factory.cc
+++ b/matlab/src/cpp/arrow/matlab/proxy/factory.cc
@@ -23,6 +23,7 @@
#include "arrow/matlab/array/proxy/time64_array.h"
#include "arrow/matlab/array/proxy/chunked_array.h"
#include "arrow/matlab/tabular/proxy/record_batch.h"
+#include "arrow/matlab/tabular/proxy/table.h"
#include "arrow/matlab/tabular/proxy/schema.h"
#include "arrow/matlab/error/error.h"
#include "arrow/matlab/type/proxy/primitive_ctype.h"
@@ -60,6 +61,7 @@ libmexclass::proxy::MakeResult Factory::make_proxy(const
ClassName& class_name,
REGISTER_PROXY(arrow.array.proxy.Date64Array ,
arrow::matlab::array::proxy::NumericArray<arrow::Date64Type>);
REGISTER_PROXY(arrow.array.proxy.ChunkedArray ,
arrow::matlab::array::proxy::ChunkedArray);
REGISTER_PROXY(arrow.tabular.proxy.RecordBatch ,
arrow::matlab::tabular::proxy::RecordBatch);
+ REGISTER_PROXY(arrow.tabular.proxy.Table ,
arrow::matlab::tabular::proxy::Table);
REGISTER_PROXY(arrow.tabular.proxy.Schema ,
arrow::matlab::tabular::proxy::Schema);
REGISTER_PROXY(arrow.type.proxy.Field ,
arrow::matlab::type::proxy::Field);
REGISTER_PROXY(arrow.type.proxy.Float32Type ,
arrow::matlab::type::proxy::PrimitiveCType<float>);
diff --git a/matlab/src/cpp/arrow/matlab/tabular/proxy/table.cc
b/matlab/src/cpp/arrow/matlab/tabular/proxy/table.cc
new file mode 100644
index 0000000000..228e28dad9
--- /dev/null
+++ b/matlab/src/cpp/arrow/matlab/tabular/proxy/table.cc
@@ -0,0 +1,215 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements. See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership. The ASF licenses this file
+// to you 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 "libmexclass/proxy/ProxyManager.h"
+
+#include "arrow/matlab/array/proxy/array.h"
+#include "arrow/matlab/array/proxy/chunked_array.h"
+#include "arrow/matlab/array/proxy/wrap.h"
+
+#include "arrow/matlab/error/error.h"
+#include "arrow/matlab/tabular/proxy/table.h"
+#include "arrow/matlab/tabular/proxy/schema.h"
+#include "arrow/type.h"
+#include "arrow/util/utf8.h"
+
+#include "libmexclass/proxy/ProxyManager.h"
+#include "libmexclass/error/Error.h"
+
+namespace arrow::matlab::tabular::proxy {
+
+ namespace {
+ libmexclass::error::Error makeEmptyTableError() {
+ const std::string error_msg = "Numeric indexing using the column
method is not supported for tables with no columns.";
+ return
libmexclass::error::Error{error::TABLE_NUMERIC_INDEX_WITH_EMPTY_TABLE,
error_msg};
+ }
+
+ libmexclass::error::Error makeInvalidNumericIndexError(const int32_t
matlab_index, const int32_t num_columns) {
+ std::stringstream error_message_stream;
+ error_message_stream << "Invalid column index: ";
+ error_message_stream << matlab_index;
+ error_message_stream << ". Column index must be between 1 and the
number of columns (";
+ error_message_stream << num_columns;
+ error_message_stream << ").";
+ return
libmexclass::error::Error{error::TABLE_INVALID_NUMERIC_COLUMN_INDEX,
error_message_stream.str()};
+ }
+ }
+
+ Table::Table(std::shared_ptr<arrow::Table> table) : table{table} {
+ REGISTER_METHOD(Table, toString);
+ REGISTER_METHOD(Table, getNumRows);
+ REGISTER_METHOD(Table, getNumColumns);
+ REGISTER_METHOD(Table, getColumnNames);
+ REGISTER_METHOD(Table, getSchema);
+ REGISTER_METHOD(Table, getColumnByIndex);
+ REGISTER_METHOD(Table, getColumnByName);
+ }
+
+ std::shared_ptr<arrow::Table> Table::unwrap() {
+ return table;
+ }
+
+ void Table::toString(libmexclass::proxy::method::Context& context) {
+ namespace mda = ::matlab::data;
+ MATLAB_ASSIGN_OR_ERROR_WITH_CONTEXT(const auto utf16_string,
arrow::util::UTF8StringToUTF16(table->ToString()), context,
error::UNICODE_CONVERSION_ERROR_ID);
+ mda::ArrayFactory factory;
+ auto str_mda = factory.createScalar(utf16_string);
+ context.outputs[0] = str_mda;
+ }
+
+ libmexclass::proxy::MakeResult Table::make(const
libmexclass::proxy::FunctionArguments& constructor_arguments) {
+ using ArrayProxy = arrow::matlab::array::proxy::Array;
+ using TableProxy = arrow::matlab::tabular::proxy::Table;
+ namespace mda = ::matlab::data;
+ mda::StructArray opts = constructor_arguments[0];
+ const mda::TypedArray<uint64_t> arrow_array_proxy_ids =
opts[0]["ArrayProxyIDs"];
+ const mda::StringArray column_names = opts[0]["ColumnNames"];
+
+ std::vector<std::shared_ptr<arrow::Array>> arrow_arrays;
+ // Retrieve all of the Arrow Array Proxy instances from the
libmexclass ProxyManager.
+ for (const auto& arrow_array_proxy_id : arrow_array_proxy_ids) {
+ auto proxy =
libmexclass::proxy::ProxyManager::getProxy(arrow_array_proxy_id);
+ auto arrow_array_proxy =
std::static_pointer_cast<ArrayProxy>(proxy);
+ auto arrow_array = arrow_array_proxy->unwrap();
+ arrow_arrays.push_back(arrow_array);
+ }
+
+ std::vector<std::shared_ptr<Field>> fields;
+ for (size_t i = 0; i < arrow_arrays.size(); ++i) {
+ const auto type = arrow_arrays[i]->type();
+ const auto column_name_utf16 = std::u16string(column_names[i]);
+ MATLAB_ASSIGN_OR_ERROR(const auto column_name_utf8,
arrow::util::UTF16StringToUTF8(column_name_utf16),
error::UNICODE_CONVERSION_ERROR_ID);
+ fields.push_back(std::make_shared<arrow::Field>(column_name_utf8,
type));
+ }
+
+ arrow::SchemaBuilder schema_builder;
+ MATLAB_ERROR_IF_NOT_OK(schema_builder.AddFields(fields),
error::SCHEMA_BUILDER_ADD_FIELDS_ERROR_ID);
+ MATLAB_ASSIGN_OR_ERROR(const auto schema, schema_builder.Finish(),
error::SCHEMA_BUILDER_FINISH_ERROR_ID);
+ const auto num_rows = arrow_arrays.size() == 0 ? 0 :
arrow_arrays[0]->length();
+ const auto table = arrow::Table::Make(schema, arrow_arrays, num_rows);
+ auto table_proxy = std::make_shared<TableProxy>(table);
+
+ return table_proxy;
+ }
+
+ void Table::getNumRows(libmexclass::proxy::method::Context& context) {
+ namespace mda = ::matlab::data;
+ mda::ArrayFactory factory;
+ const auto num_rows = table->num_rows();
+ auto num_rows_mda = factory.createScalar(num_rows);
+ context.outputs[0] = num_rows_mda;
+ }
+
+ void Table::getNumColumns(libmexclass::proxy::method::Context& context) {
+ namespace mda = ::matlab::data;
+ mda::ArrayFactory factory;
+ const auto num_columns = table->num_columns();
+ auto num_columns_mda = factory.createScalar(num_columns);
+ context.outputs[0] = num_columns_mda;
+ }
+
+ void Table::getColumnNames(libmexclass::proxy::method::Context& context) {
+ namespace mda = ::matlab::data;
+ mda::ArrayFactory factory;
+ const int num_columns = table->num_columns();
+
+ std::vector<mda::MATLABString> column_names;
+ const auto schema = table->schema();
+ const auto field_names = schema->field_names();
+ for (int i = 0; i < num_columns; ++i) {
+ const auto column_name_utf8 = field_names[i];
+ MATLAB_ASSIGN_OR_ERROR_WITH_CONTEXT(auto column_name_utf16,
arrow::util::UTF8StringToUTF16(column_name_utf8), context,
error::UNICODE_CONVERSION_ERROR_ID);
+ const mda::MATLABString matlab_string =
mda::MATLABString(std::move(column_name_utf16));
+ column_names.push_back(matlab_string);
+ }
+ auto column_names_mda = factory.createArray({size_t{1},
static_cast<size_t>(num_columns)}, column_names.begin(), column_names.end());
+ context.outputs[0] = column_names_mda;
+ }
+
+ void Table::getSchema(libmexclass::proxy::method::Context& context) {
+ namespace mda = ::matlab::data;
+ using namespace libmexclass::proxy;
+ using SchemaProxy = arrow::matlab::tabular::proxy::Schema;
+ mda::ArrayFactory factory;
+
+ const auto schema = table->schema();
+ const auto schema_proxy =
std::make_shared<SchemaProxy>(std::move(schema));
+ const auto schema_proxy_id = ProxyManager::manageProxy(schema_proxy);
+ const auto schema_proxy_id_mda = factory.createScalar(schema_proxy_id);
+
+ context.outputs[0] = schema_proxy_id_mda;
+ }
+
+ void Table::getColumnByIndex(libmexclass::proxy::method::Context& context)
{
+ using ChunkedArrayProxy = arrow::matlab::array::proxy::ChunkedArray;
+ namespace mda = ::matlab::data;
+ using namespace libmexclass::proxy;
+ mda::ArrayFactory factory;
+
+ mda::StructArray args = context.inputs[0];
+ const mda::TypedArray<int32_t> index_mda = args[0]["Index"];
+ const auto matlab_index = int32_t(index_mda[0]);
+
+ // Note: MATLAB uses 1-based indexing, so subtract 1.
+ // arrow::Schema::field does not do any bounds checking.
+ const int32_t index = matlab_index - 1;
+ const auto num_columns = table->num_columns();
+
+ if (num_columns == 0) {
+ context.error = makeEmptyTableError();
+ return;
+ }
+
+ if (matlab_index < 1 || matlab_index > num_columns) {
+ context.error = makeInvalidNumericIndexError(matlab_index,
num_columns);
+ return;
+ }
+
+ const auto chunked_array = table->column(index);
+ const auto chunked_array_proxy =
std::make_shared<ChunkedArrayProxy>(chunked_array);
+
+ const auto chunked_array_proxy_id =
ProxyManager::manageProxy(chunked_array_proxy);
+ const auto chunked_array_proxy_id_mda =
factory.createScalar(chunked_array_proxy_id);
+
+ context.outputs[0] = chunked_array_proxy_id_mda;
+ }
+
+ void Table::getColumnByName(libmexclass::proxy::method::Context& context) {
+ using ChunkedArrayProxy = arrow::matlab::array::proxy::ChunkedArray;
+ namespace mda = ::matlab::data;
+ using namespace libmexclass::proxy;
+ mda::ArrayFactory factory;
+
+ mda::StructArray args = context.inputs[0];
+ const mda::StringArray name_mda = args[0]["Name"];
+ const auto name_utf16 = std::u16string(name_mda[0]);
+ MATLAB_ASSIGN_OR_ERROR_WITH_CONTEXT(const auto name,
arrow::util::UTF16StringToUTF8(name_utf16), context,
error::UNICODE_CONVERSION_ERROR_ID);
+
+ const std::vector<std::string> names = {name};
+ const auto& schema = table->schema();
+
MATLAB_ERROR_IF_NOT_OK_WITH_CONTEXT(schema->CanReferenceFieldsByNames(names),
context, error::ARROW_TABULAR_SCHEMA_AMBIGUOUS_FIELD_NAME);
+
+ const auto chunked_array = table->GetColumnByName(name);
+ const auto chunked_array_proxy =
std::make_shared<ChunkedArrayProxy>(chunked_array);
+
+ const auto chunked_array_proxy_id =
ProxyManager::manageProxy(chunked_array_proxy);
+ const auto chunked_array_proxy_id_mda =
factory.createScalar(chunked_array_proxy_id);
+
+ context.outputs[0] = chunked_array_proxy_id_mda;
+ }
+
+}
diff --git a/matlab/src/cpp/arrow/matlab/tabular/proxy/table.h
b/matlab/src/cpp/arrow/matlab/tabular/proxy/table.h
new file mode 100644
index 0000000000..dae86a180b
--- /dev/null
+++ b/matlab/src/cpp/arrow/matlab/tabular/proxy/table.h
@@ -0,0 +1,48 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements. See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership. The ASF licenses this file
+// to you 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.
+
+#pragma once
+
+#include "arrow/table.h"
+
+#include "libmexclass/proxy/Proxy.h"
+
+namespace arrow::matlab::tabular::proxy {
+
+ class Table : public libmexclass::proxy::Proxy {
+ public:
+ Table(std::shared_ptr<arrow::Table> table);
+
+ virtual ~Table() {}
+
+ std::shared_ptr<arrow::Table> unwrap();
+
+ static libmexclass::proxy::MakeResult make(const
libmexclass::proxy::FunctionArguments& constructor_arguments);
+
+ protected:
+ void toString(libmexclass::proxy::method::Context& context);
+ void getNumRows(libmexclass::proxy::method::Context& context);
+ void getNumColumns(libmexclass::proxy::method::Context& context);
+ void getColumnNames(libmexclass::proxy::method::Context& context);
+ void getSchema(libmexclass::proxy::method::Context& context);
+ void getColumnByIndex(libmexclass::proxy::method::Context&
context);
+ void getColumnByName(libmexclass::proxy::method::Context& context);
+
+ std::shared_ptr<arrow::Table> table;
+ };
+
+}
diff --git a/matlab/src/matlab/+arrow/+tabular/Table.m
b/matlab/src/matlab/+arrow/+tabular/Table.m
new file mode 100644
index 0000000000..d9eb4d8409
--- /dev/null
+++ b/matlab/src/matlab/+arrow/+tabular/Table.m
@@ -0,0 +1,145 @@
+%TABLE A tabular data structure representing a set of
+% arrow.array.ChunkedArray objects with a fixed schema.
+
+% Licensed to the Apache Software Foundation (ASF) under one or more
+% contributor license agreements. See the NOTICE file distributed with
+% this work for additional information regarding copyright ownership.
+% The ASF licenses this file to you 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.
+
+classdef Table < matlab.mixin.CustomDisplay & matlab.mixin.Scalar
+
+ properties (Dependent, SetAccess=private, GetAccess=public)
+ NumRows
+ NumColumns
+ ColumnNames
+ Schema
+ end
+
+ properties (Hidden, SetAccess=private, GetAccess=public)
+ Proxy
+ end
+
+ methods
+
+ function obj = Table(proxy)
+ arguments
+ proxy(1, 1) libmexclass.proxy.Proxy {validate(proxy,
"arrow.tabular.proxy.Table")}
+ end
+ import arrow.internal.proxy.validate
+ obj.Proxy = proxy;
+ end
+
+ function numColumns = get.NumColumns(obj)
+ numColumns = obj.Proxy.getNumColumns();
+ end
+
+ function numRows = get.NumRows(obj)
+ numRows = obj.Proxy.getNumRows();
+ end
+
+ function columnNames = get.ColumnNames(obj)
+ columnNames = obj.Proxy.getColumnNames();
+ end
+
+ function schema = get.Schema(obj)
+ proxyID = obj.Proxy.getSchema();
+ proxy = libmexclass.proxy.Proxy(Name="arrow.tabular.proxy.Schema",
ID=proxyID);
+ schema = arrow.tabular.Schema(proxy);
+ end
+
+ function chunkedArray = column(obj, idx)
+ import arrow.internal.validate.*
+
+ idx = index.numericOrString(idx, "int32", AllowNonScalar=false);
+
+ if isnumeric(idx)
+ args = struct(Index=idx);
+ proxyID = obj.Proxy.getColumnByIndex(args);
+ else
+ args = struct(Name=idx);
+ proxyID = obj.Proxy.getColumnByName(args);
+ end
+
+ proxy =
libmexclass.proxy.Proxy(Name="arrow.array.proxy.ChunkedArray", ID=proxyID);
+ chunkedArray = arrow.array.ChunkedArray(proxy);
+ end
+
+ function T = table(obj)
+ import arrow.tabular.internal.*
+
+ numColumns = obj.NumColumns;
+ matlabArrays = cell(1, numColumns);
+
+ for ii = 1:numColumns
+ chunkedArray = obj.column(ii);
+ matlabArrays{ii} = toMATLAB(chunkedArray);
+ end
+
+ validVariableNames = makeValidVariableNames(obj.ColumnNames);
+ validDimensionNames = makeValidDimensionNames(validVariableNames);
+
+ T = table(matlabArrays{:}, ...
+ VariableNames=validVariableNames, ...
+ DimensionNames=validDimensionNames);
+ end
+
+ function T = toMATLAB(obj)
+ T = obj.table();
+ end
+
+ end
+
+ methods (Access = private)
+
+ function str = toString(obj)
+ str = obj.Proxy.toString();
+ end
+
+ end
+
+ methods (Access=protected)
+
+ function displayScalarObject(obj)
+ disp(obj.toString());
+ end
+
+ end
+
+ methods (Static, Access=public)
+
+ function arrowTable = fromArrays(arrowArrays, opts)
+ arguments(Repeating)
+ arrowArrays(1, 1) arrow.array.Array
+ end
+ arguments
+ opts.ColumnNames(1, :) string {mustBeNonmissing} =
compose("Column%d", 1:numel(arrowArrays))
+ end
+
+ import arrow.tabular.internal.validateArrayLengths
+ import arrow.tabular.internal.validateColumnNames
+ import arrow.array.internal.getArrayProxyIDs
+
+ numColumns = numel(arrowArrays);
+ validateArrayLengths(arrowArrays);
+ validateColumnNames(opts.ColumnNames, numColumns);
+
+ arrayProxyIDs = getArrayProxyIDs(arrowArrays);
+ args = struct(ArrayProxyIDs=arrayProxyIDs,
ColumnNames=opts.ColumnNames);
+ proxyName = "arrow.tabular.proxy.Table";
+ proxy = arrow.internal.proxy.create(proxyName, args);
+ arrowTable = arrow.tabular.Table(proxy);
+ end
+
+ end
+
+end
diff --git a/matlab/src/matlab/+arrow/table.m b/matlab/src/matlab/+arrow/table.m
new file mode 100644
index 0000000000..1f54481433
--- /dev/null
+++ b/matlab/src/matlab/+arrow/table.m
@@ -0,0 +1,33 @@
+%TABLE Creates an arrow.tabular.Table from a MATLAB table.
+
+% Licensed to the Apache Software Foundation (ASF) under one or more
+% contributor license agreements. See the NOTICE file distributed with
+% this work for additional information regarding copyright ownership.
+% The ASF licenses this file to you 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.
+function arrowTable = table(matlabTable)
+ arguments
+ % Use istable instead of the table type specifier here to avoid
+ % ambiguous name parsing issue with MATLAB table type and arrow.table.
+ matlabTable {istable} = table.empty(0, 0)
+ end
+
+ arrowArrays = arrow.tabular.internal.decompose(matlabTable);
+ arrayProxyIDs = arrow.array.internal.getArrayProxyIDs(arrowArrays);
+
+ columnNames = string(matlabTable.Properties.VariableNames);
+ args = struct(ArrayProxyIDs=arrayProxyIDs, ColumnNames=columnNames);
+ proxyName = "arrow.tabular.proxy.Table";
+ proxy = arrow.internal.proxy.create(proxyName, args);
+
+ arrowTable = arrow.tabular.Table(proxy);
+end
diff --git a/matlab/test/arrow/tabular/tTable.m
b/matlab/test/arrow/tabular/tTable.m
new file mode 100644
index 0000000000..8c6b9aae73
--- /dev/null
+++ b/matlab/test/arrow/tabular/tTable.m
@@ -0,0 +1,622 @@
+% Tests for the arrow.tabular.Table class and the associated arrow.table
+% construction function.
+
+% Licensed to the Apache Software Foundation (ASF) under one or more
+% contributor license agreements. See the NOTICE file distributed with
+% this work for additional information regarding copyright ownership.
+% The ASF licenses this file to you 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.
+
+classdef tTable < matlab.unittest.TestCase
+
+ methods(Test)
+
+ function Basic(testCase)
+ % Verify that an arrow.tabular.Table can be created
+ % from a MATLAB table using the arrow.table construction
+ % function.
+ matlabTable = table(...
+ [1, 2, 3]', ...
+ ["A", "B", "C"]', ...
+ [true, false, true]' ...
+ );
+ arrowTable = arrow.table(matlabTable);
+ testCase.verifyInstanceOf(arrowTable, "arrow.tabular.Table");
+ end
+
+ function SupportedTypes(testCase)
+ % Verify that a MATLAB table containing all types
+ % supported for conversion to Arrow Arrays can be round-tripped
+ % from an arrow.tabular.Table to a MATLAB table and back.
+ import arrow.internal.test.tabular.createTableWithSupportedTypes
+ import arrow.type.traits.traits
+
+ matlabTable = createTableWithSupportedTypes();
+ arrowTable = arrow.table(matlabTable);
+ expectedColumnNames = string(matlabTable.Properties.VariableNames);
+
+ % For each variable in the input MATLAB table, look up the
+ % corresponding Arrow Type of the corresponding ChunkedArray using
type traits.
+ expectedChunkedArrayTypes = varfun(@(var)
traits(string(class(var))).TypeClassName, ...
+ matlabTable, OutputFormat="uniform");
+ testCase.verifyTable(arrowTable, expectedColumnNames,
expectedChunkedArrayTypes, matlabTable);
+ end
+
+ function ToMATLAB(testCase)
+ % Verify that the toMATLAB method converts
+ % an arrow.tabular.Table to a MATLAB table as expected.
+ expectedMatlabTable = table([1, 2, 3]');
+ arrowTable = arrow.table(expectedMatlabTable);
+ actualMatlabTable = arrowTable.toMATLAB();
+ testCase.verifyEqual(actualMatlabTable, expectedMatlabTable);
+ end
+
+ function Table(testCase)
+ % Verify that the toMATLAB method converts
+ % an arrow.tabular.Table to a MATLAB table as expected.
+ TOriginal = table([1, 2, 3]');
+ arrowRecordBatch = arrow.recordBatch(TOriginal);
+ TConverted = table(arrowRecordBatch);
+ testCase.verifyEqual(TOriginal, TConverted);
+ end
+
+ function NumRows(testCase)
+ % Verify that the NumRows property of arrow.tabular.Table
+ % returns the expected number of rows.
+ numRows = int64([1, 5, 100]);
+
+ for expectedNumRows = numRows
+ matlabTable = array2table(ones(expectedNumRows, 1));
+ arrowTable = arrow.table(matlabTable);
+ testCase.verifyEqual(arrowTable.NumRows, expectedNumRows);
+ end
+
+ end
+
+ function NumColumns(testCase)
+ % Verify that the NumColumns property of arrow.tabular.Table
+ % returns the expected number of columns.
+ numColumns = int32([1, 5, 100]);
+
+ for expectedNumColumns = numColumns
+ matlabTable = array2table(ones(1, expectedNumColumns));
+ arrowTable = arrow.table(matlabTable);
+ testCase.verifyEqual(arrowTable.NumColumns,
expectedNumColumns);
+ end
+
+ end
+
+ function ColumnNames(testCase)
+ % Verify that the ColumnNames property of arrow.tabular.Table
+ % returns the expected string array of column names.
+ columnNames = ["A", "B", "C"];
+ matlabTable = table(1, 2, 3, VariableNames=columnNames);
+ arrowTable = arrow.table(matlabTable);
+ testCase.verifyEqual(arrowTable.ColumnNames, columnNames);
+ end
+
+ function UnicodeColumnNames(testCase)
+ % Verify that an arrow.tabular.Table can be created from
+ % a MATLAB table with Unicode VariableNames.
+ smiley = "😀";
+ tree = "🌲";
+ mango = "🥭";
+ columnNames = [smiley, tree, mango];
+ matlabTable = table(1, 2, 3, VariableNames=columnNames);
+ arrowTable = arrow.table(matlabTable);
+ testCase.verifyEqual(arrowTable.ColumnNames, columnNames);
+ end
+
+ function EmptyTable(testCase)
+ % Verify that an arrow.tabular.Table can be created from an
+ % empty MATLAB table.
+ matlabTable = table.empty(0, 0);
+ arrowTable = arrow.table(matlabTable);
+ testCase.verifyEqual(arrowTable.NumRows, int64(0));
+ testCase.verifyEqual(arrowTable.NumColumns, int32(0));
+ testCase.verifyEqual(arrowTable.ColumnNames, string.empty(1, 0));
+ testCase.verifyEqual(toMATLAB(arrowTable), matlabTable);
+
+ matlabTable = table.empty(1, 0);
+ arrowTable = arrow.table(matlabTable);
+ testCase.verifyEqual(arrowTable.NumRows, int64(0));
+ testCase.verifyEqual(arrowTable.NumColumns, int32(0));
+ testCase.verifyEqual(arrowTable.ColumnNames, string.empty(1, 0));
+
+ matlabTable = table.empty(0, 1);
+ arrowTable = arrow.table(matlabTable);
+ testCase.verifyEqual(arrowTable.NumRows, int64(0));
+ testCase.verifyEqual(arrowTable.NumColumns, int32(1));
+ testCase.verifyEqual(arrowTable.ColumnNames, "Var1");
+ end
+
+ function EmptyTableColumnIndexError(tc)
+ % Verify that an "arrow:tabular:table:NumericIndexWithEmptyTable"
error
+ % is thrown when calling the column method on an empty Table.
+ matlabTable = table();
+ arrowTable = arrow.table(matlabTable);
+ fcn = @() arrowTable.column(1);
+ tc.verifyError(fcn,
"arrow:tabular:table:NumericIndexWithEmptyTable");
+ end
+
+ function InvalidNumericIndexError(tc)
+ % Verify that an "arrow:tabular:table:InvalidNumericColumnIndex"
error
+ % is thrown when providing an index to the column
+ % method that is outside the range of valid column indices
+ % (e.g. greater than the number of columns).
+ matlabTable = table(1, 2, 3);
+ arrowTable = arrow.table(matlabTable);
+ fcn = @() arrowTable.column(4);
+ tc.verifyError(fcn,
"arrow:tabular:table:InvalidNumericColumnIndex");
+ end
+
+ function UnsupportedColumnIndexType(tc)
+ % Verify that an "arrow:badsubscript:UnsupportedIndexType" error
+ % is thrown when providing an index to the column
+ % method that is not a positive scalar integer.
+ matlabTable = table(1, 2, 3);
+ arrowTable = arrow.table(matlabTable);
+ fcn = @() arrowTable.column(datetime(2022, 1, 3));
+ tc.verifyError(fcn, "arrow:badsubscript:UnsupportedIndexType");
+ end
+
+ function ErrorIfIndexIsNonScalar(tc)
+ % Verify that an "arrow:badsubscript:NonScalar" error
+ % is thrown when providing a non-scalar index to the column
+ % method.
+ matlabtable = table(1, 2, 3);
+ arrowTable = arrow.table(matlabtable);
+ fcn = @() arrowTable.column([1 2]);
+ tc.verifyError(fcn, "arrow:badsubscript:NonScalar");
+ end
+
+ function ErrorIfIndexIsNonPositive(tc)
+ % Verify that an "arrow:badsubscript:NonPositive" error
+ % is thrown when providing a non-positive index to the column
+ % method.
+ matlabTable = table(1, 2, 3);
+ arrowTable = arrow.table(matlabTable);
+ fcn = @() arrowTable.column(-1);
+ tc.verifyError(fcn, "arrow:badsubscript:NonPositive");
+ end
+
+ function GetColumnByName(testCase)
+ % Verify that columns can be accessed by name.
+ matlabArray1 = [1; 2; 3];
+ matlabArray2 = ["A"; "B"; "C"];
+ matlabArray3 = [true; false; true];
+
+ arrowArray1 = arrow.array(matlabArray1);
+ arrowArray2 = arrow.array(matlabArray2);
+ arrowArray3 = arrow.array(matlabArray3);
+
+ arrowTable = arrow.tabular.Table.fromArrays(...
+ arrowArray1, ...
+ arrowArray2, ...
+ arrowArray3, ...
+ ColumnNames=["A", "B", "C"] ...
+ );
+
+ column = arrowTable.column("A");
+ expectedNumChunks = int32(1);
+ expectedLength = int64(3);
+ expectedArrowType = arrow.float64();
+ testCase.verifyChunkedArray(column, ...
+ matlabArray1, ...
+ expectedNumChunks, ...
+ expectedLength, ...
+ expectedArrowType);
+
+ column = arrowTable.column("B");
+ expectedNumChunks = int32(1);
+ expectedLength = int64(3);
+ expectedArrowType = arrow.string();
+ testCase.verifyChunkedArray(column, ...
+ matlabArray2, ...
+ expectedNumChunks, ...
+ expectedLength, ...
+ expectedArrowType);
+
+ column = arrowTable.column("C");
+ expectedNumChunks = int32(1);
+ expectedLength = int64(3);
+ expectedArrowType = arrow.boolean();
+ testCase.verifyChunkedArray(column, ...
+ matlabArray3, ...
+ expectedNumChunks, ...
+ expectedLength, ...
+ expectedArrowType);
+ end
+
+ function GetColumnByNameWithEmptyString(testCase)
+ % Verify that a column whose name is the empty string ("")
+ % can be accessed using the column() method.
+ matlabArray1 = [1; 2; 3];
+ matlabArray2 = ["A"; "B"; "C"];
+ matlabArray3 = [true; false; true];
+
+ arrowArray1 = arrow.array(matlabArray1);
+ arrowArray2 = arrow.array(matlabArray2);
+ arrowArray3 = arrow.array(matlabArray3);
+
+ arrowTable = arrow.tabular.Table.fromArrays(...
+ arrowArray1, ...
+ arrowArray2, ...
+ arrowArray3, ...
+ ColumnNames=["A", "", "C"] ...
+ );
+
+ column = arrowTable.column("");
+ expectedNumChunks = int32(1);
+ expectedLength = int64(3);
+ expectedArrowType = arrow.string();
+ testCase.verifyChunkedArray(column, ...
+ matlabArray2, ...
+ expectedNumChunks, ...
+ expectedLength, ...
+ expectedArrowType);
+ end
+
+ function GetColumnByNameWithWhitespace(testCase)
+ % Verify that a column whose name contains only whitespace
+ % characters can be accessed using the column() method.
+ matlabArray1 = [1; 2; 3];
+ matlabArray2 = ["A"; "B"; "C"];
+ matlabArray3 = [true; false; true];
+
+ arrowArray1 = arrow.array(matlabArray1);
+ arrowArray2 = arrow.array(matlabArray2);
+ arrowArray3 = arrow.array(matlabArray3);
+
+ arrowTable = arrow.tabular.Table.fromArrays(...
+ arrowArray1, ...
+ arrowArray2, ...
+ arrowArray3, ...
+ ColumnNames=[" ", " ", " "] ...
+ );
+
+ column = arrowTable.column(" ");
+ expectedNumChunks = int32(1);
+ expectedLength = int64(3);
+ expectedArrowType = arrow.float64();
+ testCase.verifyChunkedArray(column, ...
+ matlabArray1, ...
+ expectedNumChunks, ...
+ expectedLength, ...
+ expectedArrowType);
+
+ column = arrowTable.column(" ");
+ expectedNumChunks = int32(1);
+ expectedLength = int64(3);
+ expectedArrowType = arrow.string();
+ testCase.verifyChunkedArray(column, ...
+ matlabArray2, ...
+ expectedNumChunks, ...
+ expectedLength, ...
+ expectedArrowType);
+
+ column = arrowTable.column(" ");
+ expectedNumChunks = int32(1);
+ expectedLength = int64(3);
+ expectedArrowType = arrow.boolean();
+ testCase.verifyChunkedArray(column, ...
+ matlabArray3, ...
+ expectedNumChunks, ...
+ expectedLength, ...
+ expectedArrowType);
+ end
+
+ function ErrorIfColumnNameDoesNotExist(testCase)
+ % Verify that an error is thrown when trying to access a column
+ % with a name that is not part of the Schema of the Table.
+ matlabArray1 = [1; 2; 3];
+ matlabArray2 = ["A"; "B"; "C"];
+ matlabArray3 = [true; false; true];
+
+ arrowArray1 = arrow.array(matlabArray1);
+ arrowArray2 = arrow.array(matlabArray2);
+ arrowArray3 = arrow.array(matlabArray3);
+
+ arrowTable = arrow.tabular.Table.fromArrays(...
+ arrowArray1, ...
+ arrowArray2, ...
+ arrowArray3, ...
+ ColumnNames=["A", "B", "C"] ...
+ );
+
+ % Matching should be case sensitive.
+ name = "a";
+ testCase.verifyError(@() arrowTable.column(name),
"arrow:tabular:schema:AmbiguousFieldName");
+
+ name = "aA";
+ testCase.verifyError(@() arrowTable.column(name),
"arrow:tabular:schema:AmbiguousFieldName");
+
+ name = "D";
+ testCase.verifyError(@() arrowTable.column(name),
"arrow:tabular:schema:AmbiguousFieldName");
+
+ name = "";
+ testCase.verifyError(@() arrowTable.column(name),
"arrow:tabular:schema:AmbiguousFieldName");
+
+ name = " ";
+ testCase.verifyError(@() arrowTable.column(name),
"arrow:tabular:schema:AmbiguousFieldName");
+ end
+
+ function ErrorIfAmbiguousColumnName(testCase)
+ % Verify that an error is thrown when trying to access a column
+ % with a name that is ambiguous / occurs more than once in the
+ % Schema of the Table.
+ arrowTable = arrow.tabular.Table.fromArrays(...
+ arrow.array([1, 2, 3]), ...
+ arrow.array(["A", "B", "C"]), ...
+ arrow.array([true, false, true]), ...
+ arrow.array([days(1), days(2), days(3)]), ...
+ ColumnNames=["A", "A", "B", "B"] ...
+ );
+
+ name = "A";
+ testCase.verifyError(@() arrowTable.column(name),
"arrow:tabular:schema:AmbiguousFieldName");
+
+ name = "B";
+ testCase.verifyError(@() arrowTable.column(name),
"arrow:tabular:schema:AmbiguousFieldName");
+ end
+
+ function GetColumnByNameWithChar(testCase)
+ % Verify that the column method works when supplied a char
+ % vector as input.
+ matlabArray1 = [1; 2; 3];
+ matlabArray2 = ["A"; "B"; "C"];
+ matlabArray3 = [true; false; true];
+
+ arrowArray1 = arrow.array(matlabArray1);
+ arrowArray2 = arrow.array(matlabArray2);
+ arrowArray3 = arrow.array(matlabArray3);
+
+ arrowTable = arrow.tabular.Table.fromArrays(...
+ arrowArray1, ...
+ arrowArray2, ...
+ arrowArray3, ...
+ ColumnNames=["", "B", "123"] ...
+ );
+
+ % Should match the first column whose name is the
+ % empty string ("").
+ name = char.empty(0, 0);
+ column = arrowTable.column(name);
+ expectedNumChunks = int32(1);
+ expectedLength = int64(3);
+ expectedArrowType = arrow.float64();
+ testCase.verifyChunkedArray(column, ...
+ matlabArray1, ...
+ expectedNumChunks, ...
+ expectedLength, ...
+ expectedArrowType);
+
+ name = char.empty(0, 1);
+ column = arrowTable.column(name);
+ expectedNumChunks = int32(1);
+ expectedLength = int64(3);
+ expectedArrowType = arrow.float64();
+ testCase.verifyChunkedArray(column, ...
+ matlabArray1, ...
+ expectedNumChunks, ...
+ expectedLength, ...
+ expectedArrowType);
+
+ name = char.empty(1, 0);
+ column = arrowTable.column(name);
+ expectedNumChunks = int32(1);
+ expectedLength = int64(3);
+ expectedArrowType = arrow.float64();
+ testCase.verifyChunkedArray(column, ...
+ matlabArray1, ...
+ expectedNumChunks, ...
+ expectedLength, ...
+ expectedArrowType);
+
+ % Should match the second column whose name is "B".
+ name = 'B';
+ column = arrowTable.column(name);
+ expectedNumChunks = int32(1);
+ expectedLength = int64(3);
+ expectedArrowType = arrow.string();
+ testCase.verifyChunkedArray(column, ...
+ matlabArray2, ...
+ expectedNumChunks, ...
+ expectedLength, ...
+ expectedArrowType);
+
+ % Should match the third column whose name is "123".
+ name = '123';
+ column = arrowTable.column(name);
+ expectedNumChunks = int32(1);
+ expectedLength = int64(3);
+ expectedArrowType = arrow.boolean();
+ testCase.verifyChunkedArray(column, ...
+ matlabArray3, ...
+ expectedNumChunks, ...
+ expectedLength, ...
+ expectedArrowType);
+ end
+
+ function ErrorIfColumnNameIsNonScalar(testCase)
+ % Verify that an error is thrown if a nonscalar string array is
+ % specified as a column name to the column method.
+ arrowTable = arrow.tabular.Table.fromArrays(...
+ arrow.array([1, 2, 3]), ...
+ arrow.array(["A", "B", "C"]), ...
+ arrow.array([true, false, true]), ...
+ ColumnNames=["A", "B", "C"] ...
+ );
+
+ name = ["A", "B", "C"];
+ testCase.verifyError(@() arrowTable.column(name),
"arrow:badsubscript:NonScalar");
+
+ name = ["A"; "B"; "C"];
+ testCase.verifyError(@() arrowTable.column(name),
"arrow:badsubscript:NonScalar");
+ end
+
+ function FromArraysWithNoColumnNames(testCase)
+ % Verify arrow.tabular.Table.fromArrays creates the expected
+ % Table when given a comma-separated list of arrow.array.Array
values.
+ import arrow.tabular.Table
+ import arrow.internal.test.tabular.createAllSupportedArrayTypes
+
+ [arrowArrays, matlabData] = createAllSupportedArrayTypes();
+ matlabTable = table(matlabData{:});
+
+ arrowTable = Table.fromArrays(arrowArrays{:});
+ expectedColumnNames = compose("Column%d", 1:width(matlabTable));
+ testCase.verifyEqual(arrowTable.ColumnNames, expectedColumnNames)
+ end
+
+ function FromArraysWithColumnNames(testCase)
+ % Verify arrow.tabular.Table.fromArrays creates the expected
+ % Table when given a comma-separated list of arrow.array.Array
values
+ % and when the ColumnNames nv-pair is provided.
+ import arrow.tabular.Table
+ import arrow.internal.test.tabular.createAllSupportedArrayTypes
+
+ [arrowArrays, ~] = createAllSupportedArrayTypes();
+
+ expectedColumnNames = compose("MyVar%d", 1:numel(arrowArrays));
+ arrowTable = Table.fromArrays(arrowArrays{:},
ColumnNames=expectedColumnNames);
+ testCase.verifyEqual(arrowTable.ColumnNames, expectedColumnNames)
+ end
+
+ function FromArraysUnequalArrayLengthsError(testCase)
+ % Verify arrow.tabular.Table.fromArrays throws an error whose
+ % identifier is "arrow:tabular:UnequalArrayLengths" if the arrays
+ % provided don't all have the same length.
+ import arrow.tabular.Table
+
+ A1 = arrow.array([1, 2]);
+ A2 = arrow.array(["A", "B", "C"]);
+ fcn = @() Table.fromArrays(A1, A2);
+ testCase.verifyError(fcn, "arrow:tabular:UnequalArrayLengths");
+ end
+
+ function FromArraysWrongNumberColumnNamesError(testCase)
+ % Verify arrow.tabular.Table.fromArrays throws an error whose
+ % identifier is "arrow:tabular:WrongNumberColumnNames" if the
+ % ColumnNames provided doesn't have one element per array.
+ import arrow.tabular.Table
+
+ A1 = arrow.array([1, 2]);
+ A2 = arrow.array(["A", "B"]);
+ fcn = @() Table.fromArrays(A1, A2, ColumnNames=["A", "B", "C"]);
+ testCase.verifyError(fcn, "arrow:tabular:WrongNumberColumnNames");
+ end
+
+ function FromArraysColumnNamesHasMissingString(testCase)
+ % Verify arrow.tabular.Table.fromArrays throws an error whose
+ % identifier is "MATLAB:validators:mustBeNonmissing" if the
+ % ColumnNames provided has a missing string value.
+ import arrow.tabular.Table
+
+ A1 = arrow.array([1, 2]);
+ A2 = arrow.array(["A", "B"]);
+ fcn = @() Table.fromArrays(A1, A2, ColumnNames=["A", missing]);
+ testCase.verifyError(fcn, "MATLAB:validators:mustBeNonmissing");
+ end
+
+ function FromArraysNoInputs(testCase)
+ % Verify that an empty Table is returned when calling fromArrays
+ % with no input arguments.
+ arrowTable = arrow.tabular.Table.fromArrays();
+ testCase.verifyEqual(arrowTable.NumRows, int64(0));
+ testCase.verifyEqual(arrowTable.NumColumns, int32(0));
+ testCase.verifyEqual(arrowTable.ColumnNames, string.empty(1, 0));
+ end
+
+ function ConstructionFunctionNoInputs(testCase)
+ % Verify that an empty Table is returned when calling
+ % the arrow.table construction function with no inputs.
+ arrowTable = arrow.table();
+ testCase.verifyEqual(arrowTable.NumRows, int64(0));
+ testCase.verifyEqual(arrowTable.NumColumns, int32(0));
+ testCase.verifyEqual(arrowTable.ColumnNames, string.empty(1, 0));
+ end
+
+ function Schema(testCase)
+ % Verify that the public Schema property returns an approprate
+ % instance of arrow.tabular.Table.
+ matlabTable = table(...
+ ["A"; "B"; "C"], ...
+ [1; 2; 3], ...
+ [true; false; true], ...
+ VariableNames=["A", "B", "C"] ...
+ );
+ arrowTable = arrow.table(matlabTable);
+ schema = arrowTable.Schema;
+ testCase.verifyEqual(schema.NumFields, int32(3));
+ testCase.verifyEqual(schema.field(1).Type.ID,
arrow.type.ID.String);
+ testCase.verifyEqual(schema.field(1).Name, "A");
+ testCase.verifyEqual(schema.field(2).Type.ID,
arrow.type.ID.Float64);
+ testCase.verifyEqual(schema.field(2).Name, "B");
+ testCase.verifyEqual(schema.field(3).Type.ID,
arrow.type.ID.Boolean);
+ testCase.verifyEqual(schema.field(3).Name, "C");
+ end
+
+ function NoColumnsNoSetter(testCase)
+ % Verify that trying to set the value of the public NumColumns
property
+ % results in an error of type "MATLAB:class:SetProhibited".
+ matlabTable = table([1; 2; 3]);
+ arrowTable = arrow.table(matlabTable);
+ testCase.verifyError(@() setfield(arrowTable, "NumColumns",
int32(100)), ...
+ "MATLAB:class:SetProhibited");
+ end
+
+ function SchemaNoSetter(testCase)
+ % Verify that trying to set the value of the public Schema property
+ % results in an error of type "MATLAB:class:SetProhibited".
+ matlabTable = table([1; 2; 3]);
+ arrowTable = arrow.table(matlabTable);
+ testCase.verifyError(@() setfield(arrowTable, "Schema", "Value"),
...
+ "MATLAB:class:SetProhibited");
+ end
+
+ function ColumnNamesNoSetter(testCase)
+ % Verify that trying to set the value of the public ColumnNames
property
+ % results in an error of type "MATLAB:class:SetProhibited".
+ matlabTable = table([1; 2; 3]);
+ arrowTable = arrow.table(matlabTable);
+ testCase.verifyError(@() setfield(arrowTable, "ColumnNames",
"Value"), ...
+ "MATLAB:class:SetProhibited");
+ end
+
+ end
+
+ methods
+
+ function verifyTable(testCase, arrowTable, expectedColumnNames,
expectedArrayClasses, expectedMatlabTable)
+ testCase.verifyEqual(arrowTable.NumColumns,
int32(width(expectedMatlabTable)));
+ testCase.verifyEqual(arrowTable.ColumnNames, expectedColumnNames);
+ matlabTable = table(arrowTable);
+ testCase.verifyEqual(matlabTable, expectedMatlabTable);
+ for ii = 1:arrowTable.NumColumns
+ column = arrowTable.column(ii);
+ testCase.verifyEqual(column.toMATLAB(), expectedMatlabTable{:,
ii});
+ testCase.verifyInstanceOf(column.Type,
expectedArrayClasses(ii));
+ end
+ end
+
+ function verifyChunkedArray(testCase, chunkedArray,
expectedMatlabData, expectedNumChunks, expectedLength, expectedArrowType)
+ testCase.verifyInstanceOf(chunkedArray,
"arrow.array.ChunkedArray");
+ testCase.verifyEqual(toMATLAB(chunkedArray), expectedMatlabData);
+ testCase.verifyEqual(chunkedArray.NumChunks, expectedNumChunks)
+ testCase.verifyEqual(chunkedArray.Length, expectedLength);
+ testCase.verifyEqual(chunkedArray.Type, expectedArrowType);
+ end
+
+ end
+
+end
diff --git a/matlab/tools/cmake/BuildMatlabArrowInterface.cmake
b/matlab/tools/cmake/BuildMatlabArrowInterface.cmake
index 2d95682bc2..a5c0b079b3 100644
--- a/matlab/tools/cmake/BuildMatlabArrowInterface.cmake
+++ b/matlab/tools/cmake/BuildMatlabArrowInterface.cmake
@@ -50,6 +50,7 @@ set(MATLAB_ARROW_LIBMEXCLASS_CLIENT_PROXY_SOURCES
"${CMAKE_SOURCE_DIR}/src/cpp/a
"${CMAKE_SOURCE_DIR}/src/cpp/arrow/matlab/array/proxy/chunked_array.cc"
"${CMAKE_SOURCE_DIR}/src/cpp/arrow/matlab/array/proxy/wrap.cc"
"${CMAKE_SOURCE_DIR}/src/cpp/arrow/matlab/tabular/proxy/record_batch.cc"
+
"${CMAKE_SOURCE_DIR}/src/cpp/arrow/matlab/tabular/proxy/table.cc"
"${CMAKE_SOURCE_DIR}/src/cpp/arrow/matlab/tabular/proxy/schema.cc"
"${CMAKE_SOURCE_DIR}/src/cpp/arrow/matlab/bit/pack.cc"
"${CMAKE_SOURCE_DIR}/src/cpp/arrow/matlab/bit/unpack.cc"