
org.apache.flink.table.utils.PrintUtils Maven / Gradle / Ivy
/*
* 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.
*/
package org.apache.flink.table.utils;
import org.apache.flink.annotation.Internal;
import org.apache.flink.table.catalog.Column;
import org.apache.flink.table.catalog.ResolvedSchema;
import org.apache.flink.table.data.MapData;
import org.apache.flink.table.data.RowData;
import org.apache.flink.table.data.TimestampData;
import org.apache.flink.table.types.logical.ArrayType;
import org.apache.flink.table.types.logical.BigIntType;
import org.apache.flink.table.types.logical.DecimalType;
import org.apache.flink.table.types.logical.IntType;
import org.apache.flink.table.types.logical.LocalZonedTimestampType;
import org.apache.flink.table.types.logical.LogicalType;
import org.apache.flink.table.types.logical.LogicalTypeRoot;
import org.apache.flink.table.types.logical.MapType;
import org.apache.flink.table.types.logical.RowType;
import org.apache.flink.table.types.logical.SmallIntType;
import org.apache.flink.table.types.logical.TimeType;
import org.apache.flink.table.types.logical.TimestampType;
import org.apache.flink.table.types.logical.TinyIntType;
import org.apache.flink.types.Row;
import org.apache.flink.util.StringUtils;
import com.ibm.icu.lang.UCharacter;
import com.ibm.icu.lang.UProperty;
import javax.annotation.Nullable;
import java.io.PrintWriter;
import java.sql.Time;
import java.sql.Timestamp;
import java.time.Instant;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.ZoneId;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.stream.Stream;
import static org.apache.flink.table.types.logical.utils.LogicalTypeChecks.getPrecision;
import static org.apache.flink.table.utils.TimestampStringUtils.localTimeToUnixDate;
import static org.apache.flink.table.utils.TimestampStringUtils.timeToInternal;
import static org.apache.flink.table.utils.TimestampStringUtils.timestampToString;
import static org.apache.flink.table.utils.TimestampStringUtils.unixTimeToString;
/** Utilities for print formatting. */
@Internal
public class PrintUtils {
// constants for printing
public static final int MAX_COLUMN_WIDTH = 30;
public static final String NULL_COLUMN = "(NULL)";
public static final String ROW_KIND_COLUMN = "op";
private static final String COLUMN_TRUNCATED_FLAG = "...";
private PrintUtils() {}
/**
* Displays the result in a tableau form.
*
* For example:
*
*
* +-------------+---------+-------------+
* | boolean_col | int_col | varchar_col |
* +-------------+---------+-------------+
* | true | 1 | abc |
* | false | 2 | def |
* | (NULL) | (NULL) | (NULL) |
* +-------------+---------+-------------+
* 3 rows in set
*
*/
public static void printAsTableauForm(
ResolvedSchema resolvedSchema,
Iterator it,
PrintWriter printWriter,
ZoneId sessionTimeZone) {
printAsTableauForm(
resolvedSchema,
it,
printWriter,
MAX_COLUMN_WIDTH,
NULL_COLUMN,
false,
false,
sessionTimeZone);
}
/**
* Displays the result in a tableau form.
*
* NOTE: please make sure the data to print is small enough to be stored in java heap
* memory if the column width is derived from content (`deriveColumnWidthByType` is false).
*
*
For example: (printRowKind is true)
*
*
* +----+-------------+---------+-------------+
* | op | boolean_col | int_col | varchar_col |
* +----+-------------+---------+-------------+
* | +I | true | 1 | abc |
* | -U | false | 2 | def |
* | +U | false | 3 | def |
* | -D | (NULL) | (NULL) | (NULL) |
* +----+-------------+---------+-------------+
* 4 rows in set
*
*
* @param resolvedSchema The schema of the data to print
* @param it The iterator for the data to print
* @param printWriter The writer to write to
* @param maxColumnWidth The max width of a column
* @param nullColumn The string representation of a null value
* @param deriveColumnWidthByType A flag to indicate whether the column width is derived from
* type (true) or content (false).
* @param printRowKind A flag to indicate whether print row kind info
* @param sessionTimeZone The time zone of current session.
*/
public static void printAsTableauForm(
ResolvedSchema resolvedSchema,
Iterator it,
PrintWriter printWriter,
int maxColumnWidth,
String nullColumn,
boolean deriveColumnWidthByType,
boolean printRowKind,
ZoneId sessionTimeZone) {
if (!it.hasNext()) {
printWriter.println("Empty set");
printWriter.flush();
return;
}
final List columns = resolvedSchema.getColumns();
String[] columnNames = columns.stream().map(Column::getName).toArray(String[]::new);
if (printRowKind) {
columnNames =
Stream.concat(Stream.of(ROW_KIND_COLUMN), Arrays.stream(columnNames))
.toArray(String[]::new);
}
final int[] colWidths;
if (deriveColumnWidthByType) {
colWidths =
columnWidthsByType(
columns,
maxColumnWidth,
nullColumn,
printRowKind ? ROW_KIND_COLUMN : null);
} else {
final List rows = new ArrayList<>();
final List content = new ArrayList<>();
content.add(columnNames);
while (it.hasNext()) {
Row row = it.next();
rows.add(row);
content.add(
rowToString(
row, nullColumn, printRowKind, resolvedSchema, sessionTimeZone));
}
colWidths = columnWidthsByContent(columnNames, content, maxColumnWidth);
it = rows.iterator();
}
final String borderline = PrintUtils.genBorderLine(colWidths);
// print border line
printWriter.println(borderline);
// print field names
PrintUtils.printSingleRow(colWidths, columnNames, printWriter);
// print border line
printWriter.println(borderline);
long numRows = 0;
while (it.hasNext()) {
String[] cols =
rowToString(
it.next(), nullColumn, printRowKind, resolvedSchema, sessionTimeZone);
// print content
printSingleRow(colWidths, cols, printWriter);
numRows++;
}
// print border line
printWriter.println(borderline);
final String rowTerm = numRows > 1 ? "rows" : "row";
printWriter.println(numRows + " " + rowTerm + " in set");
printWriter.flush();
}
public static String[] rowToString(
Row row, ResolvedSchema resolvedSchema, ZoneId sessionTimeZone) {
return rowToString(row, NULL_COLUMN, false, resolvedSchema, sessionTimeZone);
}
public static String[] rowToString(
Row row,
String nullColumn,
boolean printRowKind,
ResolvedSchema resolvedSchema,
ZoneId sessionTimeZone) {
final int len = printRowKind ? row.getArity() + 1 : row.getArity();
final List fields = new ArrayList<>(len);
if (printRowKind) {
fields.add(row.getKind().shortString());
}
for (int i = 0; i < row.getArity(); i++) {
final Object field = row.getField(i);
final LogicalType fieldType =
resolvedSchema.getColumnDataTypes().get(i).getLogicalType();
if (field == null) {
fields.add(nullColumn);
} else {
fields.add(
StringUtils.arrayAwareToString(
formattedTimestamp(field, fieldType, sessionTimeZone)));
}
}
return fields.toArray(new String[0]);
}
/**
* Normalizes field that contains TIMESTAMP, TIMESTAMP_LTZ and TIME type data.
*
* This method also supports nested type ARRAY, ROW, MAP.
*/
private static Object formattedTimestamp(
Object field, LogicalType fieldType, ZoneId sessionTimeZone) {
final LogicalTypeRoot typeRoot = fieldType.getTypeRoot();
if (field == null) {
return "null";
}
switch (typeRoot) {
case TIMESTAMP_WITHOUT_TIME_ZONE:
case TIMESTAMP_WITH_LOCAL_TIME_ZONE:
return formatTimestampField(field, fieldType, sessionTimeZone);
case TIME_WITHOUT_TIME_ZONE:
return formatTimeField(field);
case ARRAY:
LogicalType elementType = ((ArrayType) fieldType).getElementType();
if (field instanceof List) {
List> array = (List>) field;
Object[] formattedArray = new Object[array.size()];
for (int i = 0; i < array.size(); i++) {
formattedArray[i] =
formattedTimestamp(array.get(i), elementType, sessionTimeZone);
}
return formattedArray;
} else if (field.getClass().isArray()) {
// primitive type
if (field.getClass() == byte[].class) {
byte[] array = (byte[]) field;
Object[] formattedArray = new Object[array.length];
for (int i = 0; i < array.length; i++) {
formattedArray[i] =
formattedTimestamp(array[i], elementType, sessionTimeZone);
}
return formattedArray;
} else if (field.getClass() == short[].class) {
short[] array = (short[]) field;
Object[] formattedArray = new Object[array.length];
for (int i = 0; i < array.length; i++) {
formattedArray[i] =
formattedTimestamp(array[i], elementType, sessionTimeZone);
}
return formattedArray;
} else if (field.getClass() == int[].class) {
int[] array = (int[]) field;
Object[] formattedArray = new Object[array.length];
for (int i = 0; i < array.length; i++) {
formattedArray[i] =
formattedTimestamp(array[i], elementType, sessionTimeZone);
}
return formattedArray;
} else if (field.getClass() == long[].class) {
long[] array = (long[]) field;
Object[] formattedArray = new Object[array.length];
for (int i = 0; i < array.length; i++) {
formattedArray[i] =
formattedTimestamp(array[i], elementType, sessionTimeZone);
}
return formattedArray;
} else if (field.getClass() == float[].class) {
float[] array = (float[]) field;
Object[] formattedArray = new Object[array.length];
for (int i = 0; i < array.length; i++) {
formattedArray[i] =
formattedTimestamp(array[i], elementType, sessionTimeZone);
}
return formattedArray;
} else if (field.getClass() == double[].class) {
double[] array = (double[]) field;
Object[] formattedArray = new Object[array.length];
for (int i = 0; i < array.length; i++) {
formattedArray[i] =
formattedTimestamp(array[i], elementType, sessionTimeZone);
}
return formattedArray;
} else if (field.getClass() == boolean[].class) {
boolean[] array = (boolean[]) field;
Object[] formattedArray = new Object[array.length];
for (int i = 0; i < array.length; i++) {
formattedArray[i] =
formattedTimestamp(array[i], elementType, sessionTimeZone);
}
return formattedArray;
} else if (field.getClass() == char[].class) {
char[] array = (char[]) field;
Object[] formattedArray = new Object[array.length];
for (int i = 0; i < array.length; i++) {
formattedArray[i] =
formattedTimestamp(array[i], elementType, sessionTimeZone);
}
return formattedArray;
} else {
// non-primitive type
Object[] array = (Object[]) field;
Object[] formattedArray = new Object[array.length];
for (int i = 0; i < array.length; i++) {
formattedArray[i] =
formattedTimestamp(array[i], elementType, sessionTimeZone);
}
return formattedArray;
}
} else {
return field;
}
case ROW:
if (fieldType instanceof RowType && field instanceof Row) {
Row row = (Row) field;
Row formattedRow = new Row(row.getKind(), row.getArity());
for (int i = 0; i < ((RowType) fieldType).getFields().size(); i++) {
LogicalType type = ((RowType) fieldType).getFields().get(i).getType();
formattedRow.setField(
i, formattedTimestamp(row.getField(i), type, sessionTimeZone));
}
return formattedRow;
} else if (fieldType instanceof RowType && field instanceof RowData) {
RowData rowData = (RowData) field;
Row formattedRow = new Row(rowData.getRowKind(), rowData.getArity());
for (int i = 0; i < ((RowType) fieldType).getFields().size(); i++) {
LogicalType type = ((RowType) fieldType).getFields().get(i).getType();
RowData.FieldGetter fieldGetter = RowData.createFieldGetter(type, i);
formattedRow.setField(
i,
formattedTimestamp(
fieldGetter.getFieldOrNull(rowData),
type,
sessionTimeZone));
}
return formattedRow;
} else {
return field;
}
case MAP:
LogicalType keyType = ((MapType) fieldType).getKeyType();
LogicalType valueType = ((MapType) fieldType).getValueType();
if (fieldType instanceof MapType && field instanceof Map) {
Map