You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@arrow.apache.org by ke...@apache.org on 2023/09/15 18:40:27 UTC

[arrow] branch main updated: GH-37654: [MATLAB] Add `Fields` property to `arrow.type.Type` MATLAB class (#37725)

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 e32e87529e GH-37654: [MATLAB] Add `Fields` property to `arrow.type.Type` MATLAB class (#37725)
e32e87529e is described below

commit e32e87529e0810572821b0e11afbe1562f1e7edd
Author: sgilmore10 <74...@users.noreply.github.com>
AuthorDate: Fri Sep 15 14:40:20 2023 -0400

    GH-37654: [MATLAB] Add `Fields` property to `arrow.type.Type` MATLAB class (#37725)
    
    ### Rationale for this change
    
    In order to implement `arrow.array.StructType`, we need to add a property called `Fields` to `arrow.type.Type`. This property will be a N-by-1 `arrow.type.Field` array. Adding `Fields` will let users inspect the `Type`s contained by a `StructType` object.
    
    ### What changes are included in this PR?
    
    1. Added `Fields` as a property to `arrow.type.Type`. `Fields` is a 1xN `arrow.type.Field` array, where `N` is the number of fields.
    
    2. Added method `field(idx)` to `arrow.type.Type`. This method accepts a numeric index and returns the `arrow.type.Field` stored at the specified index.
    
    ### Are these changes tested?
    
    1. Yes, updated `hFixedWidthType.m` and `tStringType.m` to verify the behavior of the new property and method.
    2. Currently, all of the concrete `arrow.type.Type`s do not have any fields. This means the `Fields` property is always a 0x0 `arrow.type.Field` array. Once we implement `StructType`, we will be able to test having a nonempty `Fields` property.
    
    ### Are there any user-facing changes?
    
    Yes, users can now extract fields from an `arrow.type.Type` object.
    
    ### Future Directions
    
    1. #37724
    2. #37653
    * Closes: #37654
    
    Authored-by: Sarah Gilmore <sg...@mathworks.com>
    Signed-off-by: Kevin Gurney <kg...@mathworks.com>
---
 matlab/src/cpp/arrow/matlab/error/error.h          |  8 +--
 matlab/src/cpp/arrow/matlab/index/validate.cc      | 56 ++++++++++++++++++
 .../matlab/{type/proxy/type.h => index/validate.h} | 29 ++-------
 .../src/cpp/arrow/matlab/tabular/proxy/schema.cc   | 68 ++++++----------------
 matlab/src/cpp/arrow/matlab/type/proxy/type.cc     | 35 +++++++++++
 matlab/src/cpp/arrow/matlab/type/proxy/type.h      |  2 +
 matlab/src/matlab/+arrow/+type/Type.m              | 24 ++++++++
 matlab/test/arrow/tabular/tSchema.m                | 10 ++--
 matlab/test/arrow/type/hFixedWidthType.m           | 25 ++++++++
 matlab/test/arrow/type/tStringType.m               | 25 ++++++++
 matlab/tools/cmake/BuildMatlabArrowInterface.cmake |  4 +-
 11 files changed, 202 insertions(+), 84 deletions(-)

diff --git a/matlab/src/cpp/arrow/matlab/error/error.h b/matlab/src/cpp/arrow/matlab/error/error.h
index 2b3009d51e..4ff77da8d8 100644
--- a/matlab/src/cpp/arrow/matlab/error/error.h
+++ b/matlab/src/cpp/arrow/matlab/error/error.h
@@ -174,10 +174,7 @@ namespace arrow::matlab::error {
     static const char* INVALID_TIME_UNIT = "arrow:type:InvalidTimeUnit";
     static const char* FIELD_FAILED_TO_CREATE_TYPE_PROXY = "arrow:field:FailedToCreateTypeProxy";
     static const char* ARRAY_FAILED_TO_CREATE_TYPE_PROXY = "arrow:array:FailedToCreateTypeProxy";
-    static const char* ARROW_TABULAR_SCHEMA_INVALID_NUMERIC_FIELD_INDEX = "arrow:tabular:schema:InvalidNumericFieldIndex";
-    static const char* ARROW_TABULAR_SCHEMA_UNKNOWN_FIELD_NAME = "arrow:tabular:schema:UnknownFieldName";
     static const char* ARROW_TABULAR_SCHEMA_AMBIGUOUS_FIELD_NAME = "arrow:tabular:schema:AmbiguousFieldName";
-    static const char* ARROW_TABULAR_SCHEMA_NUMERIC_FIELD_INDEX_WITH_EMPTY_SCHEMA = "arrow:tabular:schema:NumericFieldIndexWithEmptySchema";
     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";
@@ -195,6 +192,7 @@ namespace arrow::matlab::error {
     static const char* CHUNKED_ARRAY_MAKE_FAILED = "arrow:chunkedarray:MakeFailed";
     static const char* CHUNKED_ARRAY_NUMERIC_INDEX_WITH_EMPTY_CHUNKED_ARRAY = "arrow:chunkedarray:NumericIndexWithEmptyChunkedArray";
     static const char* CHUNKED_ARRAY_INVALID_NUMERIC_CHUNK_INDEX = "arrow:chunkedarray:InvalidNumericChunkIndex";
-
-
+    
+    static const char* INDEX_EMPTY_CONTAINER = "arrow:index:EmptyContainer";
+    static const char* INDEX_OUT_OF_RANGE = "arrow:index:OutOfRange";
 }
diff --git a/matlab/src/cpp/arrow/matlab/index/validate.cc b/matlab/src/cpp/arrow/matlab/index/validate.cc
new file mode 100644
index 0000000000..b24653f1b8
--- /dev/null
+++ b/matlab/src/cpp/arrow/matlab/index/validate.cc
@@ -0,0 +1,56 @@
+// 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 "arrow/matlab/index/validate.h"
+
+#include <sstream>
+
+namespace arrow::matlab::index {
+
+    namespace {
+        std::string makeEmptyContainerErrorMessage() {
+            return "Numeric indexing using the field method is not supported for objects with zero fields.";
+        }
+
+        std::string makeIndexOutOfRangeErrorMessage(const int32_t matlab_index, const int32_t num_fields) {
+            std::stringstream error_message_stream;
+            error_message_stream << "Invalid field index: ";
+            // matlab uses 1-based indexing
+            error_message_stream << matlab_index;
+            error_message_stream << ". Field index must be between 1 and the number of fields (";
+            error_message_stream << num_fields;
+            error_message_stream << ").";
+            return error_message_stream.str();
+        }
+    } // anonymous namespace 
+
+    arrow::Status validateNonEmptyContainer(const int32_t num_fields) {
+        if (num_fields == 0) {
+            const auto msg = makeEmptyContainerErrorMessage();
+            return arrow::Status::Invalid(std::move(msg));
+        }
+        return arrow::Status::OK();
+    }
+
+    arrow::Status validateInRange(const int32_t matlab_index, const int32_t num_fields) {
+        if (matlab_index < 1 || matlab_index > num_fields) {
+            const auto msg = makeIndexOutOfRangeErrorMessage(matlab_index, num_fields);
+            return arrow::Status::Invalid(std::move(msg));
+        }
+        return arrow::Status::OK();
+    }
+}
\ No newline at end of file
diff --git a/matlab/src/cpp/arrow/matlab/type/proxy/type.h b/matlab/src/cpp/arrow/matlab/index/validate.h
similarity index 58%
copy from matlab/src/cpp/arrow/matlab/type/proxy/type.h
copy to matlab/src/cpp/arrow/matlab/index/validate.h
index efd2b8255a..40e109c19e 100644
--- a/matlab/src/cpp/arrow/matlab/type/proxy/type.h
+++ b/matlab/src/cpp/arrow/matlab/index/validate.h
@@ -17,29 +17,10 @@
 
 #pragma once
 
-#include "arrow/type.h"
+#include "arrow/status.h"
 
-#include "libmexclass/proxy/Proxy.h"
+namespace arrow::matlab::index {
 
-namespace arrow::matlab::type::proxy {
-
-class Type : public libmexclass::proxy::Proxy {
-    public:
-        Type(std::shared_ptr<arrow::DataType> type);
-    
-        virtual ~Type() {}
-
-        std::shared_ptr<arrow::DataType> unwrap();
-
-    protected:
-
-        void getTypeID(libmexclass::proxy::method::Context& context);
-
-        void getNumFields(libmexclass::proxy::method::Context& context);
-
-        void isEqual(libmexclass::proxy::method::Context& context);
-
-        std::shared_ptr<arrow::DataType> data_type;
-};
-
-}
+    arrow::Status validateNonEmptyContainer(const int32_t num_fields);
+    arrow::Status validateInRange(const int32_t matlab_index, const int32_t num_fields);
+}
\ No newline at end of file
diff --git a/matlab/src/cpp/arrow/matlab/tabular/proxy/schema.cc b/matlab/src/cpp/arrow/matlab/tabular/proxy/schema.cc
index 62fe863ca8..ec1ac1eecb 100644
--- a/matlab/src/cpp/arrow/matlab/tabular/proxy/schema.cc
+++ b/matlab/src/cpp/arrow/matlab/tabular/proxy/schema.cc
@@ -18,6 +18,7 @@
 #include "arrow/matlab/error/error.h"
 #include "arrow/matlab/tabular/proxy/schema.h"
 #include "arrow/matlab/type/proxy/field.h"
+#include "arrow/matlab/index/validate.h"
 
 #include "libmexclass/proxy/ProxyManager.h"
 #include "libmexclass/error/Error.h"
@@ -28,25 +29,6 @@
 
 namespace arrow::matlab::tabular::proxy {
 
-    namespace {
-
-        libmexclass::error::Error makeUnknownFieldNameError(const std::string& name) {
-            using namespace libmexclass::error;
-            std::stringstream error_message_stream;
-            error_message_stream << "Unknown field name: '";
-            error_message_stream << name;
-            error_message_stream << "'.";
-            return Error{error::ARROW_TABULAR_SCHEMA_UNKNOWN_FIELD_NAME, error_message_stream.str()};
-        }
-
-        libmexclass::error::Error makeEmptySchemaError() {
-            using namespace libmexclass::error;
-            return Error{error::ARROW_TABULAR_SCHEMA_NUMERIC_FIELD_INDEX_WITH_EMPTY_SCHEMA,
-                         "Numeric indexing using the field method is not supported for schemas with no fields."};
-        }
-
-    }
-
     Schema::Schema(std::shared_ptr<arrow::Schema> schema) : schema{std::move(schema)} {
         REGISTER_METHOD(Schema, getFieldByIndex);
         REGISTER_METHOD(Schema, getFieldByName);
@@ -86,37 +68,27 @@ namespace arrow::matlab::tabular::proxy {
         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_fields = schema->num_fields();
 
-        if (num_fields == 0) {
-            const auto& error = makeEmptySchemaError();
-            context.error = error;
-            return;
-        }
+        // Validate there is at least 1 field
+        MATLAB_ERROR_IF_NOT_OK_WITH_CONTEXT(
+            index::validateNonEmptyContainer(schema->num_fields()),
+            context,
+            error::INDEX_EMPTY_CONTAINER);
 
-        if (matlab_index < 1 || matlab_index > num_fields) {
-            using namespace libmexclass::error;
-            const std::string& error_message_id = std::string{error::ARROW_TABULAR_SCHEMA_INVALID_NUMERIC_FIELD_INDEX};
-            std::stringstream error_message_stream;
-            error_message_stream << "Invalid field index: ";
-            error_message_stream << matlab_index;
-            error_message_stream << ". Field index must be between 1 and the number of fields (";
-            error_message_stream << num_fields;
-            error_message_stream << ").";
-            const std::string& error_message = error_message_stream.str();
-            context.error = Error{error_message_id, error_message}; 
-            return;
-        }
+        // Validate the matlab index provided is within the range [1, num_fields]
+        MATLAB_ERROR_IF_NOT_OK_WITH_CONTEXT(
+            index::validateInRange(matlab_index, schema->num_fields()),
+            context,
+            error::INDEX_OUT_OF_RANGE);
 
-        const auto& field = schema->field(index);
-        auto field_proxy = std::make_shared<FieldProxy>(field);
-        const auto field_proxy_id = ProxyManager::manageProxy(field_proxy);
-        const auto field_proxy_id_mda = factory.createScalar(field_proxy_id);
+        // 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;
 
-        context.outputs[0] = field_proxy_id_mda;
+        auto field = schema->field(index);
+        auto field_proxy = std::make_shared<FieldProxy>(std::move(field));
+        auto field_proxy_id  = ProxyManager::manageProxy(field_proxy);
+        context.outputs[0] = factory.createScalar(field_proxy_id);
     }
 
     void Schema::getFieldByName(libmexclass::proxy::method::Context& context) {
@@ -135,9 +107,7 @@ namespace arrow::matlab::tabular::proxy {
         const auto field = schema->GetFieldByName(name);
         auto field_proxy = std::make_shared<FieldProxy>(field);
         const auto field_proxy_id = ProxyManager::manageProxy(field_proxy);
-        const auto field_proxy_id_mda = factory.createScalar(field_proxy_id);
-
-        context.outputs[0] = field_proxy_id_mda;
+        context.outputs[0] = factory.createScalar(field_proxy_id);
     }
 
     void Schema::getNumFields(libmexclass::proxy::method::Context& context) {
diff --git a/matlab/src/cpp/arrow/matlab/type/proxy/type.cc b/matlab/src/cpp/arrow/matlab/type/proxy/type.cc
index 1eed4e6141..1cbaaf328e 100644
--- a/matlab/src/cpp/arrow/matlab/type/proxy/type.cc
+++ b/matlab/src/cpp/arrow/matlab/type/proxy/type.cc
@@ -15,7 +15,11 @@
 // specific language governing permissions and limitations
 // under the License.
 
+
+#include "arrow/matlab/error/error.h"
+#include "arrow/matlab/index/validate.h"
 #include "arrow/matlab/type/proxy/type.h"
+#include "arrow/matlab/type/proxy/field.h"
 
 #include "libmexclass/proxy/ProxyManager.h"
 
@@ -24,6 +28,7 @@ namespace arrow::matlab::type::proxy {
     Type::Type(std::shared_ptr<arrow::DataType> type) : data_type{std::move(type)} {
         REGISTER_METHOD(Type, getTypeID);
         REGISTER_METHOD(Type, getNumFields);
+        REGISTER_METHOD(Type, getFieldByIndex);
         REGISTER_METHOD(Type, isEqual);
     }
 
@@ -47,6 +52,36 @@ namespace arrow::matlab::type::proxy {
         context.outputs[0] = num_fields_mda;
     }
 
+    void Type::getFieldByIndex(libmexclass::proxy::method::Context& context) {
+        namespace mda = ::matlab::data;
+        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]);
+
+        // Validate there is at least 1 field
+        MATLAB_ERROR_IF_NOT_OK_WITH_CONTEXT(
+            index::validateNonEmptyContainer(data_type->num_fields()),
+            context,
+            error::INDEX_EMPTY_CONTAINER);
+
+        // Validate the matlab index provided is within the range [1, num_fields]
+        MATLAB_ERROR_IF_NOT_OK_WITH_CONTEXT(
+            index::validateInRange(matlab_index, data_type->num_fields()),
+            context,
+            error::INDEX_OUT_OF_RANGE);
+
+        // Note: MATLAB uses 1-based indexing, so subtract 1.
+        // arrow::DataType::field does not do any bounds checking.
+        const int32_t index = matlab_index - 1;
+
+        auto field = data_type->field(index);
+        auto field_proxy = std::make_shared<proxy::Field>(std::move(field));
+        auto field_proxy_id  = libmexclass::proxy::ProxyManager::manageProxy(field_proxy);
+        context.outputs[0] = factory.createScalar(field_proxy_id);
+    }
+
     void Type::isEqual(libmexclass::proxy::method::Context& context) {
         namespace mda = ::matlab::data;
 
diff --git a/matlab/src/cpp/arrow/matlab/type/proxy/type.h b/matlab/src/cpp/arrow/matlab/type/proxy/type.h
index efd2b8255a..3a6b287a92 100644
--- a/matlab/src/cpp/arrow/matlab/type/proxy/type.h
+++ b/matlab/src/cpp/arrow/matlab/type/proxy/type.h
@@ -37,6 +37,8 @@ class Type : public libmexclass::proxy::Proxy {
 
         void getNumFields(libmexclass::proxy::method::Context& context);
 
+        void getFieldByIndex(libmexclass::proxy::method::Context& context);
+
         void isEqual(libmexclass::proxy::method::Context& context);
 
         std::shared_ptr<arrow::DataType> data_type;
diff --git a/matlab/src/matlab/+arrow/+type/Type.m b/matlab/src/matlab/+arrow/+type/Type.m
index 24f83e0267..0fd0139b18 100644
--- a/matlab/src/matlab/+arrow/+type/Type.m
+++ b/matlab/src/matlab/+arrow/+type/Type.m
@@ -19,6 +19,7 @@ classdef (Abstract) Type < matlab.mixin.CustomDisplay & ...
 
     properties (Dependent, GetAccess=public, SetAccess=private)
         ID
+        Fields
         NumFields
     end
 
@@ -41,6 +42,29 @@ classdef (Abstract) Type < matlab.mixin.CustomDisplay & ...
         function typeID = get.ID(obj)
             typeID = arrow.type.ID(obj.Proxy.getTypeID());
         end
+
+        function F = field(obj, idx)
+            import arrow.internal.validate.*
+
+            idx = index.numeric(idx, "int32", AllowNonScalar=false);
+            args = struct(Index=idx);
+            proxyID = obj.Proxy.getFieldByIndex(args);
+            proxy = libmexclass.proxy.Proxy(Name="arrow.type.proxy.Field", ID=proxyID);
+            F = arrow.type.Field(proxy);
+        end
+
+        function fields = get.Fields(obj)
+            numFields = obj.NumFields;
+            if numFields == 0
+                fields = arrow.type.Field.empty(0, 0);
+            else
+                fields = cell(1, numFields);
+                for ii = 1:numFields
+                    fields{ii} = obj.field(ii);
+                end
+                fields = horzcat(fields);
+            end
+        end
     end
 
     methods(Access = protected)
diff --git a/matlab/test/arrow/tabular/tSchema.m b/matlab/test/arrow/tabular/tSchema.m
index 3220236d4a..e4c706d9a3 100644
--- a/matlab/test/arrow/tabular/tSchema.m
+++ b/matlab/test/arrow/tabular/tSchema.m
@@ -239,7 +239,7 @@ classdef tSchema < matlab.unittest.TestCase
             testCase.verifyEqual(field.Type.ID, arrow.type.ID.UInt32);
         end
 
-        function ErrorIfInvalidNumericFieldIndex(testCase)
+        function ErrorIfIndexIsOutOfRange(testCase)
             % Verify that an error is thrown when trying to access a field
             % with an invalid numeric index (e.g. greater than NumFields).
             schema = arrow.schema([...
@@ -250,7 +250,7 @@ classdef tSchema < matlab.unittest.TestCase
 
             % Index is greater than NumFields.
             index = 100;
-            testCase.verifyError(@() schema.field(index), "arrow:tabular:schema:InvalidNumericFieldIndex");
+            testCase.verifyError(@() schema.field(index), "arrow:index:OutOfRange");
         end
 
         function ErrorIfFieldNameDoesNotExist(testCase)
@@ -376,7 +376,7 @@ classdef tSchema < matlab.unittest.TestCase
             testCase.verifyEqual(schema.FieldNames, string.empty(1, 0));
             testCase.verifyEqual(schema.Fields, arrow.type.Field.empty(0, 0));
             testCase.verifyError(@() schema.field(0), "arrow:badsubscript:NonPositive");
-            testCase.verifyError(@() schema.field(1), "arrow:tabular:schema:NumericFieldIndexWithEmptySchema");
+            testCase.verifyError(@() schema.field(1), "arrow:index:EmptyContainer");
 
             % 0x1 empty Field array.
             fields = arrow.type.Field.empty(0, 1);
@@ -385,7 +385,7 @@ classdef tSchema < matlab.unittest.TestCase
             testCase.verifyEqual(schema.FieldNames, string.empty(1, 0));
             testCase.verifyEqual(schema.Fields, arrow.type.Field.empty(0, 0));
             testCase.verifyError(@() schema.field(0), "arrow:badsubscript:NonPositive");
-            testCase.verifyError(@() schema.field(1), "arrow:tabular:schema:NumericFieldIndexWithEmptySchema");
+            testCase.verifyError(@() schema.field(1), "arrow:index:EmptyContainer");
 
             % 1x0 empty Field array.
             fields = arrow.type.Field.empty(1, 0);
@@ -394,7 +394,7 @@ classdef tSchema < matlab.unittest.TestCase
             testCase.verifyEqual(schema.FieldNames, string.empty(1, 0));
             testCase.verifyEqual(schema.Fields, arrow.type.Field.empty(0, 0));
             testCase.verifyError(@() schema.field(0), "arrow:badsubscript:NonPositive");
-            testCase.verifyError(@() schema.field(1), "arrow:tabular:schema:NumericFieldIndexWithEmptySchema");
+            testCase.verifyError(@() schema.field(1), "arrow:index:EmptyContainer");
         end
 
         function GetFieldByNameWithChar(testCase)
diff --git a/matlab/test/arrow/type/hFixedWidthType.m b/matlab/test/arrow/type/hFixedWidthType.m
index adb234bbd3..b23c21a6b4 100644
--- a/matlab/test/arrow/type/hFixedWidthType.m
+++ b/matlab/test/arrow/type/hFixedWidthType.m
@@ -49,6 +49,31 @@ classdef hFixedWidthType < matlab.unittest.TestCase
             testCase.verifyEqual(arrowType.NumFields, int32(0));
         end
 
+        function TestFieldsProperty(testCase)
+            % Verify Fields is a 0x0 arrow.type.Field array.
+            type = testCase.ArrowType;
+            fields = type.Fields;
+            testCase.verifyEqual(fields, arrow.type.Field.empty(0, 0));
+        end
+
+        function FieldsNoSetter(testCase)
+            % Verify the Fields property is not settable.
+            type = testCase.ArrowType;
+            testCase.verifyError(@() setfield(type, "Fields", "1"), "MATLAB:class:SetProhibited");
+        end
+
+        function InvalidFieldIndex(testCase)
+            % Verify the field() method throws the expected error message
+            % when given an invalid index.
+            type = testCase.ArrowType;
+
+            testCase.verifyError(@() type.field(0), "arrow:badsubscript:NonPositive");
+            testCase.verifyError(@() type.field("A"), "arrow:badsubscript:NonNumeric");
+
+            % NOTE: For FixedWidthTypes, Fields is always empty.
+            testCase.verifyError(@() type.field(1), "arrow:index:EmptyContainer");
+        end
+
         function TestBitWidthNoSetter(testCase)
             % Verify that an error is thrown when trying to set the value
             % of the BitWidth property.
diff --git a/matlab/test/arrow/type/tStringType.m b/matlab/test/arrow/type/tStringType.m
index e2a16ab133..3d518b3da3 100644
--- a/matlab/test/arrow/type/tStringType.m
+++ b/matlab/test/arrow/type/tStringType.m
@@ -64,6 +64,31 @@ classdef tStringType < matlab.unittest.TestCase
             testCase.verifyFalse(isequal(typeArray1, typeArray2));
         end
 
+        function TestFieldsProperty(testCase)
+            % Verify Fields is a 0x0 arrow.type.Field array.
+            type = arrow.string();
+            fields = type.Fields;
+            testCase.verifyEqual(fields, arrow.type.Field.empty(0, 0));
+        end
+
+        function FieldsNoSetter(testCase)
+            % Verify the Fields property is not settable.
+            type = arrow.string();
+            testCase.verifyError(@() setfield(type, "Fields", "1"), "MATLAB:class:SetProhibited");
+        end
+
+        function InvalidFieldIndex(testCase)
+            % Verify the field() method throws the expected error message
+            % when given an invalid index.
+            type = arrow.string();
+
+            testCase.verifyError(@() type.field(0), "arrow:badsubscript:NonPositive");
+            testCase.verifyError(@() type.field("A"), "arrow:badsubscript:NonNumeric");
+
+            % NOTE: For StringType, Fields is always empty.
+            testCase.verifyError(@() type.field(1), "arrow:index:EmptyContainer");
+        end
+
     end
 
 end
diff --git a/matlab/tools/cmake/BuildMatlabArrowInterface.cmake b/matlab/tools/cmake/BuildMatlabArrowInterface.cmake
index a5c0b079b3..b5c480d6a6 100644
--- a/matlab/tools/cmake/BuildMatlabArrowInterface.cmake
+++ b/matlab/tools/cmake/BuildMatlabArrowInterface.cmake
@@ -68,7 +68,9 @@ set(MATLAB_ARROW_LIBMEXCLASS_CLIENT_PROXY_SOURCES "${CMAKE_SOURCE_DIR}/src/cpp/a
                                                   "${CMAKE_SOURCE_DIR}/src/cpp/arrow/matlab/type/proxy/field.cc"
                                                   "${CMAKE_SOURCE_DIR}/src/cpp/arrow/matlab/type/proxy/wrap.cc"
                                                   "${CMAKE_SOURCE_DIR}/src/cpp/arrow/matlab/io/feather/proxy/writer.cc"
-                                                  "${CMAKE_SOURCE_DIR}/src/cpp/arrow/matlab/io/feather/proxy/reader.cc")
+                                                  "${CMAKE_SOURCE_DIR}/src/cpp/arrow/matlab/io/feather/proxy/reader.cc"
+                                                  "${CMAKE_SOURCE_DIR}/src/cpp/arrow/matlab/index/validate.cc")
+
 
 
 set(MATLAB_ARROW_LIBMEXCLASS_CLIENT_PROXY_FACTORY_INCLUDE_DIR "${CMAKE_SOURCE_DIR}/src/cpp/arrow/matlab/proxy")