001/* 002 * Licensed to the Apache Software Foundation (ASF) under one or more 003 * contributor license agreements. See the NOTICE file distributed with 004 * this work for additional information regarding copyright ownership. 005 * The ASF licenses this file to You under the Apache License, Version 2.0 006 * (the "License"); you may not use this file except in compliance with 007 * the License. You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the License for the specific language governing permissions and 015 * limitations under the License. 016 */ 017package org.apache.juneau.marshall.parquet; 018 019import static org.apache.juneau.commons.utils.Shorts.*; 020 021import java.io.*; 022 023/** 024 * A Parquet schema element representing a field in the Parquet schema. 025 * 026 * <p> 027 * Maps to the SchemaElement struct in the 028 * <a class="doclink" href="https://github.com/apache/parquet-format/blob/master/src/main/thrift/parquet.thrift">parquet.thrift</a> 029 * specification. Used for both primitive (leaf) and group (non-leaf) nodes. 030 * 031 * <h5 class='section'>Parquet Type Enum (Type):</h5> 032 * <ul> 033 * <li>BOOLEAN = 0</li> 034 * <li>INT32 = 1</li> 035 * <li>INT64 = 2</li> 036 * <li>FLOAT = 4</li> 037 * <li>DOUBLE = 5</li> 038 * <li>BYTE_ARRAY = 6</li> 039 * <li>FIXED_LEN_BYTE_ARRAY = 7</li> 040 * </ul> 041 * 042 * <h5 class='section'>Field Repetition Type:</h5> 043 * <ul> 044 * <li>REQUIRED = 0</li> 045 * <li>OPTIONAL = 1</li> 046 * <li>REPEATED = 2</li> 047 * </ul> 048 * 049 * <h5 class='section'>Converted Type (common values):</h5> 050 * <ul> 051 * <li>UTF8 = 0, ENUM = 4, DECIMAL = 5, TIMESTAMP_MILLIS = 9, INT_8 = 15, INT_16 = 16, INT_32 = 17, INT_64 = 18, MAP = 1, LIST = 3</li> 052 * </ul> 053 */ 054public final class ParquetSchemaElement { 055 056 /** Parquet physical type: BOOLEAN. */ 057 public static final int TYPE_BOOLEAN = 0; 058 /** Parquet physical type: INT32. */ 059 public static final int TYPE_INT32 = 1; 060 /** Parquet physical type: INT64. */ 061 public static final int TYPE_INT64 = 2; 062 /** Parquet physical type: INT96 (legacy 12-byte timestamp; read-only). */ 063 public static final int TYPE_INT96 = 3; 064 /** Parquet physical type: FLOAT. */ 065 public static final int TYPE_FLOAT = 4; 066 /** Parquet physical type: DOUBLE. */ 067 public static final int TYPE_DOUBLE = 5; 068 /** Parquet physical type: BYTE_ARRAY. */ 069 public static final int TYPE_BYTE_ARRAY = 6; 070 /** Parquet physical type: FIXED_LEN_BYTE_ARRAY. */ 071 public static final int TYPE_FIXED_LEN_BYTE_ARRAY = 7; 072 073 /** Field repetition type: REQUIRED (parquet.thrift FieldRepetitionType). */ 074 public static final int REQUIRED = 0; 075 /** Field repetition type: OPTIONAL (parquet.thrift FieldRepetitionType). */ 076 public static final int OPTIONAL = 1; 077 /** Field repetition type: REPEATED (parquet.thrift FieldRepetitionType). */ 078 public static final int REPEATED = 2; 079 080 /** Converted type: UTF8 (parquet.thrift ConvertedType). */ 081 public static final int CONVERTED_UTF8 = 0; 082 /** Converted type: MAP. */ 083 public static final int CONVERTED_MAP = 1; 084 /** Converted type: LIST. */ 085 public static final int CONVERTED_LIST = 3; 086 /** Converted type: ENUM. */ 087 public static final int CONVERTED_ENUM = 4; 088 /** Converted type: DECIMAL. */ 089 public static final int CONVERTED_DECIMAL = 5; 090 /** Converted type: TIMESTAMP_MILLIS. */ 091 public static final int CONVERTED_TIMESTAMP_MILLIS = 9; 092 /** Converted type: INT_8. */ 093 public static final int CONVERTED_INT_8 = 15; 094 /** Converted type: INT_16. */ 095 public static final int CONVERTED_INT_16 = 16; 096 /** Converted type: INT_32. */ 097 public static final int CONVERTED_INT_32 = 17; 098 /** Converted type: INT_64. */ 099 public static final int CONVERTED_INT_64 = 18; 100 101 /** LogicalType union discriminant for STRING (StringType). */ 102 static final int LOGICAL_TYPE_STRING = 1; 103 /** LogicalType union discriminant for UUID (UUIDType). */ 104 static final int LOGICAL_TYPE_UUID = 14; 105 /** LogicalType union discriminant for TIMESTAMP (TimestampType). */ 106 static final int LOGICAL_TYPE_TIMESTAMP = 8; 107 /** LogicalType union discriminant for DECIMAL (DecimalType). */ 108 static final int LOGICAL_TYPE_DECIMAL = 5; 109 /** LogicalType union discriminant for DATE (DateType). */ 110 static final int LOGICAL_TYPE_DATE = 6; 111 /** LogicalType union discriminant for TIME (TimeType). */ 112 static final int LOGICAL_TYPE_TIME = 7; 113 114 /** TimeUnit union discriminant: MILLIS. */ 115 static final int TIME_UNIT_MILLIS = 1; 116 /** TimeUnit union discriminant: MICROS. */ 117 static final int TIME_UNIT_MICROS = 2; 118 /** TimeUnit union discriminant: NANOS. */ 119 static final int TIME_UNIT_NANOS = 3; 120 121 /** Converted type: DATE (days since epoch). */ 122 public static final int CONVERTED_DATE = 6; 123 /** Converted type: TIME_MILLIS. */ 124 public static final int CONVERTED_TIME_MILLIS = 7; 125 /** Converted type: TIMESTAMP_MICROS. */ 126 public static final int CONVERTED_TIMESTAMP_MICROS = 10; 127 /** Converted type: TIME_MICROS. */ 128 public static final int CONVERTED_TIME_MICROS = 8; 129 130 /** Field name. Required. */ 131 public final String name; 132 /** Parquet physical type. Null for group nodes. */ 133 public final Integer type; 134 /** Byte length for FIXED_LEN_BYTE_ARRAY. */ 135 public final Integer typeLength; 136 /** Repetition type. Null for root. */ 137 public final Integer repetitionType; 138 /** Number of child elements. Non-null for group nodes. */ 139 public final Integer numChildren; 140 /** Deprecated converted type. Used for forward compatibility. */ 141 public final Integer convertedType; 142 /** Logical type discriminant (1=STRING, 5=DECIMAL, 8=TIMESTAMP, 14=UUID, etc.). Null if none. */ 143 public final Integer logicalType; 144 /** Scale for DECIMAL. */ 145 public final Integer scale; 146 /** Precision for DECIMAL. */ 147 public final Integer precision; 148 /** Path from root (e.g. "host" or "db.port") for leaf columns. */ 149 public final String path; 150 151 /** 152 * Creates a schema element. 153 * 154 * @param name Field name (required). 155 * @param type Parquet physical type (null for groups). 156 * @param typeLength Byte length for FIXED_LEN_BYTE_ARRAY (null otherwise). 157 * @param repetitionType REQUIRED/OPTIONAL/REPEATED (null for root). 158 * @param numChildren Child count for groups (null for primitives). 159 * @param convertedType ConvertedType enum value (null if none). 160 * @param logicalType LogicalType union discriminant (null if none). 161 * @param scale Scale for DECIMAL (null otherwise). 162 * @param precision Precision for DECIMAL (null otherwise). 163 * @param path Dotted path for leaf columns (e.g. "db.port"). 164 */ 165 @SuppressWarnings({ 166 "java:S107" // Constructor mirrors Parquet schema fields; refactor would obscure mapping 167 }) 168 public ParquetSchemaElement(String name, Integer type, Integer typeLength, Integer repetitionType, 169 Integer numChildren, Integer convertedType, Integer logicalType, Integer scale, Integer precision, 170 String path) { 171 this.name = name; 172 this.type = type; 173 this.typeLength = typeLength; 174 this.repetitionType = repetitionType; 175 this.numChildren = numChildren; 176 this.convertedType = convertedType; 177 this.logicalType = logicalType; 178 this.scale = scale; 179 this.precision = precision; 180 this.path = or(path, name); 181 } 182 183 /** 184 * Writes this schema element to the Thrift Compact Protocol encoder. 185 * 186 * <p> 187 * When {@code emitLogicalTypes} is <jk>false</jk> (the default) only the physical type and the legacy 188 * {@code convertedType} discriminant are written, matching the historical wire shape. When 189 * <jk>true</jk>, an explicit {@code LogicalType} union (parquet.thrift field 10) is additionally 190 * emitted for ambiguous physical representations. The first-pass scope is UUID only: the one type whose 191 * round-trip currently relies on the {@code FIXED_LEN_BYTE_ARRAY} physical-type signal alone. The 192 * {@code convertedType} field is still written alongside the union so that readers that consume only 193 * the legacy field (including older Juneau versions) continue to work unchanged. 194 * 195 * @param enc The encoder. 196 * @param emitLogicalTypes If <jk>true</jk>, additionally emit the {@code LogicalType} union for UUID columns. 197 * @throws IOException If an I/O error occurs. 198 */ 199 public void writeTo(ThriftCompactEncoder enc, boolean emitLogicalTypes) throws IOException { 200 enc.writeStructBegin(); 201 if (type != null) { 202 enc.writeFieldBegin(ThriftCompactEncoder.I32, 1); 203 enc.writeI32(type); 204 } 205 if (typeLength != null) { 206 enc.writeFieldBegin(ThriftCompactEncoder.I32, 2); 207 enc.writeI32(typeLength); 208 } 209 if (repetitionType != null) { 210 enc.writeFieldBegin(ThriftCompactEncoder.I32, 3); 211 enc.writeI32(repetitionType); 212 } 213 enc.writeFieldBegin(ThriftCompactEncoder.BINARY, 4); 214 enc.writeString(name); 215 if (numChildren != null) { 216 enc.writeFieldBegin(ThriftCompactEncoder.I32, 5); 217 enc.writeI32(numChildren); 218 } 219 if (convertedType != null) { 220 enc.writeFieldBegin(ThriftCompactEncoder.I32, 6); 221 enc.writeI32(convertedType); 222 } 223 if (scale != null) { 224 enc.writeFieldBegin(ThriftCompactEncoder.I32, 7); 225 enc.writeI32(scale); 226 } 227 if (precision != null) { 228 enc.writeFieldBegin(ThriftCompactEncoder.I32, 8); 229 enc.writeI32(precision); 230 } 231 // Opt-in only, UUID-first scope: emit the LogicalType union (field 10) so readers can route 232 // FIXED_LEN_BYTE_ARRAY deterministically instead of relying on the "FLBA == UUID" physical-type 233 // assumption. The union member for UUID is field 14 (UUIDType, an empty struct). Other logical 234 // types (STRING/TIMESTAMP/DECIMAL) are intentionally not emitted yet — they round-trip fine via 235 // convertedType + physical type today. 236 if (emitLogicalTypes && logicalType != null && logicalType == LOGICAL_TYPE_UUID) { 237 enc.writeFieldBegin(ThriftCompactEncoder.STRUCT, 10); 238 enc.writeStructBegin(); 239 enc.writeFieldBegin(ThriftCompactEncoder.STRUCT, LOGICAL_TYPE_UUID); 240 enc.writeStructBegin(); 241 enc.writeStructEnd(); 242 enc.writeStructEnd(); 243 } 244 enc.writeStructEnd(); 245 } 246 247 /** 248 * Returns true if this is a leaf (primitive) column. 249 * 250 * @return <jk>true</jk> if this element has a type and no children. 251 */ 252 public boolean isLeaf() { 253 return type != null && (numChildren == null || numChildren == 0); 254 } 255}