kevingurney commented on code in PR #37806:
URL: https://github.com/apache/arrow/pull/37806#discussion_r1331972505


##########
matlab/src/cpp/arrow/matlab/error/error.h:
##########
@@ -192,7 +192,8 @@ 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* STRUCT_ARRAY_MAKE_FAILED = 
"arrow:structarray:MakeFailed";

Review Comment:
   Maybe we could name the error ID `arrow:array:StructArrayMakeFailed` instead?



##########
matlab/src/matlab/+arrow/+internal/+test/+tabular/createAllSupportedArrayTypes.m:
##########
@@ -23,6 +23,8 @@
         opts.NumRows(1, 1) {mustBeFinite, mustBeNonnegative} = 3;  
     end
 
+    rng(1);

Review Comment:
   Can you add a comment here like:
   
   "Seed the random number generator to ensure reproducible results in tests."



##########
matlab/src/matlab/+arrow/+type/StructType.m:
##########
@@ -33,14 +33,28 @@
     end
 
     methods (Hidden)
-        % TODO: Consider using a mixin approach to add this behavior. For
-        % example, ChunkedArray's toMATLAB method could check if its 
-        % Type inherits from a mixin called "Preallocateable" (or something
-        % more descriptive). If so, we can call preallocateMATLABArray
-        % in the toMATLAB method.
-        function preallocateMATLABArray(~)
-            error("arrow:type:UnsupportedFunction", ...
-                "preallocateMATLABArray is not supported for StructType");
-        end
+        function data = preallocateMATLABArray(obj, numElements)
+            import arrow.tabular.internal.*
+
+            fields = obj.Fields;
+            
+            % Construct the VariableNames and VariableDimensionNames
+            fieldNames = [fields.Name];
+            validVariableNames = makeValidVariableNames(fieldNames);
+            validDimensionNames = makeValidDimensionNames(validVariableNames);
+
+            % Preallocates each table variable. Uses the child field types
+            % to construct the correct MATLAB type.
+            variableData = cell(1, numel(fields));
+            for ii = 1:numel(fields)
+                type = fields(ii).Type;

Review Comment:
   Could you add a comment here that says something like "Recursively call 
preallocateMATLABArray to handle preallocation of nested types".



##########
matlab/src/matlab/+arrow/+array/StructArray.m:
##########
@@ -0,0 +1,142 @@
+% arrow.array.StructArray
+
+% 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 StructArray < arrow.array.Array
+
+    properties (Dependent, GetAccess=public, SetAccess=private)
+        NumFields
+        FieldNames
+    end
+
+    properties (Hidden, Dependent, GetAccess=public, SetAccess=private)
+        NullSubstitutionValue
+    end
+
+    methods
+        function obj = StructArray(proxy)
+            arguments
+                proxy(1, 1) libmexclass.proxy.Proxy {validate(proxy, 
"arrow.array.proxy.StructArray")}
+            end
+            import arrow.internal.proxy.validate
+            [email protected](proxy);
+        end
+
+        function numFields = get.NumFields(obj)
+            numFields = obj.Proxy.getNumFields();
+        end
+
+        function fieldNames = get.FieldNames(obj)
+            fieldNames = obj.Proxy.getFieldNames();
+        end
+
+        function F = field(obj, idx)
+            import arrow.internal.validate.*
+
+            idx = index.numericOrString(idx, "int32", AllowNonScalar=false);
+
+            if isnumeric(idx)
+                args = struct(Index=idx);
+                fieldStruct = obj.Proxy.getFieldByIndex(args);
+            else
+                args = struct(Name=idx);
+                fieldStruct = obj.Proxy.getFieldByName(args);
+            end
+            
+            traits = 
arrow.type.traits.traits(arrow.type.ID(fieldStruct.TypeID));
+            proxy = libmexclass.proxy.Proxy(Name=traits.ArrayProxyClassName, 
ID=fieldStruct.ProxyID);
+            F = traits.ArrayConstructor(proxy);
+        end
+
+        function T = toMATLAB(obj)
+            T = table(obj);
+        end
+
+        function T = table(obj)
+            import arrow.tabular.internal.*
+
+            numFields = obj.NumFields;
+            matlabArrays = cell(1, numFields);
+
+            invalid = ~obj.Valid;
+            numInvalid = nnz(invalid);
+            
+            for ii = 1:numFields
+                arrowArray = obj.field(ii);
+                matlabArray = toMATLAB(arrowArray);
+                if numInvalid ~= 0
+                    % MATLAB tables do not have "null"-values themselves,
+                    % so we represent the Struct Array's null values by
+                    % setting each variable to its type-specifc "null" value.

Review Comment:
   Suggested wording tweak:
   
   "MATLAB tables do not support null values themselves. So, to encode the 
StructArray's null values, we iterate over each variable in the resulting 
MATLAB table, and for each variable, we set the value of all null elements to 
the "NullSubstitutionValue" that corresponds to the variable's type (e.g. NaN 
for double, NaT for datetime, etc.).



##########
matlab/src/matlab/+arrow/+array/StructArray.m:
##########
@@ -0,0 +1,142 @@
+% arrow.array.StructArray
+
+% 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 StructArray < arrow.array.Array
+
+    properties (Dependent, GetAccess=public, SetAccess=private)
+        NumFields
+        FieldNames
+    end
+
+    properties (Hidden, Dependent, GetAccess=public, SetAccess=private)
+        NullSubstitutionValue
+    end
+
+    methods
+        function obj = StructArray(proxy)
+            arguments
+                proxy(1, 1) libmexclass.proxy.Proxy {validate(proxy, 
"arrow.array.proxy.StructArray")}
+            end
+            import arrow.internal.proxy.validate
+            [email protected](proxy);
+        end
+
+        function numFields = get.NumFields(obj)
+            numFields = obj.Proxy.getNumFields();
+        end
+
+        function fieldNames = get.FieldNames(obj)
+            fieldNames = obj.Proxy.getFieldNames();
+        end
+
+        function F = field(obj, idx)
+            import arrow.internal.validate.*
+
+            idx = index.numericOrString(idx, "int32", AllowNonScalar=false);
+
+            if isnumeric(idx)
+                args = struct(Index=idx);
+                fieldStruct = obj.Proxy.getFieldByIndex(args);
+            else
+                args = struct(Name=idx);
+                fieldStruct = obj.Proxy.getFieldByName(args);
+            end
+            
+            traits = 
arrow.type.traits.traits(arrow.type.ID(fieldStruct.TypeID));
+            proxy = libmexclass.proxy.Proxy(Name=traits.ArrayProxyClassName, 
ID=fieldStruct.ProxyID);
+            F = traits.ArrayConstructor(proxy);
+        end
+
+        function T = toMATLAB(obj)
+            T = table(obj);
+        end
+
+        function T = table(obj)
+            import arrow.tabular.internal.*
+
+            numFields = obj.NumFields;
+            matlabArrays = cell(1, numFields);
+
+            invalid = ~obj.Valid;
+            numInvalid = nnz(invalid);
+            
+            for ii = 1:numFields
+                arrowArray = obj.field(ii);
+                matlabArray = toMATLAB(arrowArray);
+                if numInvalid ~= 0
+                    % MATLAB tables do not have "null"-values themselves,
+                    % so we represent the Struct Array's null values by
+                    % setting each variable to its type-specifc "null" value.
+                    matlabArray(invalid, :) = 
repmat(arrowArray.NullSubstitutionValue, [numInvalid 1]);
+                end
+                matlabArrays{ii} = matlabArray;
+            end
+
+            fieldNames = [obj.Type.Fields.Name];
+            validVariableNames = makeValidVariableNames(fieldNames);
+            validDimensionNames = makeValidDimensionNames(validVariableNames);
+
+            T = table(matlabArrays{:}, ...
+                VariableNames=validVariableNames, ...
+                DimensionNames=validDimensionNames);
+        end
+
+        function nullSubVal = get.NullSubstitutionValue(obj)
+            % Return a cell array containing each field's type-specifc
+            % "null" value. For example, NaN is the type-specific null
+            % value for Float64Arrays and Float64Arrays

Review Comment:
   I think this wording might be slightly off:
   
   "Float64Arrays and Float64Arrays"



##########
matlab/src/matlab/+arrow/+internal/+validate/parseValid.m:
##########
@@ -0,0 +1,46 @@
+%PARSEVALID Utility function for parsing the Valid name-value pair. 
+
+% 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 validElements = parseValid(opts, numElements)
+    if ~isfield(opts, "Valid")
+        % If Valid is not a field in opts, return an empty logical array.
+        validElements = logical.empty(0, 1);
+        return;
+    end
+
+    valid = opts.Valid;
+    if islogical(valid)
+        validElements = reshape(valid, [], 1);
+        if ~isscalar(validElements)
+            % Verify the logical vector has the correct number of elements
+            validateattributes(validElements, "logical", {'numel', 
numElements});
+        elseif validElements == false
+            validElements = false(numElements, 1);
+        else % validElements == true
+            % Return an empty logical to represent all elements are valid. 
+            validElements = logical.empty(0, 1);
+        end
+    else
+        % valid is a list of indices. Verify the indices are numeric, 
+        % integers, and within the range 1 < indices < numElements.

Review Comment:
   range 1 <= indices <= numElements.



##########
matlab/test/arrow/array/tStructArray.m:
##########
@@ -0,0 +1,271 @@
+%TSTRUCTARRAY Unit tests for arrow.array.StructArray
+
+% 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 tStructArray < matlab.unittest.TestCase
+
+    properties
+        Float64Array = arrow.array([1 NaN 3 4 5]);
+        StringArray = arrow.array(["A" "B" "C" "D" missing]);
+    end
+
+    methods (Test)
+        function Basic(tc)
+            import arrow.array.StructArray
+            array = StructArray.fromArrays(tc.Float64Array, tc.StringArray);
+            tc.verifyInstanceOf(array, "arrow.array.StructArray");
+        end
+
+        function FieldNames(tc)
+            % Verify the FieldNames property is set to the expected value.
+            import arrow.array.StructArray
+
+            % Default field names used
+            array = StructArray.fromArrays(tc.Float64Array, tc.StringArray);
+            tc.verifyEqual(array.FieldNames, ["Field1", "Field2"]);
+            
+            % Field names provided
+            array = StructArray.fromArrays(tc.Float64Array, tc.StringArray, 
FieldNames=["A", "B"]);
+            tc.verifyEqual(array.FieldNames, ["A", "B"]);
+
+            % Duplicate field names provided
+            array = StructArray.fromArrays(tc.Float64Array, tc.StringArray, 
FieldNames=["C", "C"]);
+            tc.verifyEqual(array.FieldNames, ["C", "C"]);
+        end
+
+        function FieldNamesError(tc)
+            % Verify the FieldNames nv-pair errors when expected.
+            import arrow.array.StructArray
+
+            % Wrong type provided
+            fcn = @() StructArray.fromArrays(tc.Float64Array, tc.StringArray, 
FieldNames={table table});
+            tc.verifyError(fcn, "MATLAB:validation:UnableToConvert");
+            
+            % Wrong number of field names provided
+            fcn = @() StructArray.fromArrays(tc.Float64Array, tc.StringArray, 
FieldNames="A");
+            tc.verifyError(fcn, "arrow:tabular:WrongNumberColumnNames");
+
+             % Missing string provided
+            fcn = @() StructArray.fromArrays(tc.Float64Array, tc.StringArray, 
FieldNames=["A" missing]);
+            tc.verifyError(fcn, "MATLAB:validators:mustBeNonmissing");
+        end
+
+        function FieldNamesNoSetter(tc)
+            % Verify the FieldNames property is read-only. 
+            import arrow.array.StructArray
+
+            array = StructArray.fromArrays(tc.Float64Array, tc.StringArray, 
FieldNames=["X", "Y"]);
+            fcn = @() setfield(array, "FieldNames", ["A", "B"]);
+            tc.verifyError(fcn, "MATLAB:class:SetProhibited");
+        end
+
+        function NumFields(tc)
+            % Verify the NumFields property is set to the expected value.
+            import arrow.array.StructArray
+
+            array = StructArray.fromArrays(tc.Float64Array, tc.StringArray);
+            tc.verifyEqual(array.NumFields, int32(2));
+        end
+
+         function NumFieldsNoSetter(tc)
+            % Verify the NumFields property is read-only.
+            import arrow.array.StructArray
+
+            array = StructArray.fromArrays(tc.Float64Array, tc.StringArray);
+            fcn = @() setfield(array, "NumFields", 10);
+            tc.verifyError(fcn, "MATLAB:class:SetProhibited");
+         end
+
+         function Valid(tc)
+            % Verify the Valid property is set to the expected value.
+            import arrow.array.StructArray
+
+            array = StructArray.fromArrays(tc.Float64Array, tc.StringArray);
+            expectedValid = true([5 1]);
+            tc.verifyEqual(array.Valid, expectedValid);
+
+            % Supply the Valid nv-pair
+            valid = [true true false true false];
+            array = StructArray.fromArrays(tc.Float64Array, tc.StringArray, 
Valid=valid);
+            tc.verifyEqual(array.Valid, valid');
+         end
+
+         function ValidNVPairError(tc)
+            % Verify the Valid nv-pair errors when expected.
+            import arrow.array.StructArray
+
+            % Provided an invalid index
+            fcn = @() StructArray.fromArrays(tc.Float64Array, tc.StringArray, 
Valid=10);
+            tc.verifyError(fcn, "MATLAB:notLessEqual");
+
+            % Provided a logical vector with more elements than the array
+            % length
+            fcn = @() StructArray.fromArrays(tc.Float64Array, tc.StringArray, 
Valid=false([7 1]));
+            tc.verifyError(fcn, "MATLAB:incorrectNumel");
+         end
+
+        function ValidNoSetter(tc)
+            % Verify the Valid property is read-only.
+            import arrow.array.StructArray
+
+            array = StructArray.fromArrays(tc.Float64Array, tc.StringArray);
+            fcn = @() setfield(array, "Valid", false);
+            tc.verifyError(fcn, "MATLAB:class:SetProhibited");
+        end
+
+        function Length(tc)
+            % Verify the Length property is set to the expected value.
+            import arrow.array.StructArray
+
+            array = StructArray.fromArrays(tc.Float64Array, tc.StringArray);
+            tc.verifyEqual(array.Length, int64(5));
+        end
+
+        function LengthNoSetter(tc)
+            % Verify the Length property is read-only.
+            import arrow.array.StructArray
+
+            array = StructArray.fromArrays(tc.Float64Array, tc.StringArray);
+            fcn = @() setfield(array, "Length", 1);
+            tc.verifyError(fcn, "MATLAB:class:SetProhibited");
+        end
+
+        function Type(tc)
+            % Verify the Type property is set to the expected value.
+            import arrow.array.StructArray
+
+            array = StructArray.fromArrays(tc.Float64Array, tc.StringArray, 
FieldNames=["X", "Y"]);
+            field1 = arrow.field("X", arrow.float64());
+            field2 = arrow.field("Y", arrow.string());
+            expectedType = arrow.struct(field1, field2);
+            tc.verifyEqual(array.Type, expectedType);
+        end
+
+        function TypeNoSetter(tc)
+            % Verify the Type property is read-only.
+            import arrow.array.StructArray
+
+            array = StructArray.fromArrays(tc.Float64Array, tc.StringArray);
+            fcn = @() setfield(array, "Type", tc.Float64Array.Type);
+            tc.verifyError(fcn, "MATLAB:class:SetProhibited");
+        end
+
+        function FieldByIndex(tc)
+            import arrow.array.StructArray
+            array = StructArray.fromArrays(tc.Float64Array, tc.StringArray);
+
+            % Extract 1st field
+            field1 = array.field(1);
+            tc.verifyEqual(field1, tc.Float64Array);
+
+            % Extract 2nd field
+            field2 = array.field(2);
+            tc.verifyEqual(field2, tc.StringArray);
+        end
+
+        function FieldByIndexError(tc)
+            import arrow.array.StructArray
+            array = StructArray.fromArrays(tc.Float64Array, tc.StringArray);
+
+            % Supply a nonscalar vector
+            fcn = @() array.field([1 2]);
+            tc.verifyError(fcn, "arrow:badsubscript:NonScalar");
+
+            % Supply a noninteger
+            fcn = @() array.field(1.1);
+            tc.verifyError(fcn, "arrow:badsubscript:NonInteger");
+        end
+
+        function FieldByName(tc)
+            import arrow.array.StructArray
+            array = StructArray.fromArrays(tc.Float64Array, tc.StringArray);
+
+            % Extract 1st field
+            field1 = array.field("Field1");
+            tc.verifyEqual(field1, tc.Float64Array);
+
+            % Extract 2nd field
+            field2 = array.field("Field2");
+            tc.verifyEqual(field2, tc.StringArray);
+        end
+
+        function FieldByNameError(tc)
+            import arrow.array.StructArray
+            array = StructArray.fromArrays(tc.Float64Array, tc.StringArray);
+
+            % Supply a nonscalar string array
+            fcn = @() array.field(["Field1" "Field2"]);
+            tc.verifyError(fcn, "arrow:badsubscript:NonScalar");
+
+            % Supply a nonexistent field name
+            fcn = @() array.field("B");
+            tc.verifyError(fcn, "arrow:tabular:schema:AmbiguousFieldName");
+        end
+
+        function toMATLAB(tc)
+            % Verify toMATLAB returns the expected MATLAB table
+            import arrow.array.StructArray
+            array = StructArray.fromArrays(tc.Float64Array, tc.StringArray, 
FieldNames=["X", "Y"]);
+            expectedTable = table(toMATLAB(tc.Float64Array), 
toMATLAB(tc.StringArray), VariableNames=["X", "Y"]);
+            actualTable = toMATLAB(array);
+            tc.verifyEqual(actualTable, expectedTable);
+
+            % Verify table elements that correspond to "null" values 
+            % in the StructArray are set to the type-specific null values.
+            valid = [1 2 5];
+            array = StructArray.fromArrays(tc.Float64Array, tc.StringArray, 
FieldNames=["X", "Y"], Valid=valid);
+            expectedTable([3 4], :) = repmat({NaN string(missing)}, [2 1]);

Review Comment:
   Just to make it clear we are expecting the `NulSubstitutionValue`s here, you 
could also consider writing this as::
   
   ```matlab
   expectedTable([3 4], :) = repmat({tc.Float64Array.NullSubstitutionValue 
tc.StringArray.NullSubstitutionValue}, [2 1]);
   ```



##########
matlab/test/arrow/array/tStructArray.m:
##########
@@ -0,0 +1,271 @@
+%TSTRUCTARRAY Unit tests for arrow.array.StructArray
+
+% 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 tStructArray < matlab.unittest.TestCase
+
+    properties
+        Float64Array = arrow.array([1 NaN 3 4 5]);
+        StringArray = arrow.array(["A" "B" "C" "D" missing]);
+    end
+
+    methods (Test)
+        function Basic(tc)
+            import arrow.array.StructArray
+            array = StructArray.fromArrays(tc.Float64Array, tc.StringArray);
+            tc.verifyInstanceOf(array, "arrow.array.StructArray");
+        end
+
+        function FieldNames(tc)
+            % Verify the FieldNames property is set to the expected value.
+            import arrow.array.StructArray
+
+            % Default field names used
+            array = StructArray.fromArrays(tc.Float64Array, tc.StringArray);
+            tc.verifyEqual(array.FieldNames, ["Field1", "Field2"]);
+            
+            % Field names provided
+            array = StructArray.fromArrays(tc.Float64Array, tc.StringArray, 
FieldNames=["A", "B"]);
+            tc.verifyEqual(array.FieldNames, ["A", "B"]);
+
+            % Duplicate field names provided
+            array = StructArray.fromArrays(tc.Float64Array, tc.StringArray, 
FieldNames=["C", "C"]);
+            tc.verifyEqual(array.FieldNames, ["C", "C"]);
+        end
+
+        function FieldNamesError(tc)
+            % Verify the FieldNames nv-pair errors when expected.
+            import arrow.array.StructArray
+
+            % Wrong type provided
+            fcn = @() StructArray.fromArrays(tc.Float64Array, tc.StringArray, 
FieldNames={table table});
+            tc.verifyError(fcn, "MATLAB:validation:UnableToConvert");
+            
+            % Wrong number of field names provided
+            fcn = @() StructArray.fromArrays(tc.Float64Array, tc.StringArray, 
FieldNames="A");
+            tc.verifyError(fcn, "arrow:tabular:WrongNumberColumnNames");
+
+             % Missing string provided
+            fcn = @() StructArray.fromArrays(tc.Float64Array, tc.StringArray, 
FieldNames=["A" missing]);
+            tc.verifyError(fcn, "MATLAB:validators:mustBeNonmissing");
+        end
+
+        function FieldNamesNoSetter(tc)
+            % Verify the FieldNames property is read-only. 
+            import arrow.array.StructArray
+
+            array = StructArray.fromArrays(tc.Float64Array, tc.StringArray, 
FieldNames=["X", "Y"]);
+            fcn = @() setfield(array, "FieldNames", ["A", "B"]);
+            tc.verifyError(fcn, "MATLAB:class:SetProhibited");
+        end
+
+        function NumFields(tc)
+            % Verify the NumFields property is set to the expected value.
+            import arrow.array.StructArray
+
+            array = StructArray.fromArrays(tc.Float64Array, tc.StringArray);
+            tc.verifyEqual(array.NumFields, int32(2));
+        end
+
+         function NumFieldsNoSetter(tc)
+            % Verify the NumFields property is read-only.
+            import arrow.array.StructArray
+
+            array = StructArray.fromArrays(tc.Float64Array, tc.StringArray);
+            fcn = @() setfield(array, "NumFields", 10);
+            tc.verifyError(fcn, "MATLAB:class:SetProhibited");
+         end
+
+         function Valid(tc)
+            % Verify the Valid property is set to the expected value.
+            import arrow.array.StructArray
+
+            array = StructArray.fromArrays(tc.Float64Array, tc.StringArray);
+            expectedValid = true([5 1]);
+            tc.verifyEqual(array.Valid, expectedValid);
+
+            % Supply the Valid nv-pair
+            valid = [true true false true false];
+            array = StructArray.fromArrays(tc.Float64Array, tc.StringArray, 
Valid=valid);
+            tc.verifyEqual(array.Valid, valid');
+         end
+
+         function ValidNVPairError(tc)
+            % Verify the Valid nv-pair errors when expected.
+            import arrow.array.StructArray
+
+            % Provided an invalid index
+            fcn = @() StructArray.fromArrays(tc.Float64Array, tc.StringArray, 
Valid=10);
+            tc.verifyError(fcn, "MATLAB:notLessEqual");
+
+            % Provided a logical vector with more elements than the array
+            % length
+            fcn = @() StructArray.fromArrays(tc.Float64Array, tc.StringArray, 
Valid=false([7 1]));
+            tc.verifyError(fcn, "MATLAB:incorrectNumel");
+         end
+
+        function ValidNoSetter(tc)
+            % Verify the Valid property is read-only.
+            import arrow.array.StructArray
+
+            array = StructArray.fromArrays(tc.Float64Array, tc.StringArray);
+            fcn = @() setfield(array, "Valid", false);
+            tc.verifyError(fcn, "MATLAB:class:SetProhibited");
+        end
+
+        function Length(tc)
+            % Verify the Length property is set to the expected value.
+            import arrow.array.StructArray
+
+            array = StructArray.fromArrays(tc.Float64Array, tc.StringArray);
+            tc.verifyEqual(array.Length, int64(5));
+        end
+
+        function LengthNoSetter(tc)
+            % Verify the Length property is read-only.
+            import arrow.array.StructArray
+
+            array = StructArray.fromArrays(tc.Float64Array, tc.StringArray);
+            fcn = @() setfield(array, "Length", 1);
+            tc.verifyError(fcn, "MATLAB:class:SetProhibited");
+        end
+
+        function Type(tc)
+            % Verify the Type property is set to the expected value.
+            import arrow.array.StructArray
+
+            array = StructArray.fromArrays(tc.Float64Array, tc.StringArray, 
FieldNames=["X", "Y"]);
+            field1 = arrow.field("X", arrow.float64());
+            field2 = arrow.field("Y", arrow.string());
+            expectedType = arrow.struct(field1, field2);
+            tc.verifyEqual(array.Type, expectedType);
+        end
+
+        function TypeNoSetter(tc)
+            % Verify the Type property is read-only.
+            import arrow.array.StructArray
+
+            array = StructArray.fromArrays(tc.Float64Array, tc.StringArray);
+            fcn = @() setfield(array, "Type", tc.Float64Array.Type);
+            tc.verifyError(fcn, "MATLAB:class:SetProhibited");
+        end
+
+        function FieldByIndex(tc)
+            import arrow.array.StructArray
+            array = StructArray.fromArrays(tc.Float64Array, tc.StringArray);
+
+            % Extract 1st field
+            field1 = array.field(1);
+            tc.verifyEqual(field1, tc.Float64Array);
+
+            % Extract 2nd field
+            field2 = array.field(2);
+            tc.verifyEqual(field2, tc.StringArray);
+        end
+
+        function FieldByIndexError(tc)
+            import arrow.array.StructArray
+            array = StructArray.fromArrays(tc.Float64Array, tc.StringArray);
+
+            % Supply a nonscalar vector
+            fcn = @() array.field([1 2]);
+            tc.verifyError(fcn, "arrow:badsubscript:NonScalar");
+
+            % Supply a noninteger
+            fcn = @() array.field(1.1);
+            tc.verifyError(fcn, "arrow:badsubscript:NonInteger");
+        end
+
+        function FieldByName(tc)
+            import arrow.array.StructArray
+            array = StructArray.fromArrays(tc.Float64Array, tc.StringArray);
+
+            % Extract 1st field
+            field1 = array.field("Field1");
+            tc.verifyEqual(field1, tc.Float64Array);
+
+            % Extract 2nd field
+            field2 = array.field("Field2");
+            tc.verifyEqual(field2, tc.StringArray);
+        end
+
+        function FieldByNameError(tc)
+            import arrow.array.StructArray
+            array = StructArray.fromArrays(tc.Float64Array, tc.StringArray);
+
+            % Supply a nonscalar string array
+            fcn = @() array.field(["Field1" "Field2"]);
+            tc.verifyError(fcn, "arrow:badsubscript:NonScalar");
+
+            % Supply a nonexistent field name
+            fcn = @() array.field("B");
+            tc.verifyError(fcn, "arrow:tabular:schema:AmbiguousFieldName");
+        end
+
+        function toMATLAB(tc)
+            % Verify toMATLAB returns the expected MATLAB table
+            import arrow.array.StructArray
+            array = StructArray.fromArrays(tc.Float64Array, tc.StringArray, 
FieldNames=["X", "Y"]);
+            expectedTable = table(toMATLAB(tc.Float64Array), 
toMATLAB(tc.StringArray), VariableNames=["X", "Y"]);
+            actualTable = toMATLAB(array);
+            tc.verifyEqual(actualTable, expectedTable);
+
+            % Verify table elements that correspond to "null" values 
+            % in the StructArray are set to the type-specific null values.
+            valid = [1 2 5];
+            array = StructArray.fromArrays(tc.Float64Array, tc.StringArray, 
FieldNames=["X", "Y"], Valid=valid);
+            expectedTable([3 4], :) = repmat({NaN string(missing)}, [2 1]);
+            actualTable = toMATLAB(array);
+            tc.verifyEqual(actualTable, expectedTable);
+        end
+
+        function table(tc)
+            % Verify toMATLAB returns the expected MATLAB table
+            import arrow.array.StructArray
+            array = StructArray.fromArrays(tc.Float64Array, tc.StringArray, 
FieldNames=["X", "Y"]);
+            expectedTable = table(toMATLAB(tc.Float64Array), 
toMATLAB(tc.StringArray), VariableNames=["X", "Y"]);
+            actualTable = table(array);
+            tc.verifyEqual(actualTable, expectedTable);
+
+            % Verify table elements that correspond to "null" values 
+            % in the StructArray are set to the type-specific null values.
+            valid = [1 2 5];
+            array = StructArray.fromArrays(tc.Float64Array, tc.StringArray, 
FieldNames=["X", "Y"], Valid=valid);
+            expectedTable([3 4], :) = repmat({NaN string(missing)}, [2 1]);
+            actualTable = toMATLAB(array);
+            tc.verifyEqual(actualTable, expectedTable);
+        end
+
+        function IsEqualTrue(tc)
+            % Verify isequal returns true when expected.
+            import arrow.array.StructArray
+            array1 = StructArray.fromArrays(tc.Float64Array, tc.StringArray, 
FieldNames=["X", "Y"]);
+            array2 = StructArray.fromArrays(tc.Float64Array, tc.StringArray, 
FieldNames=["X", "Y"]);
+            tc.verifyTrue(isequal(array1, array2));
+        end
+
+        function IsEqualFalse(tc)
+            % Verify isequal returns false when expected.
+            import arrow.array.StructArray
+            array1 = StructArray.fromArrays(tc.Float64Array, tc.StringArray, 
FieldNames=["X", "Y"]);
+            array2 = StructArray.fromArrays(tc.StringArray, tc.Float64Array, 
FieldNames=["X", "Y"]);
+            array3 = StructArray.fromArrays(tc.Float64Array, tc.StringArray, 
FieldNames=["A", "B"]);
+            tc.verifyFalse(isequal(array1, array2));

Review Comment:
   Could add some comments here like:
   
   ```matlab
   % StructArrays have the same FieldNames but the Fields have different types.
   tc.verifyFalse(isequal(array1, array2));
   % Fields of the StructArrays have the same types but the StructArrays have 
different FieldNames. 
   tc.verifyFalse(isequal(array1, array3));
   



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]

Reply via email to