This is an automated email from the ASF dual-hosted git repository.

AlenkaF 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 ca37093fc2 GH-40062: [C++][Python] Conversion of Table to Arrow Tensor 
(#41870)
ca37093fc2 is described below

commit ca37093fc2433b98d45a44fd4146d87ea4c3755e
Author: Alenka Frim <[email protected]>
AuthorDate: Wed Jul 1 14:23:39 2026 +0200

    GH-40062: [C++][Python] Conversion of Table to Arrow Tensor (#41870)
    
    ### Rationale for this change
    
    There is currently no method to convert Arrow Table to Arrow Tensor 
(conversion from columnar format to a contiguous block of memory). This work is 
a continuation of `RecordBatch::ToTensor` work, see 
https://github.com/apache/arrow/issues/40058.
    
    ### What changes are included in this PR?
    
    This PR:
    - implements `Table::ToTensor` conversion
    - adds bindings to Python
    - adds benchmarks in C++
    - removes the code in `RecordBatch::ToTensor` and uses the Table 
implementation (`RecordBatch::ToTensor` benchmarks checked)
    
    ### Are these changes tested?
    
    Yes, in C++ and Python.
    
    ### Are there any user-facing changes?
    
    No, it is a new feature.
    * GitHub Issue: #40062
    
    Lead-authored-by: AlenkaF <[email protected]>
    Co-authored-by: Alenka Frim <[email protected]>
    Co-authored-by: tadeja <[email protected]>
    Co-authored-by: Copilot Autofix powered by AI 
<[email protected]>
    Co-authored-by: Rok Mihevc <[email protected]>
    Signed-off-by: AlenkaF <[email protected]>
---
 cpp/src/arrow/record_batch.cc        |   1 -
 cpp/src/arrow/record_batch.h         |   6 +-
 cpp/src/arrow/record_batch_test.cc   |  56 +++-
 cpp/src/arrow/table.cc               |   9 +
 cpp/src/arrow/table.h                |  12 +
 cpp/src/arrow/table_test.cc          | 570 +++++++++++++++++++++++++++++++++++
 cpp/src/arrow/tensor.cc              | 179 +++++++----
 cpp/src/arrow/tensor.h               |   4 +
 cpp/src/arrow/tensor_benchmark.cc    |  41 +++
 python/pyarrow/includes/libarrow.pxd |   3 +
 python/pyarrow/table.pxi             |  87 +++++-
 python/pyarrow/tests/test_table.py   | 113 +++++--
 12 files changed, 967 insertions(+), 114 deletions(-)

diff --git a/cpp/src/arrow/record_batch.cc b/cpp/src/arrow/record_batch.cc
index 12e0f553b7..bc2612f92a 100644
--- a/cpp/src/arrow/record_batch.cc
+++ b/cpp/src/arrow/record_batch.cc
@@ -18,7 +18,6 @@
 #include "arrow/record_batch.h"
 
 #include <algorithm>
-#include <cmath>
 #include <cstdlib>
 #include <memory>
 #include <mutex>
diff --git a/cpp/src/arrow/record_batch.h b/cpp/src/arrow/record_batch.h
index 17d7f9857a..d7c6de1ed3 100644
--- a/cpp/src/arrow/record_batch.h
+++ b/cpp/src/arrow/record_batch.h
@@ -90,11 +90,9 @@ class ARROW_EXPORT RecordBatch {
   /// in the resulting struct array.
   Result<std::shared_ptr<StructArray>> ToStructArray() const;
 
-  /// \brief Convert record batch with one data type to Tensor
+  /// \brief Convert RecordBatch to Tensor
   ///
-  /// Create a Tensor object with shape (number of rows, number of columns) and
-  /// strides (type size in bytes, type size in bytes * number of rows).
-  /// Generated Tensor will have column-major layout.
+  /// Create a Tensor object.
   ///
   /// \param[in] null_to_nan if true, convert nulls to NaN
   /// \param[in] row_major if true, create row-major Tensor else column-major 
Tensor
diff --git a/cpp/src/arrow/record_batch_test.cc 
b/cpp/src/arrow/record_batch_test.cc
index 904285fd1c..fea47244da 100644
--- a/cpp/src/arrow/record_batch_test.cc
+++ b/cpp/src/arrow/record_batch_test.cc
@@ -910,10 +910,11 @@ TEST_F(TestRecordBatch, ToTensorUnsupportedMissing) {
 
   auto batch = RecordBatch::Make(schema, length, {a0, a1});
 
-  ASSERT_RAISES_WITH_MESSAGE(TypeError,
-                             "Type error: Can only convert a RecordBatch with 
no nulls. "
-                             "Set null_to_nan to true to convert nulls to NaN",
-                             batch->ToTensor());
+  ASSERT_RAISES_WITH_MESSAGE(
+      TypeError,
+      "Type error: Can only convert a Table or RecordBatch with no "
+      "nulls. Set null_to_nan to true to convert nulls to NaN",
+      batch->ToTensor());
 }
 
 TEST_F(TestRecordBatch, ToTensorEmptyBatch) {
@@ -944,10 +945,11 @@ TEST_F(TestRecordBatch, ToTensorEmptyBatch) {
   auto batch_no_columns =
       RecordBatch::Make(::arrow::schema({}), 10, 
std::vector<std::shared_ptr<Array>>{});
 
-  ASSERT_RAISES_WITH_MESSAGE(TypeError,
-                             "Type error: Conversion to Tensor for 
RecordBatches without "
-                             "columns/schema is not supported.",
-                             batch_no_columns->ToTensor());
+  ASSERT_RAISES_WITH_MESSAGE(
+      TypeError,
+      "Type error: Conversion to Tensor for Tables or RecordBatches "
+      "without columns/schema is not supported.",
+      batch_no_columns->ToTensor());
 }
 
 template <typename DataType>
@@ -1116,6 +1118,44 @@ TEST_F(TestRecordBatch, ToTensorSupportedNullToNan) {
   CheckTensorRowMajor<FloatType>(tensor2_row, 18, shape, strides_2);
 }
 
+TEST_F(TestRecordBatch, ToTensorNullToNanFloat16) {
+  // Tensor::Equals does not yet support NaN-aware comparison for float16, so
+  // null slots are verified by inspecting the raw buffer directly.
+  const int length = 9;
+
+  auto f0 = field("f0", float16());
+  auto f1 = field("f1", float16());
+  auto schema = ::arrow::schema({f0, f1});
+
+  auto a0 = ArrayFromJSON(float16(), "[null, 2, 3, 4, 5, 6, 7, 8, 9]");
+  auto a1 = ArrayFromJSON(float16(), "[10, 20, 30, 40, null, 60, 70, 80, 90]");
+  auto batch = RecordBatch::Make(schema, length, {a0, a1});
+
+  // Column-major
+  ASSERT_OK_AND_ASSIGN(auto tensor,
+                       batch->ToTensor(/*null_to_nan=*/true, 
/*row_major=*/false));
+  ASSERT_OK(tensor->Validate());
+
+  std::vector<int64_t> shape = {9, 2};
+  const int64_t f16_size = sizeof(uint16_t);
+  CheckTensor<HalfFloatType>(tensor, 18, shape, {f16_size, f16_size * 
shape[0]});
+
+  const auto* buf = reinterpret_cast<const uint16_t*>(tensor->raw_data());
+  EXPECT_TRUE(util::Float16::FromBits(buf[0]).is_nan());
+  EXPECT_TRUE(util::Float16::FromBits(buf[13]).is_nan());
+
+  // Row-major
+  ASSERT_OK_AND_ASSIGN(auto tensor_row, batch->ToTensor(/*null_to_nan=*/true));
+  ASSERT_OK(tensor_row->Validate());
+
+  CheckTensorRowMajor<HalfFloatType>(tensor_row, 18, shape,
+                                     {f16_size * shape[1], f16_size});
+
+  const auto* buf_row = reinterpret_cast<const 
uint16_t*>(tensor_row->raw_data());
+  EXPECT_TRUE(util::Float16::FromBits(buf_row[0]).is_nan());
+  EXPECT_TRUE(util::Float16::FromBits(buf_row[9]).is_nan());
+}
+
 TEST_F(TestRecordBatch, ToTensorSupportedTypesMixed) {
   const int length = 9;
 
diff --git a/cpp/src/arrow/table.cc b/cpp/src/arrow/table.cc
index 68a8a1951f..7a7168e931 100644
--- a/cpp/src/arrow/table.cc
+++ b/cpp/src/arrow/table.cc
@@ -36,6 +36,7 @@
 #include "arrow/record_batch.h"
 #include "arrow/result.h"
 #include "arrow/status.h"
+#include "arrow/tensor.h"
 #include "arrow/type.h"
 #include "arrow/type_fwd.h"
 #include "arrow/type_traits.h"
@@ -346,6 +347,14 @@ Result<std::shared_ptr<Table>> 
Table::FromChunkedStructArray(
                      array->length());
 }
 
+Result<std::shared_ptr<Tensor>> Table::ToTensor(bool null_to_nan, bool 
row_major,
+                                                MemoryPool* pool) const {
+  std::shared_ptr<Tensor> tensor;
+  ARROW_RETURN_NOT_OK(
+      internal::TableToTensor(*this, null_to_nan, row_major, pool, &tensor));
+  return tensor;
+}
+
 std::vector<std::string> Table::ColumnNames() const {
   std::vector<std::string> names(num_columns());
   for (int i = 0; i < num_columns(); ++i) {
diff --git a/cpp/src/arrow/table.h b/cpp/src/arrow/table.h
index dee6f6fdd3..051060a52c 100644
--- a/cpp/src/arrow/table.h
+++ b/cpp/src/arrow/table.h
@@ -102,6 +102,18 @@ class ARROW_EXPORT Table {
   static Result<std::shared_ptr<Table>> FromChunkedStructArray(
       const std::shared_ptr<ChunkedArray>& array);
 
+  /// \brief Convert Table to Tensor
+  ///
+  /// Create a Tensor object.
+  ///
+  /// \param[in] null_to_nan if true, convert nulls to NaN
+  /// \param[in] row_major if true, create row-major Tensor else column-major 
Tensor
+  /// \param[in] pool the memory pool to allocate the tensor buffer
+  /// \return the resulting Tensor
+  Result<std::shared_ptr<Tensor>> ToTensor(
+      bool null_to_nan = false, bool row_major = true,
+      MemoryPool* pool = default_memory_pool()) const;
+
   /// \brief Return the table schema
   const std::shared_ptr<Schema>& schema() const { return schema_; }
 
diff --git a/cpp/src/arrow/table_test.cc b/cpp/src/arrow/table_test.cc
index 4182bf020a..6d60b6bda5 100644
--- a/cpp/src/arrow/table_test.cc
+++ b/cpp/src/arrow/table_test.cc
@@ -33,9 +33,11 @@
 #include "arrow/compute/cast.h"
 #include "arrow/record_batch.h"
 #include "arrow/status.h"
+#include "arrow/tensor.h"
 #include "arrow/testing/gtest_util.h"
 #include "arrow/testing/random.h"
 #include "arrow/type.h"
+#include "arrow/util/float16.h"
 #include "arrow/util/key_value_metadata.h"
 
 namespace arrow {
@@ -528,6 +530,574 @@ TEST_F(TestTable, ConcatenateTables) {
   ASSERT_RAISES(Invalid, ConcatenateTables({t1, t3}));
 }
 
+TEST_F(TestTable, ToTensorUnsupportedType) {
+  auto f0 = field("f0", int32());
+  // Unsupported data type
+  auto f1 = field("f1", utf8());
+
+  std::vector<std::shared_ptr<Field>> fields = {f0, f1};
+  auto schema = ::arrow::schema(fields);
+
+  auto a0 = ChunkedArrayFromJSON(int32(), {"[1, 2, 3]", "[4, 5, 6, 7, 8, 9]"});
+  auto a1 = ChunkedArrayFromJSON(
+      utf8(), {R"(["a", "b", "c", "a", "b"])", R"(["c", "a", "b", "c"])"});
+
+  auto table = Table::Make(schema, {a0, a1});
+
+  ASSERT_RAISES_WITH_MESSAGE(
+      TypeError, "Type error: DataType is not supported: " + 
a1->type()->ToString(),
+      table->ToTensor());
+
+  // Unsupported boolean data type
+  auto f2 = field("f2", boolean());
+
+  std::vector<std::shared_ptr<Field>> fields2 = {f0, f2};
+  auto schema2 = ::arrow::schema(fields2);
+  auto a2 = ChunkedArrayFromJSON(
+      boolean(), {"[true, false, true, true, false, true, false, true, 
true]"});
+  auto table2 = Table::Make(schema2, {a0, a2});
+
+  ASSERT_RAISES_WITH_MESSAGE(
+      TypeError, "Type error: DataType is not supported: " + 
a2->type()->ToString(),
+      table2->ToTensor());
+}
+
+TEST_F(TestTable, ToTensorUnsupportedMissing) {
+  auto f0 = field("f0", int32());
+  auto f1 = field("f1", int32());
+
+  std::vector<std::shared_ptr<Field>> fields = {f0, f1};
+  auto schema = ::arrow::schema(fields);
+
+  auto a0 = ChunkedArrayFromJSON(int32(), {"[1, 2, 3]", "[4, 5, 6, 7, 8, 9]"});
+  auto a1 = ChunkedArrayFromJSON(int32(), {"[10, 20]", "[30, 40, null, 60, 70, 
80, 90]"});
+
+  auto table = Table::Make(schema, {a0, a1});
+
+  ASSERT_RAISES_WITH_MESSAGE(
+      TypeError,
+      "Type error: Can only convert a Table or RecordBatch with no "
+      "nulls. Set null_to_nan to true to convert nulls to NaN",
+      table->ToTensor());
+}
+
+TEST_F(TestTable, ToTensorEmptyTable) {
+  auto f0 = field("f0", int32());
+  auto f1 = field("f1", int32());
+
+  std::vector<std::shared_ptr<Field>> fields = {f0, f1};
+  auto schema = ::arrow::schema(fields);
+
+  ASSERT_OK_AND_ASSIGN(std::shared_ptr<Table> empty, Table::MakeEmpty(schema));
+
+  ASSERT_OK_AND_ASSIGN(auto tensor_column,
+                       empty->ToTensor(/*null_to_nan=*/false, 
/*row_major=*/false));
+  ASSERT_OK(tensor_column->Validate());
+
+  ASSERT_OK_AND_ASSIGN(auto tensor_row, empty->ToTensor());
+  ASSERT_OK(tensor_row->Validate());
+
+  const std::vector<int64_t> strides = {4, 4};
+  const std::vector<int64_t> shape = {0, 2};
+
+  EXPECT_EQ(strides, tensor_column->strides());
+  EXPECT_EQ(shape, tensor_column->shape());
+  EXPECT_EQ(strides, tensor_row->strides());
+  EXPECT_EQ(shape, tensor_row->shape());
+
+  auto table_no_columns =
+      Table::Make(::arrow::schema({}), std::vector<std::shared_ptr<Array>>{});
+
+  ASSERT_RAISES_WITH_MESSAGE(
+      TypeError,
+      "Type error: Conversion to Tensor for Tables or RecordBatches "
+      "without columns/schema is not supported.",
+      table_no_columns->ToTensor());
+}
+
+template <typename DataType>
+void CheckTableToTensor(const std::shared_ptr<Tensor>& tensor, const int size,
+                        const std::vector<int64_t> shape,
+                        const std::vector<int64_t> f_strides) {
+  EXPECT_EQ(size, tensor->size());
+  EXPECT_EQ(TypeTraits<DataType>::type_singleton(), tensor->type());
+  EXPECT_EQ(shape, tensor->shape());
+  EXPECT_EQ(f_strides, tensor->strides());
+  EXPECT_FALSE(tensor->is_row_major());
+  EXPECT_TRUE(tensor->is_column_major());
+  EXPECT_TRUE(tensor->is_contiguous());
+}
+
+template <typename DataType>
+void CheckTableToTensorRowMajor(const std::shared_ptr<Tensor>& tensor, const 
int size,
+                                const std::vector<int64_t> shape,
+                                const std::vector<int64_t> strides) {
+  EXPECT_EQ(size, tensor->size());
+  EXPECT_EQ(TypeTraits<DataType>::type_singleton(), tensor->type());
+  EXPECT_EQ(shape, tensor->shape());
+  EXPECT_EQ(strides, tensor->strides());
+  EXPECT_TRUE(tensor->is_row_major());
+  EXPECT_FALSE(tensor->is_column_major());
+  EXPECT_TRUE(tensor->is_contiguous());
+}
+
+TEST_F(TestTable, ToTensorSupportedNaN) {
+  auto f0 = field("f0", float32());
+  auto f1 = field("f1", float32());
+
+  std::vector<std::shared_ptr<Field>> fields = {f0, f1};
+  auto schema = ::arrow::schema(fields);
+
+  auto a0 = ChunkedArrayFromJSON(float32(), {"[NaN, 2, 3]", "[4, 5, 6, 7, 8, 
9]"});
+  auto a1 =
+      ChunkedArrayFromJSON(float32(), {"[10, 20]", "[30, 40, NaN, 60, 70, 80, 
90]"});
+
+  auto table = Table::Make(schema, {a0, a1});
+
+  ASSERT_OK_AND_ASSIGN(auto tensor,
+                       table->ToTensor(/*null_to_nan=*/false, 
/*row_major=*/false));
+  ASSERT_OK(tensor->Validate());
+
+  std::vector<int64_t> shape = {9, 2};
+  const int64_t f32_size = sizeof(float);
+  std::vector<int64_t> f_strides = {f32_size, f32_size * shape[0]};
+  std::shared_ptr<Tensor> tensor_expected = TensorFromJSON(
+      float32(), "[NaN, 2,  3,  4,  5, 6, 7, 8, 9, 10, 20, 30, 40, NaN, 60, 
70, 80, 90]",
+      shape, f_strides);
+
+  EXPECT_FALSE(tensor_expected->Equals(*tensor));
+  EXPECT_TRUE(tensor_expected->Equals(*tensor, 
EqualOptions().nans_equal(true)));
+  CheckTableToTensor<FloatType>(tensor, 18, shape, f_strides);
+}
+
+TEST_F(TestTable, ToTensorSupportedNullToNan) {
+  // int32 + float32 = float64
+  auto f0 = field("f0", int32());
+  auto f1 = field("f1", float32());
+
+  std::vector<std::shared_ptr<Field>> fields = {f0, f1};
+  auto schema = ::arrow::schema(fields);
+
+  auto a0 = ChunkedArrayFromJSON(int32(), {"[null, 2, 3]", "[4, 5, 6, 7, 8, 
9]"});
+  auto a1 =
+      ChunkedArrayFromJSON(float32(), {"[10, 20]", "[30, 40, null, 60, 70, 80, 
90]"});
+
+  auto table = Table::Make(schema, {a0, a1});
+
+  ASSERT_OK_AND_ASSIGN(auto tensor,
+                       table->ToTensor(/*null_to_nan=*/true, 
/*row_major=*/false));
+  ASSERT_OK(tensor->Validate());
+
+  std::vector<int64_t> shape = {9, 2};
+  const int64_t f64_size = sizeof(double);
+  std::vector<int64_t> f_strides = {f64_size, f64_size * shape[0]};
+  std::shared_ptr<Tensor> tensor_expected = TensorFromJSON(
+      float64(), "[NaN, 2,  3,  4,  5, 6, 7, 8, 9, 10, 20, 30, 40, NaN, 60, 
70, 80, 90]",
+      shape, f_strides);
+
+  EXPECT_FALSE(tensor_expected->Equals(*tensor));
+  EXPECT_TRUE(tensor_expected->Equals(*tensor, 
EqualOptions().nans_equal(true)));
+
+  CheckTableToTensor<DoubleType>(tensor, 18, shape, f_strides);
+
+  ASSERT_OK_AND_ASSIGN(auto tensor_row, table->ToTensor(/*null_to_nan=*/true));
+  ASSERT_OK(tensor_row->Validate());
+
+  std::vector<int64_t> strides = {f64_size * shape[1], f64_size};
+  std::shared_ptr<Tensor> tensor_expected_row = TensorFromJSON(
+      float64(), "[NaN, 10, 2,  20, 3, 30,  4, 40, 5, NaN, 6, 60, 7, 70, 8, 
80, 9, 90]",
+      shape, strides);
+
+  EXPECT_FALSE(tensor_expected_row->Equals(*tensor_row));
+  EXPECT_TRUE(tensor_expected_row->Equals(*tensor_row, 
EqualOptions().nans_equal(true)));
+
+  CheckTableToTensorRowMajor<DoubleType>(tensor_row, 18, shape, strides);
+
+  // int32 -> float64
+  auto f2 = field("f2", int32());
+
+  std::vector<std::shared_ptr<Field>> fields1 = {f0, f2};
+  auto schema1 = ::arrow::schema(fields1);
+
+  auto a2 = ChunkedArrayFromJSON(int32(), {"[10, 20]", "[30, 40, null, 60, 70, 
80, 90]"});
+  auto table1 = Table::Make(schema1, {a0, a2});
+
+  ASSERT_OK_AND_ASSIGN(auto tensor1,
+                       table1->ToTensor(/*null_to_nan=*/true, 
/*row_major=*/false));
+  ASSERT_OK(tensor1->Validate());
+
+  EXPECT_FALSE(tensor_expected->Equals(*tensor1));
+  EXPECT_TRUE(tensor_expected->Equals(*tensor1, 
EqualOptions().nans_equal(true)));
+
+  CheckTableToTensor<DoubleType>(tensor1, 18, shape, f_strides);
+
+  ASSERT_OK_AND_ASSIGN(auto tensor1_row, 
table1->ToTensor(/*null_to_nan=*/true));
+  ASSERT_OK(tensor1_row->Validate());
+
+  EXPECT_FALSE(tensor_expected_row->Equals(*tensor1_row));
+  EXPECT_TRUE(tensor_expected_row->Equals(*tensor1_row, 
EqualOptions().nans_equal(true)));
+
+  CheckTableToTensorRowMajor<DoubleType>(tensor1_row, 18, shape, strides);
+
+  // int8 -> float32
+  auto f3 = field("f3", int8());
+  auto f4 = field("f4", int8());
+
+  std::vector<std::shared_ptr<Field>> fields2 = {f3, f4};
+  auto schema2 = ::arrow::schema(fields2);
+
+  auto a3 = ChunkedArrayFromJSON(int8(), {"[null, 2, 3]", "[4, 5, 6, 7, 8, 
9]"});
+  auto a4 = ChunkedArrayFromJSON(int8(), {"[10, 20]", "[30, 40, null, 60, 70, 
80, 90]"});
+  auto table2 = Table::Make(schema2, {a3, a4});
+
+  ASSERT_OK_AND_ASSIGN(auto tensor2,
+                       table2->ToTensor(/*null_to_nan=*/true, 
/*row_major=*/false));
+  ASSERT_OK(tensor2->Validate());
+
+  const int64_t f32_size = sizeof(float);
+  std::vector<int64_t> f_strides_2 = {f32_size, f32_size * shape[0]};
+  std::shared_ptr<Tensor> tensor_expected_2 = TensorFromJSON(
+      float32(), "[NaN, 2,  3,  4,  5, 6, 7, 8, 9, 10, 20, 30, 40, NaN, 60, 
70, 80, 90]",
+      shape, f_strides_2);
+
+  EXPECT_FALSE(tensor_expected_2->Equals(*tensor2));
+  EXPECT_TRUE(tensor_expected_2->Equals(*tensor2, 
EqualOptions().nans_equal(true)));
+
+  CheckTableToTensor<FloatType>(tensor2, 18, shape, f_strides_2);
+
+  ASSERT_OK_AND_ASSIGN(auto tensor2_row, 
table2->ToTensor(/*null_to_nan=*/true));
+  ASSERT_OK(tensor2_row->Validate());
+
+  std::vector<int64_t> strides_2 = {f32_size * shape[1], f32_size};
+  std::shared_ptr<Tensor> tensor2_expected_row = TensorFromJSON(
+      float32(), "[NaN, 10, 2,  20, 3, 30,  4, 40, 5, NaN, 6, 60, 7, 70, 8, 
80, 9, 90]",
+      shape, strides_2);
+
+  EXPECT_FALSE(tensor2_expected_row->Equals(*tensor2_row));
+  EXPECT_TRUE(
+      tensor2_expected_row->Equals(*tensor2_row, 
EqualOptions().nans_equal(true)));
+
+  CheckTableToTensorRowMajor<FloatType>(tensor2_row, 18, shape, strides_2);
+}
+
+TEST_F(TestTable, ToTensorNullToNanFloat16) {
+  // Tensor::Equals does not yet support NaN-aware comparison for float16, so
+  // null slots are verified by inspecting the raw buffer directly.
+  auto f0 = field("f0", float16());
+  auto f1 = field("f1", float16());
+  auto schema = ::arrow::schema({f0, f1});
+
+  auto a0 = ChunkedArrayFromJSON(float16(), {"[null, 2, 3]", "[4, 5, 6, 7, 8, 
9]"});
+  auto a1 =
+      ChunkedArrayFromJSON(float16(), {"[10, 20]", "[30, 40, null, 60, 70, 80, 
90]"});
+  auto table = Table::Make(schema, {a0, a1});
+
+  // Column-major
+  ASSERT_OK_AND_ASSIGN(auto tensor,
+                       table->ToTensor(/*null_to_nan=*/true, 
/*row_major=*/false));
+  ASSERT_OK(tensor->Validate());
+
+  std::vector<int64_t> shape = {9, 2};
+  const int64_t f16_size = sizeof(uint16_t);
+  CheckTableToTensor<HalfFloatType>(tensor, 18, shape, {f16_size, f16_size * 
shape[0]});
+
+  const auto* buf = reinterpret_cast<const uint16_t*>(tensor->raw_data());
+  EXPECT_TRUE(util::Float16::FromBits(buf[0]).is_nan());
+  EXPECT_TRUE(util::Float16::FromBits(buf[13]).is_nan());
+
+  // Row-major
+  ASSERT_OK_AND_ASSIGN(auto tensor_row, table->ToTensor(/*null_to_nan=*/true));
+  ASSERT_OK(tensor_row->Validate());
+
+  CheckTableToTensorRowMajor<HalfFloatType>(tensor_row, 18, shape,
+                                            {f16_size * shape[1], f16_size});
+
+  const auto* buf_row = reinterpret_cast<const 
uint16_t*>(tensor_row->raw_data());
+  EXPECT_TRUE(util::Float16::FromBits(buf_row[0]).is_nan());
+  EXPECT_TRUE(util::Float16::FromBits(buf_row[9]).is_nan());
+}
+
+TEST_F(TestTable, ToTensorSupportedTypesMixed) {
+  auto f0 = field("f0", uint16());
+  auto f1 = field("f1", int16());
+  auto f2 = field("f2", float32());
+
+  auto a0 = ChunkedArrayFromJSON(uint16(), {"[1, 2, 3]", "[4, 5, 6, 7, 8, 
9]"});
+  auto a1 = ChunkedArrayFromJSON(int16(), {"[10, 20]", "[30, 40, 50, 60, 70, 
80, 90]"});
+  auto a2 = ChunkedArrayFromJSON(float32(),
+                                 {"[100, 200, 300, NaN, 500, 600]", "[700, 
800, 900]"});
+
+  // Single column
+  std::vector<std::shared_ptr<Field>> fields = {f0};
+  auto schema = ::arrow::schema(fields);
+  auto table = Table::Make(schema, {a0});
+
+  ASSERT_OK_AND_ASSIGN(auto tensor,
+                       table->ToTensor(/*null_to_nan=*/false, 
/*row_major=*/false));
+  ASSERT_OK(tensor->Validate());
+
+  std::vector<int64_t> shape = {9, 1};
+  const int64_t uint16_size = sizeof(uint16_t);
+  std::vector<int64_t> f_strides = {uint16_size, uint16_size * shape[0]};
+  std::shared_ptr<Tensor> tensor_expected =
+      TensorFromJSON(uint16(), "[1, 2, 3, 4, 5, 6, 7, 8, 9]", shape, 
f_strides);
+
+  EXPECT_TRUE(tensor_expected->Equals(*tensor));
+  CheckTableToTensor<UInt16Type>(tensor, 9, shape, f_strides);
+
+  // uint16 + int16 = int32
+  std::vector<std::shared_ptr<Field>> fields1 = {f0, f1};
+  auto schema1 = ::arrow::schema(fields1);
+  auto table1 = Table::Make(schema1, {a0, a1});
+
+  ASSERT_OK_AND_ASSIGN(auto tensor1,
+                       table1->ToTensor(/*null_to_nan=*/false, 
/*row_major=*/false));
+  ASSERT_OK(tensor1->Validate());
+
+  std::vector<int64_t> shape1 = {9, 2};
+  const int64_t int32_size = sizeof(int32_t);
+  std::vector<int64_t> f_strides_1 = {int32_size, int32_size * shape1[0]};
+  std::shared_ptr<Tensor> tensor_expected_1 = TensorFromJSON(
+      int32(), "[1,  2,  3,  4,  5,  6,  7,  8,  9, 10, 20, 30, 40, 50, 60, 
70, 80, 90]",
+      shape1, f_strides_1);
+
+  EXPECT_TRUE(tensor_expected_1->Equals(*tensor1));
+
+  CheckTableToTensor<Int32Type>(tensor1, 18, shape1, f_strides_1);
+
+  ASSERT_EQ(tensor1->type()->bit_width(), 
tensor_expected_1->type()->bit_width());
+
+  ASSERT_EQ(1, tensor_expected_1->Value<Int32Type>({0, 0}));
+  ASSERT_EQ(2, tensor_expected_1->Value<Int32Type>({1, 0}));
+  ASSERT_EQ(10, tensor_expected_1->Value<Int32Type>({0, 1}));
+
+  // uint16 + int16 + float32 = float64
+  std::vector<std::shared_ptr<Field>> fields2 = {f0, f1, f2};
+  auto schema2 = ::arrow::schema(fields2);
+  auto table2 = Table::Make(schema2, {a0, a1, a2});
+
+  ASSERT_OK_AND_ASSIGN(auto tensor2,
+                       table2->ToTensor(/*null_to_nan=*/false, 
/*row_major=*/false));
+  ASSERT_OK(tensor2->Validate());
+
+  std::vector<int64_t> shape2 = {9, 3};
+  const int64_t f64_size = sizeof(double);
+  std::vector<int64_t> f_strides_2 = {f64_size, f64_size * shape2[0]};
+  std::shared_ptr<Tensor> tensor_expected_2 =
+      TensorFromJSON(float64(),
+                     "[1,   2,   3,   4,   5,  6,  7,  8,   9,   10,  20, 30,  
40,  50,"
+                     "60,  70, 80, 90, 100, 200, 300, NaN, 500, 600, 700, 800, 
900]",
+                     shape2, f_strides_2);
+
+  EXPECT_FALSE(tensor_expected_2->Equals(*tensor2));
+  EXPECT_TRUE(tensor_expected_2->Equals(*tensor2, 
EqualOptions().nans_equal(true)));
+
+  CheckTableToTensor<DoubleType>(tensor2, 27, shape2, f_strides_2);
+}
+
+TEST_F(TestTable, ToTensorUnsupportedMixedFloat16) {
+  auto f0 = field("f0", float16());
+  auto f1 = field("f1", float64());
+
+  auto a0 = ChunkedArrayFromJSON(float16(), {"[1, 2, 3]", "[4, 5, 6, 7, 8, 
9]"});
+  auto a1 = ChunkedArrayFromJSON(float64(), {"[10, 20]", "[30, 40, 50, 60, 70, 
80, 90]"});
+
+  std::vector<std::shared_ptr<Field>> fields = {f0, f1};
+  auto schema = ::arrow::schema(fields);
+  auto table = Table::Make(schema, {a0, a1});
+
+  ASSERT_RAISES_WITH_MESSAGE(
+      NotImplemented, "NotImplemented: Casting from or to halffloat is not 
supported.",
+      table->ToTensor());
+
+  std::vector<std::shared_ptr<Field>> fields1 = {f1, f0};
+  auto schema1 = ::arrow::schema(fields1);
+  auto table1 = Table::Make(schema1, {a1, a0});
+
+  ASSERT_RAISES_WITH_MESSAGE(
+      NotImplemented, "NotImplemented: Casting from or to halffloat is not 
supported.",
+      table1->ToTensor());
+}
+
+template <typename DataType>
+class TestTableToTensorColumnMajor : public ::testing::Test {};
+
+TYPED_TEST_SUITE_P(TestTableToTensorColumnMajor);
+
+TYPED_TEST_P(TestTableToTensorColumnMajor, SupportedTypes) {
+  using DataType = TypeParam;
+  using c_data_type = typename DataType::c_type;
+  const int unit_size = sizeof(c_data_type);
+
+  auto f0 = field("f0", TypeTraits<DataType>::type_singleton());
+  auto f1 = field("f1", TypeTraits<DataType>::type_singleton());
+  auto f2 = field("f2", TypeTraits<DataType>::type_singleton());
+
+  std::vector<std::shared_ptr<Field>> fields = {f0, f1, f2};
+  auto schema = ::arrow::schema(fields);
+
+  auto a0 = ChunkedArrayFromJSON(TypeTraits<DataType>::type_singleton(),
+                                 {"[1, 2, 3]", "[4, 5, 6, 7, 8, 9]"});
+  auto a1 = ChunkedArrayFromJSON(TypeTraits<DataType>::type_singleton(),
+                                 {"[10, 20]", "[30, 40, 50, 60, 70, 80, 90]"});
+  auto a2 = ChunkedArrayFromJSON(TypeTraits<DataType>::type_singleton(),
+                                 {"[100, 100, 100, 100, 100, 100]", "[100, 
100, 100]"});
+
+  auto table = Table::Make(schema, {a0, a1, a2});
+
+  ASSERT_OK_AND_ASSIGN(auto tensor,
+                       table->ToTensor(/*null_to_nan=*/false, 
/*row_major=*/false));
+  ASSERT_OK(tensor->Validate());
+
+  std::vector<int64_t> shape = {9, 3};
+  std::vector<int64_t> f_strides = {unit_size, unit_size * shape[0]};
+  std::shared_ptr<Tensor> tensor_expected = TensorFromJSON(
+      TypeTraits<DataType>::type_singleton(),
+      "[1,   2,   3,   4,   5,   6,   7,   8,   9, 10,  20,  30,  40,  50,  
60,  70,  "
+      "80,  90, 100, 100, 100, 100, 100, 100, 100, 100, 100]",
+      shape, f_strides);
+
+  EXPECT_TRUE(tensor_expected->Equals(*tensor));
+  CheckTableToTensor<DataType>(tensor, 27, shape, f_strides);
+
+  // Test offsets
+  auto table_slice = table->Slice(1);
+
+  ASSERT_OK_AND_ASSIGN(auto tensor_sliced, 
table_slice->ToTensor(/*null_to_nan=*/false,
+                                                                 
/*row_major=*/false));
+  ASSERT_OK(tensor_sliced->Validate());
+
+  std::vector<int64_t> shape_sliced = {8, 3};
+  std::vector<int64_t> f_strides_sliced = {unit_size, unit_size * 
shape_sliced[0]};
+  std::shared_ptr<Tensor> tensor_expected_sliced =
+      TensorFromJSON(TypeTraits<DataType>::type_singleton(),
+                     "[2,   3,   4,   5,   6,   7,   8,   9, 20,  30,  40,  
50,  60,  "
+                     "70,  80,  90, 100, 100, 100, 100, 100, 100, 100, 100]",
+                     shape_sliced, f_strides_sliced);
+
+  EXPECT_TRUE(tensor_expected_sliced->Equals(*tensor_sliced));
+  CheckTableToTensor<DataType>(tensor_sliced, 24, shape_sliced, 
f_strides_sliced);
+
+  auto table_slice_1 = table->Slice(1, 5);
+
+  ASSERT_OK_AND_ASSIGN(
+      auto tensor_sliced_1,
+      table_slice_1->ToTensor(/*null_to_nan=*/false, /*row_major=*/false));
+  ASSERT_OK(tensor_sliced_1->Validate());
+
+  std::vector<int64_t> shape_sliced_1 = {5, 3};
+  std::vector<int64_t> f_strides_sliced_1 = {unit_size, unit_size * 
shape_sliced_1[0]};
+  std::shared_ptr<Tensor> tensor_expected_sliced_1 =
+      TensorFromJSON(TypeTraits<DataType>::type_singleton(),
+                     "[2, 3, 4, 5, 6, 20, 30, 40, 50, 60, 100, 100, 100, 100, 
100]",
+                     shape_sliced_1, f_strides_sliced_1);
+
+  EXPECT_TRUE(tensor_expected_sliced_1->Equals(*tensor_sliced_1));
+  CheckTableToTensor<DataType>(tensor_sliced_1, 15, shape_sliced_1, 
f_strides_sliced_1);
+}
+
+REGISTER_TYPED_TEST_SUITE_P(TestTableToTensorColumnMajor, SupportedTypes);
+
+INSTANTIATE_TYPED_TEST_SUITE_P(UInt8, TestTableToTensorColumnMajor, UInt8Type);
+INSTANTIATE_TYPED_TEST_SUITE_P(UInt16, TestTableToTensorColumnMajor, 
UInt16Type);
+INSTANTIATE_TYPED_TEST_SUITE_P(UInt32, TestTableToTensorColumnMajor, 
UInt32Type);
+INSTANTIATE_TYPED_TEST_SUITE_P(UInt64, TestTableToTensorColumnMajor, 
UInt64Type);
+INSTANTIATE_TYPED_TEST_SUITE_P(Int8, TestTableToTensorColumnMajor, Int8Type);
+INSTANTIATE_TYPED_TEST_SUITE_P(Int16, TestTableToTensorColumnMajor, Int16Type);
+INSTANTIATE_TYPED_TEST_SUITE_P(Int32, TestTableToTensorColumnMajor, Int32Type);
+INSTANTIATE_TYPED_TEST_SUITE_P(Int64, TestTableToTensorColumnMajor, Int64Type);
+INSTANTIATE_TYPED_TEST_SUITE_P(Float16, TestTableToTensorColumnMajor, 
HalfFloatType);
+INSTANTIATE_TYPED_TEST_SUITE_P(Float32, TestTableToTensorColumnMajor, 
FloatType);
+INSTANTIATE_TYPED_TEST_SUITE_P(Float64, TestTableToTensorColumnMajor, 
DoubleType);
+
+template <typename DataType>
+class TestTableToTensorRowMajor : public ::testing::Test {};
+
+TYPED_TEST_SUITE_P(TestTableToTensorRowMajor);
+
+TYPED_TEST_P(TestTableToTensorRowMajor, SupportedTypes) {
+  using DataType = TypeParam;
+  using c_data_type = typename DataType::c_type;
+  const int unit_size = sizeof(c_data_type);
+
+  auto f0 = field("f0", TypeTraits<DataType>::type_singleton());
+  auto f1 = field("f1", TypeTraits<DataType>::type_singleton());
+  auto f2 = field("f2", TypeTraits<DataType>::type_singleton());
+
+  std::vector<std::shared_ptr<Field>> fields = {f0, f1, f2};
+  auto schema = ::arrow::schema(fields);
+
+  auto a0 = ChunkedArrayFromJSON(TypeTraits<DataType>::type_singleton(),
+                                 {"[1, 2, 3]", "[4, 5, 6, 7, 8, 9]"});
+  auto a1 = ChunkedArrayFromJSON(TypeTraits<DataType>::type_singleton(),
+                                 {"[10, 20]", "[30, 40, 50, 60, 70, 80, 90]"});
+  auto a2 = ChunkedArrayFromJSON(TypeTraits<DataType>::type_singleton(),
+                                 {"[100, 100, 100, 100, 100, 100]", "[100, 
100, 100]"});
+
+  auto table = Table::Make(schema, {a0, a1, a2});
+
+  ASSERT_OK_AND_ASSIGN(auto tensor, table->ToTensor());
+  ASSERT_OK(tensor->Validate());
+
+  std::vector<int64_t> shape = {9, 3};
+  std::vector<int64_t> strides = {unit_size * shape[1], unit_size};
+  std::shared_ptr<Tensor> tensor_expected =
+      TensorFromJSON(TypeTraits<DataType>::type_singleton(),
+                     "[1,   10, 100, 2, 20, 100, 3, 30, 100, 4, 40, 100, 5, 
50, 100, 6, "
+                     "60, 100, 7, 70, 100, 8, 80, 100, 9, 90, 100]",
+                     shape, strides);
+
+  EXPECT_TRUE(tensor_expected->Equals(*tensor));
+  CheckTableToTensorRowMajor<DataType>(tensor, 27, shape, strides);
+
+  // Test offsets
+  auto table_slice = table->Slice(1);
+
+  ASSERT_OK_AND_ASSIGN(auto tensor_sliced, table_slice->ToTensor());
+  ASSERT_OK(tensor_sliced->Validate());
+
+  std::vector<int64_t> shape_sliced = {8, 3};
+  std::vector<int64_t> strides_sliced = {unit_size * shape[1], unit_size};
+  std::shared_ptr<Tensor> tensor_expected_sliced =
+      TensorFromJSON(TypeTraits<DataType>::type_singleton(),
+                     "[2, 20, 100, 3, 30, 100, 4, 40, 100, 5, 50, 100, 6, "
+                     "60, 100, 7, 70, 100, 8, 80, 100, 9, 90, 100]",
+                     shape_sliced, strides_sliced);
+
+  EXPECT_TRUE(tensor_expected_sliced->Equals(*tensor_sliced));
+  CheckTableToTensorRowMajor<DataType>(tensor_sliced, 24, shape_sliced, 
strides_sliced);
+
+  auto table_slice_1 = table->Slice(1, 5);
+
+  ASSERT_OK_AND_ASSIGN(auto tensor_sliced_1, table_slice_1->ToTensor());
+  ASSERT_OK(tensor_sliced_1->Validate());
+
+  std::vector<int64_t> shape_sliced_1 = {5, 3};
+  std::vector<int64_t> strides_sliced_1 = {unit_size * shape_sliced_1[1], 
unit_size};
+  std::shared_ptr<Tensor> tensor_expected_sliced_1 =
+      TensorFromJSON(TypeTraits<DataType>::type_singleton(),
+                     "[2, 20, 100, 3, 30, 100, 4, 40, 100, 5, 50, 100, 6, 60, 
100]",
+                     shape_sliced_1, strides_sliced_1);
+
+  EXPECT_TRUE(tensor_expected_sliced_1->Equals(*tensor_sliced_1));
+  CheckTableToTensorRowMajor<DataType>(tensor_sliced_1, 15, shape_sliced_1,
+                                       strides_sliced_1);
+}
+
+REGISTER_TYPED_TEST_SUITE_P(TestTableToTensorRowMajor, SupportedTypes);
+
+INSTANTIATE_TYPED_TEST_SUITE_P(UInt8, TestTableToTensorRowMajor, UInt8Type);
+INSTANTIATE_TYPED_TEST_SUITE_P(UInt16, TestTableToTensorRowMajor, UInt16Type);
+INSTANTIATE_TYPED_TEST_SUITE_P(UInt32, TestTableToTensorRowMajor, UInt32Type);
+INSTANTIATE_TYPED_TEST_SUITE_P(UInt64, TestTableToTensorRowMajor, UInt64Type);
+INSTANTIATE_TYPED_TEST_SUITE_P(Int8, TestTableToTensorRowMajor, Int8Type);
+INSTANTIATE_TYPED_TEST_SUITE_P(Int16, TestTableToTensorRowMajor, Int16Type);
+INSTANTIATE_TYPED_TEST_SUITE_P(Int32, TestTableToTensorRowMajor, Int32Type);
+INSTANTIATE_TYPED_TEST_SUITE_P(Int64, TestTableToTensorRowMajor, Int64Type);
+INSTANTIATE_TYPED_TEST_SUITE_P(Float16, TestTableToTensorRowMajor, 
HalfFloatType);
+INSTANTIATE_TYPED_TEST_SUITE_P(Float32, TestTableToTensorRowMajor, FloatType);
+INSTANTIATE_TYPED_TEST_SUITE_P(Float64, TestTableToTensorRowMajor, DoubleType);
+
 std::shared_ptr<Table> MakeTableWithOneNullFilledColumn(
     const std::string& column_name, const std::shared_ptr<DataType>& data_type,
     const int length) {
diff --git a/cpp/src/arrow/tensor.cc b/cpp/src/arrow/tensor.cc
index 49b7a0b28e..b5988d7810 100644
--- a/cpp/src/arrow/tensor.cc
+++ b/cpp/src/arrow/tensor.cc
@@ -28,11 +28,12 @@
 #include <type_traits>
 #include <vector>
 
-#include "arrow/record_batch.h"
 #include "arrow/status.h"
+#include "arrow/table.h"
 #include "arrow/type.h"
 #include "arrow/type_traits.h"
 #include "arrow/util/checked_cast.h"
+#include "arrow/util/float16.h"
 #include "arrow/util/int_util_overflow.h"
 #include "arrow/util/logging_internal.h"
 #include "arrow/util/unreachable.h"
@@ -225,7 +226,7 @@ Status ValidateTensorParameters(const 
std::shared_ptr<DataType>& type,
 }
 
 template <typename Out>
-struct ConvertColumnsToTensorVisitor {
+struct ConvertArrayToTensorVisitor {
   Out*& out_values;
   const ArrayData& in_data;
 
@@ -246,8 +247,14 @@ struct ConvertColumnsToTensorVisitor {
         }
       } else {
         for (int64_t i = 0; i < in_data.length; ++i) {
-          *out_values++ =
-              in_data.IsNull(i) ? static_cast<Out>(NAN) : 
static_cast<Out>(in_values[i]);
+          if constexpr (T::type_id == Type::HALF_FLOAT && std::is_same_v<Out, 
uint16_t>) {
+            *out_values++ = in_data.IsNull(i)
+                                ? 
std::numeric_limits<util::Float16>::quiet_NaN().bits()
+                                : static_cast<Out>(in_values[i]);
+          } else {
+            *out_values++ = in_data.IsNull(i) ? static_cast<Out>(NAN)
+                                              : static_cast<Out>(in_values[i]);
+          }
         }
       }
       return Status::OK();
@@ -257,11 +264,12 @@ struct ConvertColumnsToTensorVisitor {
 };
 
 template <typename Out>
-struct ConvertColumnsToTensorRowMajorVisitor {
+struct ConvertArrayToTensorRowMajorVisitor {
   Out*& out_values;
   const ArrayData& in_data;
-  int num_cols;
-  int col_idx;
+  int64_t num_cols;
+  int64_t col_idx;
+  int64_t chunk_idx;
 
   template <typename T>
   Status Visit(const T&) {
@@ -269,14 +277,23 @@ struct ConvertColumnsToTensorRowMajorVisitor {
       using In = typename T::c_type;
       auto in_values = ArraySpan(in_data).GetSpan<In>(1, in_data.length);
 
+      const int64_t base = chunk_idx * num_cols + col_idx;
+
       if (in_data.null_count == 0) {
         for (int64_t i = 0; i < in_data.length; ++i) {
-          out_values[i * num_cols + col_idx] = static_cast<Out>(in_values[i]);
+          out_values[base + i * num_cols] = static_cast<Out>(in_values[i]);
         }
       } else {
         for (int64_t i = 0; i < in_data.length; ++i) {
-          out_values[i * num_cols + col_idx] =
-              in_data.IsNull(i) ? static_cast<Out>(NAN) : 
static_cast<Out>(in_values[i]);
+          if constexpr (T::type_id == Type::HALF_FLOAT && std::is_same_v<Out, 
uint16_t>) {
+            out_values[base + i * num_cols] =
+                in_data.IsNull(i) ? 
std::numeric_limits<util::Float16>::quiet_NaN().bits()
+                                  : static_cast<Out>(in_values[i]);
+          } else {
+            out_values[base + i * num_cols] = in_data.IsNull(i)
+                                                  ? static_cast<Out>(NAN)
+                                                  : 
static_cast<Out>(in_values[i]);
+          }
         }
       }
       return Status::OK();
@@ -285,50 +302,75 @@ struct ConvertColumnsToTensorRowMajorVisitor {
   }
 };
 
-template <typename DataType>
-inline void ConvertColumnsToTensor(const RecordBatch& batch, uint8_t* out,
+template <typename DataType, typename Container>
+inline void ConvertColumnsToTensor(const Container& container, uint8_t* out,
                                    bool row_major) {
   using CType = typename arrow::TypeTraits<DataType>::CType;
   auto* out_values = reinterpret_cast<CType*>(out);
 
-  int i = 0;
-  for (const auto& column : batch.columns()) {
-    if (row_major) {
-      ConvertColumnsToTensorRowMajorVisitor<CType> visitor{out_values, 
*column->data(),
-                                                           
batch.num_columns(), i++};
-      DCHECK_OK(VisitTypeInline(*column->type(), &visitor));
-    } else {
-      ConvertColumnsToTensorVisitor<CType> visitor{out_values, 
*column->data()};
-      DCHECK_OK(VisitTypeInline(*column->type(), &visitor));
+  const int num_columns = container.num_columns();
+
+  for (int col_idx = 0; col_idx < num_columns; ++col_idx) {
+    if constexpr (std::is_same_v<Container, Table>) {
+      int64_t chunk_idx = 0;
+
+      for (const auto& chunk : container.columns()[col_idx]->chunks()) {
+        if (row_major) {
+          ConvertArrayToTensorRowMajorVisitor<CType> visitor{
+              out_values, *chunk->data(), num_columns, col_idx, chunk_idx};
+          DCHECK_OK(VisitTypeInline(*chunk->type(), &visitor));
+          chunk_idx += chunk->length();
+        } else {
+          ConvertArrayToTensorVisitor<CType> visitor{out_values, 
*chunk->data()};
+          DCHECK_OK(VisitTypeInline(*chunk->type(), &visitor));
+        }
+      }
+    } else if constexpr (std::is_same_v<Container, RecordBatch>) {
+      const auto& array_data = container.column_data()[col_idx];
+
+      if (row_major) {
+        ConvertArrayToTensorRowMajorVisitor<CType> visitor{out_values, 
*array_data,
+                                                           num_columns, 
col_idx, 0};
+        DCHECK_OK(VisitTypeInline(*array_data->type, &visitor));
+      } else {
+        ConvertArrayToTensorVisitor<CType> visitor{out_values, *array_data};
+        DCHECK_OK(VisitTypeInline(*array_data->type, &visitor));
+      }
     }
   }
 }
 
-Status RecordBatchToTensor(const RecordBatch& batch, bool null_to_nan, bool 
row_major,
-                           MemoryPool* pool, std::shared_ptr<Tensor>* tensor) {
-  if (batch.num_columns() == 0) {
+template <typename Container>
+Status ToTensorImpl(const Container& container, bool null_to_nan, bool 
row_major,
+                    MemoryPool* pool, std::shared_ptr<Tensor>* tensor) {
+  if (container.num_columns() == 0) {
     return Status::TypeError(
-        "Conversion to Tensor for RecordBatches without columns/schema is not "
+        "Conversion to Tensor for Tables or RecordBatches without 
columns/schema is not "
         "supported.");
   }
   // Check for no validity bitmap of each field
   // if null_to_nan conversion is set to false
-  for (int i = 0; i < batch.num_columns(); ++i) {
-    if (batch.column(i)->null_count() > 0 && !null_to_nan) {
+  for (int i = 0; i < container.num_columns(); ++i) {
+    int64_t null_count = 0;
+    if constexpr (std::is_same_v<Container, Table>) {
+      null_count = container.column(i)->null_count();
+    } else if constexpr (std::is_same_v<Container, RecordBatch>) {
+      null_count = container.column_data(i)->GetNullCount();
+    }
+    if (null_count > 0 && !null_to_nan) {
       return Status::TypeError(
-          "Can only convert a RecordBatch with no nulls. Set null_to_nan to 
true to "
-          "convert nulls to NaN");
+          "Can only convert a Table or RecordBatch with no nulls. Set 
null_to_nan to "
+          "true to convert nulls to NaN");
     }
   }
 
   // Check for supported data types and merge fields
   // to get the resulting uniform data type
-  if (!is_integer(batch.column(0)->type()->id()) &&
-      !is_floating(batch.column(0)->type()->id())) {
-    return Status::TypeError("DataType is not supported: ",
-                             batch.column(0)->type()->ToString());
+  const auto& col_0_type = container.schema()->field(0)->type();
+  if (!is_integer(col_0_type->id()) && !is_floating(col_0_type->id())) {
+    return Status::TypeError("DataType is not supported: ", 
col_0_type->ToString());
   }
-  std::shared_ptr<Field> result_field = batch.schema()->field(0);
+  std::shared_ptr<Field> result_field = container.schema()->field(0);
   std::shared_ptr<DataType> result_type = result_field->type();
 
   Field::MergeOptions options;
@@ -336,24 +378,27 @@ Status RecordBatchToTensor(const RecordBatch& batch, bool 
null_to_nan, bool row_
   options.promote_integer_sign = true;
   options.promote_numeric_width = true;
 
-  if (batch.num_columns() > 1) {
-    for (int i = 1; i < batch.num_columns(); ++i) {
-      if (!is_numeric(batch.column(i)->type()->id())) {
-        return Status::TypeError("DataType is not supported: ",
-                                 batch.column(i)->type()->ToString());
+  if (container.num_columns() > 1) {
+    for (int i = 1; i < container.num_columns(); ++i) {
+      const auto& col_type = container.schema()->field(i)->type();
+
+      if (!is_numeric(col_type->id())) {
+        return Status::TypeError("DataType is not supported: ", 
col_type->ToString());
       }
 
       // Casting of float16 is not supported, throw an error in this case
-      if ((batch.column(i)->type()->id() == Type::HALF_FLOAT ||
+      if ((col_type->id() == Type::HALF_FLOAT ||
            result_field->type()->id() == Type::HALF_FLOAT) &&
-          batch.column(i)->type()->id() != result_field->type()->id()) {
+          col_type->id() != result_field->type()->id()) {
         return Status::NotImplemented("Casting from or to halffloat is not 
supported.");
       }
 
-      ARROW_ASSIGN_OR_RAISE(
-          result_field,
-          result_field->MergeWith(
-              batch.schema()->field(i)->WithName(result_field->name()), 
options));
+      if (!col_type->Equals(result_field->type())) {
+        ARROW_ASSIGN_OR_RAISE(
+            result_field,
+            result_field->MergeWith(
+                container.schema()->field(i)->WithName(result_field->name()), 
options));
+      }
     }
     result_type = result_field->type();
   }
@@ -368,42 +413,46 @@ Status RecordBatchToTensor(const RecordBatch& batch, bool 
null_to_nan, bool row_
   }
 
   // Allocate memory
-  ARROW_ASSIGN_OR_RAISE(
-      std::shared_ptr<Buffer> result,
-      AllocateBuffer(result_type->bit_width() * batch.num_columns() * 
batch.num_rows(),
-                     pool));
+  int64_t buffer_size = result_type->byte_width();
+  if (internal::MultiplyWithOverflow(
+          buffer_size, static_cast<int64_t>(container.num_columns()), 
&buffer_size) ||
+      internal::MultiplyWithOverflow(buffer_size, container.num_rows(), 
&buffer_size)) {
+    return Status::Invalid("Buffer size for tensor would not fit in 64-bit 
integer");
+  }
+  ARROW_ASSIGN_OR_RAISE(std::shared_ptr<Buffer> result,
+                        AllocateBuffer(buffer_size, pool));
   // Copy data
   switch (result_type->id()) {
     case Type::UINT8:
-      ConvertColumnsToTensor<UInt8Type>(batch, result->mutable_data(), 
row_major);
+      ConvertColumnsToTensor<UInt8Type>(container, result->mutable_data(), 
row_major);
       break;
     case Type::UINT16:
     case Type::HALF_FLOAT:
-      ConvertColumnsToTensor<UInt16Type>(batch, result->mutable_data(), 
row_major);
+      ConvertColumnsToTensor<UInt16Type>(container, result->mutable_data(), 
row_major);
       break;
     case Type::UINT32:
-      ConvertColumnsToTensor<UInt32Type>(batch, result->mutable_data(), 
row_major);
+      ConvertColumnsToTensor<UInt32Type>(container, result->mutable_data(), 
row_major);
       break;
     case Type::UINT64:
-      ConvertColumnsToTensor<UInt64Type>(batch, result->mutable_data(), 
row_major);
+      ConvertColumnsToTensor<UInt64Type>(container, result->mutable_data(), 
row_major);
       break;
     case Type::INT8:
-      ConvertColumnsToTensor<Int8Type>(batch, result->mutable_data(), 
row_major);
+      ConvertColumnsToTensor<Int8Type>(container, result->mutable_data(), 
row_major);
       break;
     case Type::INT16:
-      ConvertColumnsToTensor<Int16Type>(batch, result->mutable_data(), 
row_major);
+      ConvertColumnsToTensor<Int16Type>(container, result->mutable_data(), 
row_major);
       break;
     case Type::INT32:
-      ConvertColumnsToTensor<Int32Type>(batch, result->mutable_data(), 
row_major);
+      ConvertColumnsToTensor<Int32Type>(container, result->mutable_data(), 
row_major);
       break;
     case Type::INT64:
-      ConvertColumnsToTensor<Int64Type>(batch, result->mutable_data(), 
row_major);
+      ConvertColumnsToTensor<Int64Type>(container, result->mutable_data(), 
row_major);
       break;
     case Type::FLOAT:
-      ConvertColumnsToTensor<FloatType>(batch, result->mutable_data(), 
row_major);
+      ConvertColumnsToTensor<FloatType>(container, result->mutable_data(), 
row_major);
       break;
     case Type::DOUBLE:
-      ConvertColumnsToTensor<DoubleType>(batch, result->mutable_data(), 
row_major);
+      ConvertColumnsToTensor<DoubleType>(container, result->mutable_data(), 
row_major);
       break;
     default:
       return Status::TypeError("DataType is not supported: ", 
result_type->ToString());
@@ -412,7 +461,7 @@ Status RecordBatchToTensor(const RecordBatch& batch, bool 
null_to_nan, bool row_
   // Construct Tensor object
   const auto& fixed_width_type =
       internal::checked_cast<const FixedWidthType&>(*result_type);
-  std::vector<int64_t> shape = {batch.num_rows(), batch.num_columns()};
+  std::vector<int64_t> shape = {container.num_rows(), container.num_columns()};
   std::vector<int64_t> strides;
 
   if (row_major) {
@@ -427,6 +476,16 @@ Status RecordBatchToTensor(const RecordBatch& batch, bool 
null_to_nan, bool row_
   return Status::OK();
 }
 
+Status TableToTensor(const Table& table, bool null_to_nan, bool row_major,
+                     MemoryPool* pool, std::shared_ptr<Tensor>* tensor) {
+  return ToTensorImpl(table, null_to_nan, row_major, pool, tensor);
+}
+
+Status RecordBatchToTensor(const RecordBatch& batch, bool null_to_nan, bool 
row_major,
+                           MemoryPool* pool, std::shared_ptr<Tensor>* tensor) {
+  return ToTensorImpl(batch, null_to_nan, row_major, pool, tensor);
+}
+
 }  // namespace internal
 
 /// Constructor with strides and dimension names
diff --git a/cpp/src/arrow/tensor.h b/cpp/src/arrow/tensor.h
index beb62a11bd..1300003c29 100644
--- a/cpp/src/arrow/tensor.h
+++ b/cpp/src/arrow/tensor.h
@@ -77,6 +77,10 @@ Status ValidateTensorParameters(const 
std::shared_ptr<DataType>& type,
                                 const std::vector<int64_t>& strides,
                                 const std::vector<std::string>& dim_names);
 
+ARROW_EXPORT
+Status TableToTensor(const Table& table, bool null_to_nan, bool row_major,
+                     MemoryPool* pool, std::shared_ptr<Tensor>* tensor);
+
 ARROW_EXPORT
 Status RecordBatchToTensor(const RecordBatch& batch, bool null_to_nan, bool 
row_major,
                            MemoryPool* pool, std::shared_ptr<Tensor>* tensor);
diff --git a/cpp/src/arrow/tensor_benchmark.cc 
b/cpp/src/arrow/tensor_benchmark.cc
index 91a9270ef3..f064abf612 100644
--- a/cpp/src/arrow/tensor_benchmark.cc
+++ b/cpp/src/arrow/tensor_benchmark.cc
@@ -18,6 +18,7 @@
 #include "benchmark/benchmark.h"
 
 #include "arrow/record_batch.h"
+#include "arrow/table.h"
 #include "arrow/testing/gtest_util.h"
 #include "arrow/testing/random.h"
 #include "arrow/type.h"
@@ -51,6 +52,37 @@ static void BatchToTensorSimple(benchmark::State& state) {
   state.SetBytesProcessed(state.iterations() * ty->byte_width() * num_rows * 
num_cols);
 }
 
+template <typename ValueType, bool row_major>
+static void TableToTensorSimple(benchmark::State& state) {
+  using CType = typename ValueType::c_type;
+  std::shared_ptr<DataType> ty = TypeTraits<ValueType>::type_singleton();
+
+  const int64_t num_cols = state.range(1);
+  const int64_t num_rows = state.range(0) / num_cols / sizeof(CType);
+  arrow::random::RandomArrayGenerator gen_{42};
+
+  std::vector<std::shared_ptr<Field>> fields = {};
+  std::vector<std::shared_ptr<ChunkedArray>> columns = {};
+
+  for (int64_t i = 0; i < num_cols; ++i) {
+    fields.push_back(field("f" + std::to_string(i), ty));
+    const int64_t chunk1_len = num_rows / 2;
+    const int64_t chunk2_len = num_rows - chunk1_len;
+    ArrayVector arrays = {gen_.ArrayOf(ty, chunk1_len), gen_.ArrayOf(ty, 
chunk2_len)};
+    auto chunks = std::make_shared<ChunkedArray>(arrays, ty);
+    columns.push_back(chunks);
+  }
+  auto schema = std::make_shared<Schema>(std::move(fields));
+  auto table = Table::Make(schema, columns);
+
+  for (auto _ : state) {
+    ASSERT_OK_AND_ASSIGN(auto tensor,
+                         table->ToTensor(/*null_to_nan=*/false, 
/*row_major=*/row_major));
+  }
+  state.SetItemsProcessed(state.iterations() * num_rows * num_cols);
+  state.SetBytesProcessed(state.iterations() * ty->byte_width() * num_rows * 
num_cols);
+}
+
 void SetArgs(benchmark::internal::Benchmark* bench) {
   for (int64_t size : {kL1Size, kL2Size}) {
     for (int64_t num_columns : {3, 30, 300}) {
@@ -65,4 +97,13 @@ BENCHMARK_TEMPLATE(BatchToTensorSimple, 
Int16Type)->Apply(SetArgs);
 BENCHMARK_TEMPLATE(BatchToTensorSimple, Int32Type)->Apply(SetArgs);
 BENCHMARK_TEMPLATE(BatchToTensorSimple, Int64Type)->Apply(SetArgs);
 
+#define DECLARE_TABLE_TO_TENSOR_BENCHMARKS(row_major)                          
  \
+  BENCHMARK_TEMPLATE(TableToTensorSimple, Int8Type, 
row_major)->Apply(SetArgs);  \
+  BENCHMARK_TEMPLATE(TableToTensorSimple, Int16Type, 
row_major)->Apply(SetArgs); \
+  BENCHMARK_TEMPLATE(TableToTensorSimple, Int32Type, 
row_major)->Apply(SetArgs); \
+  BENCHMARK_TEMPLATE(TableToTensorSimple, Int64Type, 
row_major)->Apply(SetArgs);
+
+DECLARE_TABLE_TO_TENSOR_BENCHMARKS(false);
+DECLARE_TABLE_TO_TENSOR_BENCHMARKS(true);
+
 }  // namespace arrow
diff --git a/python/pyarrow/includes/libarrow.pxd 
b/python/pyarrow/includes/libarrow.pxd
index f4ac1fd5ef..8b4786ecbf 100644
--- a/python/pyarrow/includes/libarrow.pxd
+++ b/python/pyarrow/includes/libarrow.pxd
@@ -1139,6 +1139,9 @@ cdef extern from "arrow/api.h" namespace "arrow" nogil:
             const shared_ptr[CSchema]& schema,
             const vector[shared_ptr[CRecordBatch]]& batches)
 
+        CResult[shared_ptr[CTensor]] ToTensor(c_bool null_to_nan, c_bool 
row_major,
+                                              CMemoryPool* pool) const
+
         int num_columns()
         int64_t num_rows()
 
diff --git a/python/pyarrow/table.pxi b/python/pyarrow/table.pxi
index 5b3872bd26..fc7c4fcfc8 100644
--- a/python/pyarrow/table.pxi
+++ b/python/pyarrow/table.pxi
@@ -3634,7 +3634,7 @@ cdef class RecordBatch(_Tabular):
         b: [10,20,30,40,null]
 
         Convert a RecordBatch to row-major Tensor with null values
-        written as NaN values
+        written as ``NaN``:
 
         >>> batch.to_tensor(null_to_nan=True)
         <pyarrow.Tensor>
@@ -3648,7 +3648,7 @@ cdef class RecordBatch(_Tabular):
                [ 4., 40.],
                [nan, nan]])
 
-        Convert a RecordBatch to column-major Tensor
+        Convert a RecordBatch to column-major Tensor:
 
         >>> batch.to_tensor(null_to_nan=True, row_major=False)
         <pyarrow.Tensor>
@@ -3664,15 +3664,11 @@ cdef class RecordBatch(_Tabular):
         """
         self._assert_cpu()
         cdef:
-            shared_ptr[CRecordBatch] c_record_batch
             shared_ptr[CTensor] c_tensor
             CMemoryPool* pool = maybe_unbox_memory_pool(memory_pool)
 
-        c_record_batch = pyarrow_unwrap_batch(self)
         with nogil:
-            c_tensor = GetResultValue(
-                
<CResult[shared_ptr[CTensor]]>deref(c_record_batch).ToTensor(null_to_nan,
-                                                                             
row_major, pool))
+            c_tensor = GetResultValue(self.batch.ToTensor(null_to_nan, 
row_major, pool))
         return pyarrow_wrap_tensor(c_tensor)
 
     def copy_to(self, destination):
@@ -4994,7 +4990,7 @@ cdef class Table(_Tabular):
         animals: string
         ----
         n_legs: [[2,4,5,100],[2,4,5,100]]
-        animals: [["Flamingo","Horse","Brittle 
stars","Centipede"],["Flamingo","Horse","Brittle stars","Centipede"]]
+        animals: [["Flamingo",...,"Centipede"],["Flamingo",...,"Centipede"]]
         """
         cdef:
             vector[shared_ptr[CRecordBatch]] c_batches
@@ -5089,6 +5085,81 @@ cdef class Table(_Tabular):
 
         return result
 
+    def to_tensor(self, c_bool null_to_nan=False, c_bool row_major=True, 
MemoryPool memory_pool=None):
+        """
+        Convert to a :class:`~pyarrow.Tensor`.
+
+        Tables that can be converted have fields of type signed or unsigned 
integer or float,
+        including all bit-widths.
+
+        ``null_to_nan`` is ``False`` by default and this method will raise an 
error in case
+        any nulls are present. Tables with nulls can be converted with 
``null_to_nan`` set to
+        ``True``. In this case null values are converted to ``NaN`` and 
integer type arrays are
+        promoted to the appropriate float type.
+
+        Parameters
+        ----------
+        null_to_nan : bool, default False
+            Whether to write null values in the result as ``NaN``.
+        row_major : bool, default True
+            Whether resulting Tensor is row-major or column-major
+        memory_pool : MemoryPool, default None
+            For memory allocations, if required, otherwise use default pool
+
+        Examples
+        --------
+        >>> import pyarrow as pa
+        >>> table = pa.table(
+        ...    [
+        ...       pa.chunked_array([[1, 2], [3, 4, None]], type=pa.int32()),
+        ...       pa.chunked_array([[10, 20, 30], [40, None]], 
type=pa.float32()),
+        ...    ], names = ["a", "b"]
+        ... )
+
+        >>> table
+        pyarrow.Table
+        a: int32
+        b: float
+        ----
+        a: [[1,2],[3,4,null]]
+        b: [[10,20,30],[40,null]]
+
+        Convert a Table to row-major Tensor with null values written as 
``NaN``:
+
+        >>> table.to_tensor(null_to_nan=True)
+        <pyarrow.Tensor>
+        type: double
+        shape: (5, 2)
+        strides: (16, 8)
+        >>> table.to_tensor(null_to_nan=True).to_numpy()
+        array([[ 1., 10.],
+               [ 2., 20.],
+               [ 3., 30.],
+               [ 4., 40.],
+               [nan, nan]])
+
+        Convert a Table to column-major Tensor
+
+        >>> table.to_tensor(null_to_nan=True, row_major=False)
+        <pyarrow.Tensor>
+        type: double
+        shape: (5, 2)
+        strides: (8, 40)
+        >>> table.to_tensor(null_to_nan=True, row_major=False).to_numpy()
+        array([[ 1., 10.],
+               [ 2., 20.],
+               [ 3., 30.],
+               [ 4., 40.],
+               [nan, nan]])
+        """
+        self._assert_cpu()
+        cdef:
+            shared_ptr[CTensor] c_tensor
+            CMemoryPool* pool = maybe_unbox_memory_pool(memory_pool)
+        with nogil:
+            c_tensor = GetResultValue(self.table.ToTensor(null_to_nan, 
row_major, pool))
+        return pyarrow_wrap_tensor(c_tensor)
+
     def to_reader(self, max_chunksize=None):
         """
         Convert the Table to a RecordBatchReader.
diff --git a/python/pyarrow/tests/test_table.py 
b/python/pyarrow/tests/test_table.py
index b267486fca..cb010f4387 100644
--- a/python/pyarrow/tests/test_table.py
+++ b/python/pyarrow/tests/test_table.py
@@ -998,7 +998,7 @@ def check_tensors(tensor, expected_tensor, type, size):
 @pytest.mark.parametrize('typ_str', [
     "uint8", "uint16", "uint32", "uint64",
     "int8", "int16", "int32", "int64",
-    "float32", "float64",
+    "float16", "float32", "float64",
 ])
 def test_recordbatch_to_tensor_uniform_type(typ_str):
     typ = np.dtype(typ_str)
@@ -1056,61 +1056,44 @@ def test_recordbatch_to_tensor_uniform_type(typ_str):
 
 
 @pytest.mark.numpy
-def test_recordbatch_to_tensor_uniform_float_16():
-    arr1 = [1, 2, 3, 4, 5, 6, 7, 8, 9]
-    arr2 = [10, 20, 30, 40, 50, 60, 70, 80, 90]
-    arr3 = [100, 100, 100, 100, 100, 100, 100, 100, 100]
-    batch = pa.RecordBatch.from_arrays(
-        [
-            pa.array(np.array(arr1, dtype=np.float16), type=pa.float16()),
-            pa.array(np.array(arr2, dtype=np.float16), type=pa.float16()),
-            pa.array(np.array(arr3, dtype=np.float16), type=pa.float16()),
-        ], ["a", "b", "c"]
-    )
-
-    result = batch.to_tensor(row_major=False)
-    x = np.column_stack([arr1, arr2, arr3]).astype(np.float16, order="F")
-    expected = pa.Tensor.from_numpy(x)
-    check_tensors(result, expected, pa.float16(), 27)
-
-    result = batch.to_tensor()
-    x = np.column_stack([arr1, arr2, arr3]).astype(np.float16, order="C")
-    expected = pa.Tensor.from_numpy(x)
-    check_tensors(result, expected, pa.float16(), 27)
-
-
[email protected]
-def test_recordbatch_to_tensor_mixed_type():
[email protected](
+    ('cls'),
+    [
+        (pa.Table),
+        (pa.RecordBatch)
+    ]
+)
+def test_to_tensor_mixed_type(cls):
     # uint16 + int16 = int32
     arr1 = [1, 2, 3, 4, 5, 6, 7, 8, 9]
     arr2 = [10, 20, 30, 40, 50, 60, 70, 80, 90]
     arr3 = [100, 200, 300, np.nan, 500, 600, 700, 800, 900]
-    batch = pa.RecordBatch.from_arrays(
+    tabular = cls.from_arrays(
         [
             pa.array(arr1, type=pa.uint16()),
             pa.array(arr2, type=pa.int16()),
         ], ["a", "b"]
     )
 
-    result = batch.to_tensor(row_major=False)
+    result = tabular.to_tensor(row_major=False)
     x = np.column_stack([arr1, arr2]).astype(np.int32, order="F")
     expected = pa.Tensor.from_numpy(x)
     check_tensors(result, expected, pa.int32(), 18)
 
-    result = batch.to_tensor()
+    result = tabular.to_tensor()
     x = np.column_stack([arr1, arr2]).astype(np.int32, order="C")
     expected = pa.Tensor.from_numpy(x)
     check_tensors(result, expected, pa.int32(), 18)
 
     # uint16 + int16 + float32 = float64
-    batch = pa.RecordBatch.from_arrays(
+    tabular = cls.from_arrays(
         [
             pa.array(arr1, type=pa.uint16()),
             pa.array(arr2, type=pa.int16()),
             pa.array(arr3, type=pa.float32()),
         ], ["a", "b", "c"]
     )
-    result = batch.to_tensor(row_major=False)
+    result = tabular.to_tensor(row_major=False)
     x = np.column_stack([arr1, arr2, arr3]).astype(np.float64, order="F")
     expected = pa.Tensor.from_numpy(x)
 
@@ -1120,7 +1103,7 @@ def test_recordbatch_to_tensor_mixed_type():
     assert result.shape == expected.shape
     assert result.strides == expected.strides
 
-    result = batch.to_tensor()
+    result = tabular.to_tensor()
     x = np.column_stack([arr1, arr2, arr3]).astype(np.float64, order="C")
     expected = pa.Tensor.from_numpy(x)
 
@@ -1184,7 +1167,7 @@ def test_recordbatch_to_tensor_null():
     )
     with pytest.raises(
         pa.ArrowTypeError,
-        match="Can only convert a RecordBatch with no nulls."
+        match="Can only convert a Table or RecordBatch with no nulls."
     ):
         batch.to_tensor()
 
@@ -1269,6 +1252,70 @@ def test_recordbatch_to_tensor_unsupported():
         batch.to_tensor()
 
 
[email protected]
[email protected]('typ_str', [
+    "uint8", "uint16", "uint32", "uint64",
+    "int8", "int16", "int32", "int64",
+    "float16", "float32", "float64",
+])
+def test_table_to_tensor_uniform_type(typ_str):
+    arr1 = [[1, 2, 3], [4, 5, 6, 7, 8, 9]]
+    arr2 = [[10, 20], [30, 40, 50, 60, 70, 80, 90]]
+    arr3 = [[100, 100, 100, 100, 100, 100], [100, 100, 100]]
+    table = pa.Table.from_arrays(
+        [
+            pa.chunked_array(arr1, type=pa.from_numpy_dtype(typ_str)),
+            pa.chunked_array(arr2, type=pa.from_numpy_dtype(typ_str)),
+            pa.chunked_array(arr3, type=pa.from_numpy_dtype(typ_str)),
+        ], ["a", "b", "c"]
+    )
+
+    arr1_f = [1, 2, 3, 4, 5, 6, 7, 8, 9]
+    arr2_f = [10, 20, 30, 40, 50, 60, 70, 80, 90]
+    arr3_f = [100, 100, 100, 100, 100, 100, 100, 100, 100]
+
+    result = table.to_tensor(row_major=False)
+    x = np.column_stack([arr1_f, arr2_f, arr3_f]).astype(typ_str, order="F")
+    expected = pa.Tensor.from_numpy(x)
+    check_tensors(result, expected, pa.from_numpy_dtype(typ_str), 27)
+
+    result = table.to_tensor()
+    x = np.column_stack([arr1_f, arr2_f, arr3_f]).astype(typ_str, order="C")
+    expected = pa.Tensor.from_numpy(x)
+    check_tensors(result, expected, pa.from_numpy_dtype(typ_str), 27)
+
+    # Test offset
+    table1 = table.slice(1)
+    arr1_f = [2, 3, 4, 5, 6, 7, 8, 9]
+    arr2_f = [20, 30, 40, 50, 60, 70, 80, 90]
+    arr3_f = [100, 100, 100, 100, 100, 100, 100, 100]
+
+    result = table1.to_tensor(row_major=False)
+    x = np.column_stack([arr1_f, arr2_f, arr3_f]).astype(typ_str, order="F")
+    expected = pa.Tensor.from_numpy(x)
+    check_tensors(result, expected, pa.from_numpy_dtype(typ_str), 24)
+
+    result = table1.to_tensor()
+    x = np.column_stack([arr1_f, arr2_f, arr3_f]).astype(typ_str, order="C")
+    expected = pa.Tensor.from_numpy(x)
+    check_tensors(result, expected, pa.from_numpy_dtype(typ_str), 24)
+
+    table2 = table.slice(1, 5)
+    arr1_f = [2, 3, 4, 5, 6]
+    arr2_f = [20, 30, 40, 50, 60]
+    arr3_f = [100, 100, 100, 100, 100]
+
+    result = table2.to_tensor(row_major=False)
+    x = np.column_stack([arr1_f, arr2_f, arr3_f]).astype(typ_str, order="F")
+    expected = pa.Tensor.from_numpy(x)
+    check_tensors(result, expected, pa.from_numpy_dtype(typ_str), 15)
+
+    result = table2.to_tensor()
+    x = np.column_stack([arr1_f, arr2_f, arr3_f]).astype(typ_str, order="C")
+    expected = pa.Tensor.from_numpy(x)
+    check_tensors(result, expected, pa.from_numpy_dtype(typ_str), 15)
+
+
 def _table_like_slice_tests(factory):
     data = [
         pa.array(range(5)),

Reply via email to