1 // Licensed to the Apache Software Foundation (ASF) under one
2 // or more contributor license agreements.  See the NOTICE file
3 // distributed with this work for additional information
4 // regarding copyright ownership.  The ASF licenses this file
5 // to you under the Apache License, Version 2.0 (the
6 // "License"); you may not use this file except in compliance
7 // with the License.  You may obtain a copy of the License at
8 //
9 //   http://www.apache.org/licenses/LICENSE-2.0
10 //
11 // Unless required by applicable law or agreed to in writing,
12 // software distributed under the License is distributed on an
13 // "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14 // KIND, either express or implied.  See the License for the
15 // specific language governing permissions and limitations
16 // under the License.
17 
18 #include <algorithm>
19 #include <chrono>
20 #include <cstddef>
21 #include <cstdint>
22 #include <iostream>
23 #include <memory>
24 #include <sstream>  // IWYU pragma: keep
25 #include <string>
26 #include <type_traits>
27 #include <vector>
28 
29 #include "arrow/array.h"
30 #include "arrow/pretty_print.h"
31 #include "arrow/record_batch.h"
32 #include "arrow/status.h"
33 #include "arrow/table.h"
34 #include "arrow/type.h"
35 #include "arrow/type_traits.h"
36 #include "arrow/util/checked_cast.h"
37 #include "arrow/util/int_util.h"
38 #include "arrow/util/key_value_metadata.h"
39 #include "arrow/util/string.h"
40 #include "arrow/vendored/datetime.h"
41 #include "arrow/visitor_inline.h"
42 
43 namespace arrow {
44 
45 using internal::checked_cast;
46 
47 class PrettyPrinter {
48  public:
PrettyPrinter(const PrettyPrintOptions & options,std::ostream * sink)49   PrettyPrinter(const PrettyPrintOptions& options, std::ostream* sink)
50       : options_(options), indent_(options.indent), sink_(sink) {}
51 
52   void Write(const char* data);
53   void Write(const std::string& data);
54   void WriteIndented(const char* data);
55   void WriteIndented(const std::string& data);
56   void Newline();
57   void Indent();
58   void OpenArray(const Array& array);
59   void CloseArray(const Array& array);
60 
Flush()61   void Flush() { (*sink_) << std::flush; }
62 
63  protected:
64   const PrettyPrintOptions& options_;
65   int indent_;
66   std::ostream* sink_;
67 };
68 
OpenArray(const Array & array)69 void PrettyPrinter::OpenArray(const Array& array) {
70   Indent();
71   (*sink_) << "[";
72   if (array.length() > 0) {
73     (*sink_) << "\n";
74     indent_ += options_.indent_size;
75   }
76 }
77 
CloseArray(const Array & array)78 void PrettyPrinter::CloseArray(const Array& array) {
79   if (array.length() > 0) {
80     indent_ -= options_.indent_size;
81     Indent();
82   }
83   (*sink_) << "]";
84 }
85 
Write(const char * data)86 void PrettyPrinter::Write(const char* data) { (*sink_) << data; }
Write(const std::string & data)87 void PrettyPrinter::Write(const std::string& data) { (*sink_) << data; }
88 
WriteIndented(const char * data)89 void PrettyPrinter::WriteIndented(const char* data) {
90   Indent();
91   Write(data);
92 }
93 
WriteIndented(const std::string & data)94 void PrettyPrinter::WriteIndented(const std::string& data) {
95   Indent();
96   Write(data);
97 }
98 
Newline()99 void PrettyPrinter::Newline() {
100   if (options_.skip_new_lines) {
101     return;
102   }
103   (*sink_) << "\n";
104   Indent();
105 }
106 
Indent()107 void PrettyPrinter::Indent() {
108   for (int i = 0; i < indent_; ++i) {
109     (*sink_) << " ";
110   }
111 }
112 
113 class ArrayPrinter : public PrettyPrinter {
114  public:
ArrayPrinter(const PrettyPrintOptions & options,std::ostream * sink)115   ArrayPrinter(const PrettyPrintOptions& options, std::ostream* sink)
116       : PrettyPrinter(options, sink) {}
117 
118   template <typename FormatFunction>
WriteValues(const Array & array,FormatFunction && func)119   void WriteValues(const Array& array, FormatFunction&& func) {
120     bool skip_comma = true;
121     for (int64_t i = 0; i < array.length(); ++i) {
122       if (skip_comma) {
123         skip_comma = false;
124       } else {
125         (*sink_) << ",\n";
126       }
127       Indent();
128       if ((i >= options_.window) && (i < (array.length() - options_.window))) {
129         (*sink_) << "...\n";
130         i = array.length() - options_.window - 1;
131         skip_comma = true;
132       } else if (array.IsNull(i)) {
133         (*sink_) << options_.null_rep;
134       } else {
135         func(i);
136       }
137     }
138     (*sink_) << "\n";
139   }
140 
WriteDataValues(const BooleanArray & array)141   Status WriteDataValues(const BooleanArray& array) {
142     WriteValues(array, [&](int64_t i) { Write(array.Value(i) ? "true" : "false"); });
143     return Status::OK();
144   }
145 
146   template <typename T>
WriteDataValues(const T & array)147   enable_if_integer<typename T::TypeClass, Status> WriteDataValues(const T& array) {
148     const auto data = array.raw_values();
149     // Need to upcast integers to avoid selecting operator<<(char)
150     WriteValues(array, [&](int64_t i) { (*sink_) << internal::UpcastInt(data[i]); });
151     return Status::OK();
152   }
153 
154   template <typename T>
WriteDataValues(const T & array)155   enable_if_floating_point<typename T::TypeClass, Status> WriteDataValues(
156       const T& array) {
157     const auto data = array.raw_values();
158     WriteValues(array, [&](int64_t i) { (*sink_) << data[i]; });
159     return Status::OK();
160   }
161 
162   template <typename T>
WriteDataValues(const T & array)163   enable_if_date<typename T::TypeClass, Status> WriteDataValues(const T& array) {
164     const auto data = array.raw_values();
165     using unit = typename std::conditional<std::is_same<T, Date32Array>::value,
166                                            arrow_vendored::date::days,
167                                            std::chrono::milliseconds>::type;
168     WriteValues(array, [&](int64_t i) { FormatDateTime<unit>("%F", data[i], true); });
169     return Status::OK();
170   }
171 
172   template <typename T>
WriteDataValues(const T & array)173   enable_if_time<typename T::TypeClass, Status> WriteDataValues(const T& array) {
174     const auto data = array.raw_values();
175     const auto type = static_cast<const TimeType*>(array.type().get());
176     WriteValues(array,
177                 [&](int64_t i) { FormatDateTime(type->unit(), "%T", data[i], false); });
178     return Status::OK();
179   }
180 
WriteDataValues(const TimestampArray & array)181   Status WriteDataValues(const TimestampArray& array) {
182     const int64_t* data = array.raw_values();
183     const auto type = static_cast<const TimestampType*>(array.type().get());
184     WriteValues(array,
185                 [&](int64_t i) { FormatDateTime(type->unit(), "%F %T", data[i], true); });
186     return Status::OK();
187   }
188 
189   template <typename T>
WriteDataValues(const T & array)190   enable_if_duration<typename T::TypeClass, Status> WriteDataValues(const T& array) {
191     const auto data = array.raw_values();
192     WriteValues(array, [&](int64_t i) { (*sink_) << data[i]; });
193     return Status::OK();
194   }
195 
WriteDataValues(const DayTimeIntervalArray & array)196   Status WriteDataValues(const DayTimeIntervalArray& array) {
197     WriteValues(array, [&](int64_t i) {
198       auto day_millis = array.GetValue(i);
199       (*sink_) << day_millis.days << "d" << day_millis.milliseconds << "ms";
200     });
201     return Status::OK();
202   }
203 
WriteDataValues(const MonthIntervalArray & array)204   Status WriteDataValues(const MonthIntervalArray& array) {
205     const auto data = array.raw_values();
206     WriteValues(array, [&](int64_t i) { (*sink_) << data[i]; });
207     return Status::OK();
208   }
209 
210   template <typename T>
WriteDataValues(const T & array)211   enable_if_string_like<typename T::TypeClass, Status> WriteDataValues(const T& array) {
212     WriteValues(array, [&](int64_t i) { (*sink_) << "\"" << array.GetView(i) << "\""; });
213     return Status::OK();
214   }
215 
216   // Binary
217   template <typename T>
WriteDataValues(const T & array)218   enable_if_binary_like<typename T::TypeClass, Status> WriteDataValues(const T& array) {
219     WriteValues(array, [&](int64_t i) { (*sink_) << HexEncode(array.GetView(i)); });
220     return Status::OK();
221   }
222 
WriteDataValues(const Decimal128Array & array)223   Status WriteDataValues(const Decimal128Array& array) {
224     WriteValues(array, [&](int64_t i) { (*sink_) << array.FormatValue(i); });
225     return Status::OK();
226   }
227 
228   template <typename T>
WriteDataValues(const T & array)229   enable_if_list_like<typename T::TypeClass, Status> WriteDataValues(const T& array) {
230     bool skip_comma = true;
231     for (int64_t i = 0; i < array.length(); ++i) {
232       if (skip_comma) {
233         skip_comma = false;
234       } else {
235         (*sink_) << ",\n";
236       }
237       if ((i >= options_.window) && (i < (array.length() - options_.window))) {
238         Indent();
239         (*sink_) << "...\n";
240         i = array.length() - options_.window - 1;
241         skip_comma = true;
242       } else if (array.IsNull(i)) {
243         Indent();
244         (*sink_) << options_.null_rep;
245       } else {
246         std::shared_ptr<Array> slice =
247             array.values()->Slice(array.value_offset(i), array.value_length(i));
248         RETURN_NOT_OK(PrettyPrint(*slice, {indent_, options_.window}, sink_));
249       }
250     }
251     (*sink_) << "\n";
252     return Status::OK();
253   }
254 
WriteDataValues(const MapArray & array)255   Status WriteDataValues(const MapArray& array) {
256     bool skip_comma = true;
257     for (int64_t i = 0; i < array.length(); ++i) {
258       if (skip_comma) {
259         skip_comma = false;
260       } else {
261         (*sink_) << ",\n";
262       }
263       if ((i >= options_.window) && (i < (array.length() - options_.window))) {
264         Indent();
265         (*sink_) << "...\n";
266         i = array.length() - options_.window - 1;
267         skip_comma = true;
268       } else if (array.IsNull(i)) {
269         Indent();
270         (*sink_) << options_.null_rep;
271       } else {
272         Indent();
273         (*sink_) << "keys:\n";
274         auto keys_slice =
275             array.keys()->Slice(array.value_offset(i), array.value_length(i));
276         RETURN_NOT_OK(PrettyPrint(*keys_slice, {indent_, options_.window}, sink_));
277         (*sink_) << "\n";
278         Indent();
279         (*sink_) << "values:\n";
280         auto values_slice =
281             array.items()->Slice(array.value_offset(i), array.value_length(i));
282         RETURN_NOT_OK(PrettyPrint(*values_slice, {indent_, options_.window}, sink_));
283       }
284     }
285     (*sink_) << "\n";
286     return Status::OK();
287   }
288 
Visit(const NullArray & array)289   Status Visit(const NullArray& array) {
290     (*sink_) << array.length() << " nulls";
291     return Status::OK();
292   }
293 
294   template <typename T>
295   enable_if_t<std::is_base_of<PrimitiveArray, T>::value ||
296                   std::is_base_of<FixedSizeBinaryArray, T>::value ||
297                   std::is_base_of<BinaryArray, T>::value ||
298                   std::is_base_of<LargeBinaryArray, T>::value ||
299                   std::is_base_of<ListArray, T>::value ||
300                   std::is_base_of<LargeListArray, T>::value ||
301                   std::is_base_of<MapArray, T>::value ||
302                   std::is_base_of<FixedSizeListArray, T>::value,
303               Status>
Visit(const T & array)304   Visit(const T& array) {
305     OpenArray(array);
306     if (array.length() > 0) {
307       RETURN_NOT_OK(WriteDataValues(array));
308     }
309     CloseArray(array);
310     return Status::OK();
311   }
312 
Visit(const ExtensionArray & array)313   Status Visit(const ExtensionArray& array) { return Print(*array.storage()); }
314 
315   Status WriteValidityBitmap(const Array& array);
316 
PrintChildren(const std::vector<std::shared_ptr<Array>> & fields,int64_t offset,int64_t length)317   Status PrintChildren(const std::vector<std::shared_ptr<Array>>& fields, int64_t offset,
318                        int64_t length) {
319     for (size_t i = 0; i < fields.size(); ++i) {
320       Newline();
321       std::stringstream ss;
322       ss << "-- child " << i << " type: " << fields[i]->type()->ToString() << "\n";
323       Write(ss.str());
324 
325       std::shared_ptr<Array> field = fields[i];
326       if (offset != 0) {
327         field = field->Slice(offset, length);
328       }
329 
330       RETURN_NOT_OK(PrettyPrint(*field, indent_ + options_.indent_size, sink_));
331     }
332     return Status::OK();
333   }
334 
Visit(const StructArray & array)335   Status Visit(const StructArray& array) {
336     RETURN_NOT_OK(WriteValidityBitmap(array));
337     std::vector<std::shared_ptr<Array>> children;
338     children.reserve(array.num_fields());
339     for (int i = 0; i < array.num_fields(); ++i) {
340       children.emplace_back(array.field(i));
341     }
342     return PrintChildren(children, 0, array.length());
343   }
344 
Visit(const UnionArray & array)345   Status Visit(const UnionArray& array) {
346     RETURN_NOT_OK(WriteValidityBitmap(array));
347 
348     Newline();
349     Write("-- type_ids: ");
350     UInt8Array type_codes(array.length(), array.type_codes(), nullptr, 0, array.offset());
351     RETURN_NOT_OK(PrettyPrint(type_codes, indent_ + options_.indent_size, sink_));
352 
353     if (array.mode() == UnionMode::DENSE) {
354       Newline();
355       Write("-- value_offsets: ");
356       Int32Array value_offsets(array.length(), array.value_offsets(), nullptr, 0,
357                                array.offset());
358       RETURN_NOT_OK(PrettyPrint(value_offsets, indent_ + options_.indent_size, sink_));
359     }
360 
361     // Print the children without any offset, because the type ids are absolute
362     std::vector<std::shared_ptr<Array>> children;
363     children.reserve(array.num_fields());
364     for (int i = 0; i < array.num_fields(); ++i) {
365       children.emplace_back(array.field(i));
366     }
367     return PrintChildren(children, 0, array.length() + array.offset());
368   }
369 
Visit(const DictionaryArray & array)370   Status Visit(const DictionaryArray& array) {
371     Newline();
372     Write("-- dictionary:\n");
373     RETURN_NOT_OK(
374         PrettyPrint(*array.dictionary(), indent_ + options_.indent_size, sink_));
375 
376     Newline();
377     Write("-- indices:\n");
378     return PrettyPrint(*array.indices(), indent_ + options_.indent_size, sink_);
379   }
380 
Print(const Array & array)381   Status Print(const Array& array) {
382     RETURN_NOT_OK(VisitArrayInline(array, this));
383     Flush();
384     return Status::OK();
385   }
386 
387  private:
388   template <typename Unit>
FormatDateTime(const char * fmt,int64_t value,bool add_epoch)389   void FormatDateTime(const char* fmt, int64_t value, bool add_epoch) {
390     if (add_epoch) {
391       (*sink_) << arrow_vendored::date::format(fmt, epoch_ + Unit{value});
392     } else {
393       (*sink_) << arrow_vendored::date::format(fmt, Unit{value});
394     }
395   }
396 
FormatDateTime(TimeUnit::type unit,const char * fmt,int64_t value,bool add_epoch)397   void FormatDateTime(TimeUnit::type unit, const char* fmt, int64_t value,
398                       bool add_epoch) {
399     switch (unit) {
400       case TimeUnit::NANO:
401         FormatDateTime<std::chrono::nanoseconds>(fmt, value, add_epoch);
402         break;
403       case TimeUnit::MICRO:
404         FormatDateTime<std::chrono::microseconds>(fmt, value, add_epoch);
405         break;
406       case TimeUnit::MILLI:
407         FormatDateTime<std::chrono::milliseconds>(fmt, value, add_epoch);
408         break;
409       case TimeUnit::SECOND:
410         FormatDateTime<std::chrono::seconds>(fmt, value, add_epoch);
411         break;
412     }
413   }
414 
415   static arrow_vendored::date::sys_days epoch_;
416 };
417 
418 arrow_vendored::date::sys_days ArrayPrinter::epoch_ =
419     arrow_vendored::date::sys_days{arrow_vendored::date::jan / 1 / 1970};
420 
WriteValidityBitmap(const Array & array)421 Status ArrayPrinter::WriteValidityBitmap(const Array& array) {
422   Indent();
423   Write("-- is_valid:");
424 
425   if (array.null_count() > 0) {
426     Newline();
427     BooleanArray is_valid(array.length(), array.null_bitmap(), nullptr, 0,
428                           array.offset());
429     return PrettyPrint(is_valid, indent_ + options_.indent_size, sink_);
430   } else {
431     Write(" all not null");
432     return Status::OK();
433   }
434 }
435 
PrettyPrint(const Array & arr,int indent,std::ostream * sink)436 Status PrettyPrint(const Array& arr, int indent, std::ostream* sink) {
437   PrettyPrintOptions options;
438   options.indent = indent;
439   ArrayPrinter printer(options, sink);
440   return printer.Print(arr);
441 }
442 
PrettyPrint(const Array & arr,const PrettyPrintOptions & options,std::ostream * sink)443 Status PrettyPrint(const Array& arr, const PrettyPrintOptions& options,
444                    std::ostream* sink) {
445   ArrayPrinter printer(options, sink);
446   return printer.Print(arr);
447 }
448 
PrettyPrint(const Array & arr,const PrettyPrintOptions & options,std::string * result)449 Status PrettyPrint(const Array& arr, const PrettyPrintOptions& options,
450                    std::string* result) {
451   std::ostringstream sink;
452   RETURN_NOT_OK(PrettyPrint(arr, options, &sink));
453   *result = sink.str();
454   return Status::OK();
455 }
456 
PrettyPrint(const ChunkedArray & chunked_arr,const PrettyPrintOptions & options,std::ostream * sink)457 Status PrettyPrint(const ChunkedArray& chunked_arr, const PrettyPrintOptions& options,
458                    std::ostream* sink) {
459   int num_chunks = chunked_arr.num_chunks();
460   int indent = options.indent;
461   int window = options.window;
462 
463   for (int i = 0; i < indent; ++i) {
464     (*sink) << " ";
465   }
466   (*sink) << "[\n";
467   bool skip_comma = true;
468   for (int i = 0; i < num_chunks; ++i) {
469     if (skip_comma) {
470       skip_comma = false;
471     } else {
472       (*sink) << ",\n";
473     }
474     if ((i >= window) && (i < (num_chunks - window))) {
475       for (int i = 0; i < indent; ++i) {
476         (*sink) << " ";
477       }
478       (*sink) << "...\n";
479       i = num_chunks - window - 1;
480       skip_comma = true;
481     } else {
482       PrettyPrintOptions chunk_options = options;
483       chunk_options.indent += options.indent_size;
484       ArrayPrinter printer(chunk_options, sink);
485       RETURN_NOT_OK(printer.Print(*chunked_arr.chunk(i)));
486     }
487   }
488   (*sink) << "\n";
489 
490   for (int i = 0; i < indent; ++i) {
491     (*sink) << " ";
492   }
493   (*sink) << "]";
494 
495   return Status::OK();
496 }
497 
PrettyPrint(const ChunkedArray & chunked_arr,const PrettyPrintOptions & options,std::string * result)498 Status PrettyPrint(const ChunkedArray& chunked_arr, const PrettyPrintOptions& options,
499                    std::string* result) {
500   std::ostringstream sink;
501   RETURN_NOT_OK(PrettyPrint(chunked_arr, options, &sink));
502   *result = sink.str();
503   return Status::OK();
504 }
505 
PrettyPrint(const RecordBatch & batch,int indent,std::ostream * sink)506 Status PrettyPrint(const RecordBatch& batch, int indent, std::ostream* sink) {
507   for (int i = 0; i < batch.num_columns(); ++i) {
508     const std::string& name = batch.column_name(i);
509     (*sink) << name << ": ";
510     RETURN_NOT_OK(PrettyPrint(*batch.column(i), indent + 2, sink));
511     (*sink) << "\n";
512   }
513   (*sink) << std::flush;
514   return Status::OK();
515 }
516 
PrettyPrint(const RecordBatch & batch,const PrettyPrintOptions & options,std::ostream * sink)517 Status PrettyPrint(const RecordBatch& batch, const PrettyPrintOptions& options,
518                    std::ostream* sink) {
519   for (int i = 0; i < batch.num_columns(); ++i) {
520     const std::string& name = batch.column_name(i);
521     PrettyPrintOptions column_options = options;
522     column_options.indent += 2;
523 
524     (*sink) << name << ": ";
525     RETURN_NOT_OK(PrettyPrint(*batch.column(i), column_options, sink));
526     (*sink) << "\n";
527   }
528   (*sink) << std::flush;
529   return Status::OK();
530 }
531 
PrettyPrint(const Table & table,const PrettyPrintOptions & options,std::ostream * sink)532 Status PrettyPrint(const Table& table, const PrettyPrintOptions& options,
533                    std::ostream* sink) {
534   RETURN_NOT_OK(PrettyPrint(*table.schema(), options, sink));
535   (*sink) << "\n";
536   (*sink) << "----\n";
537 
538   PrettyPrintOptions column_options = options;
539   column_options.indent += 2;
540   for (int i = 0; i < table.num_columns(); ++i) {
541     for (int j = 0; j < options.indent; ++j) {
542       (*sink) << " ";
543     }
544     (*sink) << table.schema()->field(i)->name() << ":\n";
545     RETURN_NOT_OK(PrettyPrint(*table.column(i), column_options, sink));
546     (*sink) << "\n";
547   }
548   (*sink) << std::flush;
549   return Status::OK();
550 }
551 
DebugPrint(const Array & arr,int indent)552 Status DebugPrint(const Array& arr, int indent) {
553   return PrettyPrint(arr, indent, &std::cout);
554 }
555 
556 class SchemaPrinter : public PrettyPrinter {
557  public:
SchemaPrinter(const Schema & schema,const PrettyPrintOptions & options,std::ostream * sink)558   SchemaPrinter(const Schema& schema, const PrettyPrintOptions& options,
559                 std::ostream* sink)
560       : PrettyPrinter(options, sink), schema_(schema) {}
561 
562   Status PrintType(const DataType& type, bool nullable);
563   Status PrintField(const Field& field);
564 
PrintVerboseMetadata(const KeyValueMetadata & metadata)565   void PrintVerboseMetadata(const KeyValueMetadata& metadata) {
566     for (int64_t i = 0; i < metadata.size(); ++i) {
567       Newline();
568       Write(metadata.key(i) + ": '" + metadata.value(i) + "'");
569     }
570   }
571 
PrintTruncatedMetadata(const KeyValueMetadata & metadata)572   void PrintTruncatedMetadata(const KeyValueMetadata& metadata) {
573     for (int64_t i = 0; i < metadata.size(); ++i) {
574       Newline();
575       size_t size = metadata.value(i).size();
576       size_t truncated_size = std::max<size_t>(10, 70 - metadata.key(i).size() - indent_);
577       if (size <= truncated_size) {
578         Write(metadata.key(i) + ": '" + metadata.value(i) + "'");
579         continue;
580       }
581 
582       Write(metadata.key(i) + ": '" + metadata.value(i).substr(0, truncated_size) +
583             "' + " + std::to_string(size - truncated_size));
584     }
585   }
586 
PrintMetadata(const std::string & metadata_type,const KeyValueMetadata & metadata)587   void PrintMetadata(const std::string& metadata_type, const KeyValueMetadata& metadata) {
588     if (metadata.size() > 0) {
589       Newline();
590       Write(metadata_type);
591       if (options_.truncate_metadata) {
592         PrintTruncatedMetadata(metadata);
593       } else {
594         PrintVerboseMetadata(metadata);
595       }
596     }
597   }
598 
Print()599   Status Print() {
600     for (int i = 0; i < schema_.num_fields(); ++i) {
601       if (i > 0) {
602         Newline();
603       } else {
604         Indent();
605       }
606       RETURN_NOT_OK(PrintField(*schema_.field(i)));
607     }
608 
609     if (options_.show_schema_metadata && schema_.metadata() != nullptr) {
610       PrintMetadata("-- schema metadata --", *schema_.metadata());
611     }
612     Flush();
613     return Status::OK();
614   }
615 
616  private:
617   const Schema& schema_;
618 };
619 
PrintType(const DataType & type,bool nullable)620 Status SchemaPrinter::PrintType(const DataType& type, bool nullable) {
621   Write(type.ToString());
622   if (!nullable) {
623     Write(" not null");
624   }
625   for (int i = 0; i < type.num_fields(); ++i) {
626     Newline();
627 
628     std::stringstream ss;
629     ss << "child " << i << ", ";
630 
631     indent_ += options_.indent_size;
632     WriteIndented(ss.str());
633     RETURN_NOT_OK(PrintField(*type.field(i)));
634     indent_ -= options_.indent_size;
635   }
636   return Status::OK();
637 }
638 
PrintField(const Field & field)639 Status SchemaPrinter::PrintField(const Field& field) {
640   Write(field.name());
641   Write(": ");
642   RETURN_NOT_OK(PrintType(*field.type(), field.nullable()));
643 
644   if (options_.show_field_metadata && field.metadata() != nullptr) {
645     indent_ += options_.indent_size;
646     PrintMetadata("-- field metadata --", *field.metadata());
647     indent_ -= options_.indent_size;
648   }
649   return Status::OK();
650 }
651 
PrettyPrint(const Schema & schema,const PrettyPrintOptions & options,std::ostream * sink)652 Status PrettyPrint(const Schema& schema, const PrettyPrintOptions& options,
653                    std::ostream* sink) {
654   SchemaPrinter printer(schema, options, sink);
655   return printer.Print();
656 }
657 
PrettyPrint(const Schema & schema,const PrettyPrintOptions & options,std::string * result)658 Status PrettyPrint(const Schema& schema, const PrettyPrintOptions& options,
659                    std::string* result) {
660   std::ostringstream sink;
661   RETURN_NOT_OK(PrettyPrint(schema, options, &sink));
662   *result = sink.str();
663   return Status::OK();
664 }
665 
666 }  // namespace arrow
667