Cómo escribir tipo lógico marca de tiempo (INT96) para parquet, utilizando ParquetWriter?

James Willow:

Tengo una herramienta que utiliza una org.apache.parquet.hadoop.ParquetWriter a archivos de datos CSV Convertir a los archivos de datos de parquet.

Actualmente, sólo los mangos int32, doubleystring

Necesito para apoyar el parquet de timestamptipo lógico (anotado como int96), y estoy perdido en la manera de hacer eso porque no puedo encontrar una especificación precisa en línea.

Parece esta codificación de marca de tiempo (int96) es raro y no está bien soportado. He encontrado muy pocos detalles de las especificaciones en línea. Este README github establece que:

Las marcas de tiempo guarda como un int96 se componen de los nanosegundos en el día (primeros 8 bytes) y el día Juliano (últimos 4 bytes).

Específicamente:

  1. ¿Qué Parquet Tipo puedo utilizar para la columna en MessageType esquema? Asumo que debería utilizar el tipo primitivo, PrimitiveTypeName.INT96pero no estoy seguro de si puede haber una manera de especificar un tipo de lógica?
  2. ¿Cómo se escribe los datos? es decir, ¿En qué formato puedo escribir la fecha y hora para el grupo? Para una marca de tiempo INT96, supongo que tengo que escribir algún tipo binario?

Aquí es una versión simplificada de mi código que demuestra lo que estoy tratando de hacer. En concreto, echar un vistazo a los comentarios "todo", estos son los dos puntos en el código que se correlacionan con las preguntas anteriores.

List<Type> fields = new ArrayList<>();
fields.add(new PrimitiveType(Type.Repetition.OPTIONAL, PrimitiveTypeName.INT32, "int32_col", null));
fields.add(new PrimitiveType(Type.Repetition.OPTIONAL, PrimitiveTypeName.DOUBLE, "double_col", null));
fields.add(new PrimitiveType(Type.Repetition.OPTIONAL, PrimitiveTypeName.STRING, "string_col", null));

// TODO: 
//   Specify the TIMESTAMP type. 
//   How? INT96 primitive type? Is there a logical timestamp type I can use w/ MessageType schema?
fields.add(new PrimitiveType(Type.Repetition.OPTIONAL, PrimitiveTypeName.INT96, "timestamp_col", null)); 

MessageType schema = new MessageType("input", fields);

// initialize writer
Configuration configuration = new Configuration();
configuration.setQuietMode(true);
GroupWriteSupport.setSchema(schema, configuration);
ParquetWriter<Group> writer = new ParquetWriter<Group>(
  new Path("output.parquet"),
  new GroupWriteSupport(),
  CompressionCodecName.SNAPPY,
  ParquetWriter.DEFAULT_BLOCK_SIZE,
  ParquetWriter.DEFAULT_PAGE_SIZE,
  1048576,
  true,
  false,
  ParquetProperties.WriterVersion.PARQUET_1_0,
  configuration
);

// write CSV data
CSVParser parser = CSVParser.parse(new File(csv), StandardCharsets.UTF_8, CSVFormat.TDF.withQuote(null));
ArrayList<String> columns = new ArrayList<>(schemaMap.keySet());
int colIndex;
int rowNum = 0;
for (CSVRecord csvRecord : parser) {
  rowNum ++;
  Group group = f.newGroup();
  colIndex = 0;
  for (String record : csvRecord) {
    if (record == null || record.isEmpty() || record.equals( "NULL")) {
      colIndex++;
      continue;
    }


    record = record.trim();
    String type = schemaMap.get(columns.get(colIndex)).get("type").toString();
    MessageTypeConverter.addTypeValueToGroup(type, record, group, colIndex++);

    switch (colIndex) {
      case 0: // int32
        group.add(colIndex, Integer.parseInt(record));
        break;
      case 1: // double
        group.add(colIndex, Double.parseDouble(record));
        break;
      case 2: // string
        group.add(colIndex, record);
        break;
      case 3:
        // TODO: convert CSV string value to TIMESTAMP type (how?)
        throw new NotImplementedException();
    }
  }
  writer.write(group);
}
writer.close();
James Willow:

Lo he descubierto, utilizando el código de SQL chispa como referencia.

La codificación binaria INT96 se divide en 2 partes: En primer lugar 8 bytes son nanosegundos desde la medianoche pasado 4 bytes día Juliano

String value = "2019-02-13 13:35:05";

final long NANOS_PER_HOUR = TimeUnit.HOURS.toNanos(1);
final long NANOS_PER_MINUTE = TimeUnit.MINUTES.toNanos(1);
final long NANOS_PER_SECOND = TimeUnit.SECONDS.toNanos(1);

// Parse date
SimpleDateFormat parser = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
Calendar cal = Calendar.getInstance(TimeZone.getTimeZone("UTC"));
cal.setTime(parser.parse(value));

// Calculate Julian days and nanoseconds in the day
LocalDate dt = LocalDate.of(cal.get(Calendar.YEAR), cal.get(Calendar.MONTH)+1, cal.get(Calendar.DAY_OF_MONTH));
int julianDays = (int) JulianFields.JULIAN_DAY.getFrom(dt);
long nanos = (cal.get(Calendar.HOUR_OF_DAY) * NANOS_PER_HOUR)
        + (cal.get(Calendar.MINUTE) * NANOS_PER_MINUTE)
        + (cal.get(Calendar.SECOND) * NANOS_PER_SECOND);

// Write INT96 timestamp
byte[] timestampBuffer = new byte[12];
ByteBuffer buf = ByteBuffer.wrap(timestampBuffer);
buf.order(ByteOrder.LITTLE_ENDIAN).putLong(nanos).putInt(julianDays);

// This is the properly encoded INT96 timestamp
Binary tsValue = Binary.fromReusedByteArray(timestampBuffer);

Supongo que te gusta

Origin http://43.154.161.224:23101/article/api/json?id=180309&siteId=1
Recomendado
Clasificación