blob: 52b6ebfb5fd8338e2f0277d05b7d398706d42911 [file] [log] [blame]
/*
* 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.iceberg;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.databind.JsonNode;
import java.io.IOException;
import java.io.StringWriter;
import java.io.UncheckedIOException;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import org.apache.iceberg.relocated.com.google.common.base.Preconditions;
import org.apache.iceberg.relocated.com.google.common.collect.ImmutableMap;
import org.apache.iceberg.relocated.com.google.common.collect.ImmutableSet;
import org.apache.iceberg.relocated.com.google.common.collect.Iterables;
import org.apache.iceberg.util.JsonUtil;
public class MetadataUpdateParser {
private MetadataUpdateParser() {}
private static final String ACTION = "action";
// action types - visible for testing
static final String ASSIGN_UUID = "assign-uuid";
static final String UPGRADE_FORMAT_VERSION = "upgrade-format-version";
static final String ADD_SCHEMA = "add-schema";
static final String SET_CURRENT_SCHEMA = "set-current-schema";
static final String ADD_PARTITION_SPEC = "add-spec";
static final String SET_DEFAULT_PARTITION_SPEC = "set-default-spec";
static final String ADD_SORT_ORDER = "add-sort-order";
static final String SET_DEFAULT_SORT_ORDER = "set-default-sort-order";
static final String ADD_SNAPSHOT = "add-snapshot";
static final String REMOVE_SNAPSHOTS = "remove-snapshots";
static final String REMOVE_SNAPSHOT_REF = "remove-snapshot-ref";
static final String SET_SNAPSHOT_REF = "set-snapshot-ref";
static final String SET_PROPERTIES = "set-properties";
static final String REMOVE_PROPERTIES = "remove-properties";
static final String SET_LOCATION = "set-location";
// AssignUUID
private static final String UUID = "uuid";
// UpgradeFormatVersion
private static final String FORMAT_VERSION = "format-version";
// AddSchema
private static final String SCHEMA = "schema";
private static final String LAST_COLUMN_ID = "last-column-id";
// SetCurrentSchema
private static final String SCHEMA_ID = "schema-id";
// AddPartitionSpec
private static final String SPEC = "spec";
// SetDefaultPartitionSpec
private static final String SPEC_ID = "spec-id";
// AddSortOrder
private static final String SORT_ORDER = "sort-order";
// SetDefaultSortOrder
private static final String SORT_ORDER_ID = "sort-order-id";
// AddSnapshot
private static final String SNAPSHOT = "snapshot";
// RemoveSnapshot
private static final String SNAPSHOT_IDS = "snapshot-ids";
// SetSnapshotRef
private static final String REF_NAME = "ref-name"; // Also used in RemoveSnapshotRef
private static final String SNAPSHOT_ID = "snapshot-id";
private static final String TYPE = "type";
private static final String MIN_SNAPSHOTS_TO_KEEP = "min-snapshots-to-keep";
private static final String MAX_SNAPSHOT_AGE_MS = "max-snapshot-age-ms";
private static final String MAX_REF_AGE_MS = "max-ref-age-ms";
// SetProperties
private static final String UPDATED = "updated";
// RemoveProperties
private static final String REMOVED = "removed";
// SetLocation
private static final String LOCATION = "location";
private static final Map<Class<? extends MetadataUpdate>, String> ACTIONS =
ImmutableMap.<Class<? extends MetadataUpdate>, String>builder()
.put(MetadataUpdate.AssignUUID.class, ASSIGN_UUID)
.put(MetadataUpdate.UpgradeFormatVersion.class, UPGRADE_FORMAT_VERSION)
.put(MetadataUpdate.AddSchema.class, ADD_SCHEMA)
.put(MetadataUpdate.SetCurrentSchema.class, SET_CURRENT_SCHEMA)
.put(MetadataUpdate.AddPartitionSpec.class, ADD_PARTITION_SPEC)
.put(MetadataUpdate.SetDefaultPartitionSpec.class, SET_DEFAULT_PARTITION_SPEC)
.put(MetadataUpdate.AddSortOrder.class, ADD_SORT_ORDER)
.put(MetadataUpdate.SetDefaultSortOrder.class, SET_DEFAULT_SORT_ORDER)
.put(MetadataUpdate.AddSnapshot.class, ADD_SNAPSHOT)
.put(MetadataUpdate.RemoveSnapshot.class, REMOVE_SNAPSHOTS)
.put(MetadataUpdate.RemoveSnapshotRef.class, REMOVE_SNAPSHOT_REF)
.put(MetadataUpdate.SetSnapshotRef.class, SET_SNAPSHOT_REF)
.put(MetadataUpdate.SetProperties.class, SET_PROPERTIES)
.put(MetadataUpdate.RemoveProperties.class, REMOVE_PROPERTIES)
.put(MetadataUpdate.SetLocation.class, SET_LOCATION)
.build();
public static String toJson(MetadataUpdate metadataUpdate) {
return toJson(metadataUpdate, false);
}
public static String toJson(MetadataUpdate metadataUpdate, boolean pretty) {
try {
StringWriter writer = new StringWriter();
JsonGenerator generator = JsonUtil.factory().createGenerator(writer);
if (pretty) {
generator.useDefaultPrettyPrinter();
}
toJson(metadataUpdate, generator);
generator.flush();
return writer.toString();
} catch (IOException e) {
throw new UncheckedIOException(
String.format("Failed to write metadata update json for: %s", metadataUpdate), e);
}
}
public static void toJson(MetadataUpdate metadataUpdate, JsonGenerator generator)
throws IOException {
String updateAction = ACTIONS.get(metadataUpdate.getClass());
// Provide better exception message than the NPE thrown by writing null for the update action,
// which is required
Preconditions.checkArgument(
updateAction != null,
"Cannot convert metadata update to json. Unrecognized metadata update type: {}",
metadataUpdate.getClass().getName());
generator.writeStartObject();
generator.writeStringField(ACTION, updateAction);
switch (updateAction) {
case ASSIGN_UUID:
writeAssignUUID((MetadataUpdate.AssignUUID) metadataUpdate, generator);
break;
case UPGRADE_FORMAT_VERSION:
writeUpgradeFormatVersion((MetadataUpdate.UpgradeFormatVersion) metadataUpdate, generator);
break;
case ADD_SCHEMA:
writeAddSchema((MetadataUpdate.AddSchema) metadataUpdate, generator);
break;
case SET_CURRENT_SCHEMA:
writeSetCurrentSchema((MetadataUpdate.SetCurrentSchema) metadataUpdate, generator);
break;
case ADD_PARTITION_SPEC:
writeAddPartitionSpec((MetadataUpdate.AddPartitionSpec) metadataUpdate, generator);
break;
case SET_DEFAULT_PARTITION_SPEC:
writeSetDefaultPartitionSpec(
(MetadataUpdate.SetDefaultPartitionSpec) metadataUpdate, generator);
break;
case ADD_SORT_ORDER:
writeAddSortOrder((MetadataUpdate.AddSortOrder) metadataUpdate, generator);
break;
case SET_DEFAULT_SORT_ORDER:
writeSetDefaultSortOrder((MetadataUpdate.SetDefaultSortOrder) metadataUpdate, generator);
break;
case ADD_SNAPSHOT:
writeAddSnapshot((MetadataUpdate.AddSnapshot) metadataUpdate, generator);
break;
case REMOVE_SNAPSHOTS:
writeRemoveSnapshots((MetadataUpdate.RemoveSnapshot) metadataUpdate, generator);
break;
case REMOVE_SNAPSHOT_REF:
writeRemoveSnapshotRef((MetadataUpdate.RemoveSnapshotRef) metadataUpdate, generator);
break;
case SET_SNAPSHOT_REF:
writeSetSnapshotRef((MetadataUpdate.SetSnapshotRef) metadataUpdate, generator);
break;
case SET_PROPERTIES:
writeSetProperties((MetadataUpdate.SetProperties) metadataUpdate, generator);
break;
case REMOVE_PROPERTIES:
writeRemoveProperties((MetadataUpdate.RemoveProperties) metadataUpdate, generator);
break;
case SET_LOCATION:
writeSetLocation((MetadataUpdate.SetLocation) metadataUpdate, generator);
break;
default:
throw new IllegalArgumentException(
String.format(
"Cannot convert metadata update to json. Unrecognized action: %s", updateAction));
}
generator.writeEndObject();
}
/**
* Read MetadataUpdate from a JSON string.
*
* @param json a JSON string of a MetadataUpdate
* @return a MetadataUpdate object
*/
public static MetadataUpdate fromJson(String json) {
try {
return fromJson(JsonUtil.mapper().readValue(json, JsonNode.class));
} catch (IOException e) {
throw new UncheckedIOException("Failed to read JSON string: " + json, e);
}
}
public static MetadataUpdate fromJson(JsonNode jsonNode) {
Preconditions.checkArgument(
jsonNode != null && jsonNode.isObject(),
"Cannot parse metadata update from non-object value: %s",
jsonNode);
Preconditions.checkArgument(
jsonNode.hasNonNull(ACTION), "Cannot parse metadata update. Missing field: action");
String action = JsonUtil.getString(ACTION, jsonNode).toLowerCase(Locale.ROOT);
switch (action) {
case ASSIGN_UUID:
return readAssignUUID(jsonNode);
case UPGRADE_FORMAT_VERSION:
return readUpgradeFormatVersion(jsonNode);
case ADD_SCHEMA:
return readAddSchema(jsonNode);
case SET_CURRENT_SCHEMA:
return readSetCurrentSchema(jsonNode);
case ADD_PARTITION_SPEC:
return readAddPartitionSpec(jsonNode);
case SET_DEFAULT_PARTITION_SPEC:
return readSetDefaultPartitionSpec(jsonNode);
case ADD_SORT_ORDER:
return readAddSortOrder(jsonNode);
case SET_DEFAULT_SORT_ORDER:
return readSetDefaultSortOrder(jsonNode);
case ADD_SNAPSHOT:
return readAddSnapshot(jsonNode);
case REMOVE_SNAPSHOTS:
return readRemoveSnapshots(jsonNode);
case REMOVE_SNAPSHOT_REF:
return readRemoveSnapshotRef(jsonNode);
case SET_SNAPSHOT_REF:
return readSetSnapshotRef(jsonNode);
case SET_PROPERTIES:
return readSetProperties(jsonNode);
case REMOVE_PROPERTIES:
return readRemoveProperties(jsonNode);
case SET_LOCATION:
return readSetLocation(jsonNode);
default:
throw new UnsupportedOperationException(
String.format("Cannot convert metadata update action to json: %s", action));
}
}
private static void writeAssignUUID(MetadataUpdate.AssignUUID update, JsonGenerator gen)
throws IOException {
gen.writeStringField(UUID, update.uuid());
}
private static void writeUpgradeFormatVersion(
MetadataUpdate.UpgradeFormatVersion update, JsonGenerator gen) throws IOException {
gen.writeNumberField(FORMAT_VERSION, update.formatVersion());
}
private static void writeAddSchema(MetadataUpdate.AddSchema update, JsonGenerator gen)
throws IOException {
gen.writeFieldName(SCHEMA);
SchemaParser.toJson(update.schema(), gen);
gen.writeNumberField(LAST_COLUMN_ID, update.lastColumnId());
}
private static void writeSetCurrentSchema(
MetadataUpdate.SetCurrentSchema update, JsonGenerator gen) throws IOException {
gen.writeNumberField(SCHEMA_ID, update.schemaId());
}
private static void writeAddPartitionSpec(
MetadataUpdate.AddPartitionSpec update, JsonGenerator gen) throws IOException {
gen.writeFieldName(SPEC);
PartitionSpecParser.toJson(update.spec(), gen);
}
private static void writeSetDefaultPartitionSpec(
MetadataUpdate.SetDefaultPartitionSpec update, JsonGenerator gen) throws IOException {
gen.writeNumberField(SPEC_ID, update.specId());
}
private static void writeAddSortOrder(MetadataUpdate.AddSortOrder update, JsonGenerator gen)
throws IOException {
gen.writeFieldName(SORT_ORDER);
SortOrderParser.toJson(update.sortOrder(), gen);
}
private static void writeSetDefaultSortOrder(
MetadataUpdate.SetDefaultSortOrder update, JsonGenerator gen) throws IOException {
gen.writeNumberField(SORT_ORDER_ID, update.sortOrderId());
}
private static void writeAddSnapshot(MetadataUpdate.AddSnapshot update, JsonGenerator gen)
throws IOException {
gen.writeFieldName(SNAPSHOT);
SnapshotParser.toJson(update.snapshot(), gen);
}
// TODO - Reconcile the spec's set-based removal with the current class implementation that only
// handles one value.
private static void writeRemoveSnapshots(MetadataUpdate.RemoveSnapshot update, JsonGenerator gen)
throws IOException {
gen.writeArrayFieldStart(SNAPSHOT_IDS);
for (long snapshotId : ImmutableSet.of(update.snapshotId())) {
gen.writeNumber(snapshotId);
}
gen.writeEndArray();
}
private static void writeSetSnapshotRef(MetadataUpdate.SetSnapshotRef update, JsonGenerator gen)
throws IOException {
gen.writeStringField(REF_NAME, update.name());
gen.writeNumberField(SNAPSHOT_ID, update.snapshotId());
gen.writeStringField(TYPE, update.type());
JsonUtil.writeIntegerFieldIf(
update.minSnapshotsToKeep() != null,
MIN_SNAPSHOTS_TO_KEEP,
update.minSnapshotsToKeep(),
gen);
JsonUtil.writeLongFieldIf(
update.maxSnapshotAgeMs() != null, MAX_SNAPSHOT_AGE_MS, update.maxSnapshotAgeMs(), gen);
JsonUtil.writeLongFieldIf(
update.maxRefAgeMs() != null, MAX_REF_AGE_MS, update.maxRefAgeMs(), gen);
}
private static void writeRemoveSnapshotRef(
MetadataUpdate.RemoveSnapshotRef update, JsonGenerator gen) throws IOException {
gen.writeStringField(REF_NAME, update.name());
}
private static void writeSetProperties(MetadataUpdate.SetProperties update, JsonGenerator gen)
throws IOException {
gen.writeFieldName(UPDATED);
gen.writeObject(update.updated());
}
private static void writeRemoveProperties(
MetadataUpdate.RemoveProperties update, JsonGenerator gen) throws IOException {
gen.writeFieldName(REMOVED);
gen.writeObject(update.removed());
}
private static void writeSetLocation(MetadataUpdate.SetLocation update, JsonGenerator gen)
throws IOException {
gen.writeStringField(LOCATION, update.location());
}
private static MetadataUpdate readAssignUUID(JsonNode node) {
String uuid = JsonUtil.getString(UUID, node);
return new MetadataUpdate.AssignUUID(uuid);
}
private static MetadataUpdate readUpgradeFormatVersion(JsonNode node) {
int formatVersion = JsonUtil.getInt(FORMAT_VERSION, node);
return new MetadataUpdate.UpgradeFormatVersion(formatVersion);
}
private static MetadataUpdate readAddSchema(JsonNode node) {
JsonNode schemaNode = JsonUtil.get(SCHEMA, node);
Schema schema = SchemaParser.fromJson(schemaNode);
int lastColumnId = JsonUtil.getInt(LAST_COLUMN_ID, node);
return new MetadataUpdate.AddSchema(schema, lastColumnId);
}
private static MetadataUpdate readSetCurrentSchema(JsonNode node) {
int schemaId = JsonUtil.getInt(SCHEMA_ID, node);
return new MetadataUpdate.SetCurrentSchema(schemaId);
}
private static MetadataUpdate readAddPartitionSpec(JsonNode node) {
JsonNode specNode = JsonUtil.get(SPEC, node);
UnboundPartitionSpec spec = PartitionSpecParser.fromJson(specNode);
return new MetadataUpdate.AddPartitionSpec(spec);
}
private static MetadataUpdate readSetDefaultPartitionSpec(JsonNode node) {
int specId = JsonUtil.getInt(SPEC_ID, node);
return new MetadataUpdate.SetDefaultPartitionSpec(specId);
}
private static MetadataUpdate readAddSortOrder(JsonNode node) {
JsonNode sortOrderNode = JsonUtil.get(SORT_ORDER, node);
UnboundSortOrder sortOrder = SortOrderParser.fromJson(sortOrderNode);
return new MetadataUpdate.AddSortOrder(sortOrder);
}
private static MetadataUpdate readSetDefaultSortOrder(JsonNode node) {
int sortOrderId = JsonUtil.getInt(SORT_ORDER_ID, node);
return new MetadataUpdate.SetDefaultSortOrder(sortOrderId);
}
private static MetadataUpdate readAddSnapshot(JsonNode node) {
Snapshot snapshot = SnapshotParser.fromJson(null, JsonUtil.get(SNAPSHOT, node));
return new MetadataUpdate.AddSnapshot(snapshot);
}
private static MetadataUpdate readRemoveSnapshots(JsonNode node) {
Set<Long> snapshotIds = JsonUtil.getLongSetOrNull(SNAPSHOT_IDS, node);
Preconditions.checkArgument(
snapshotIds != null && snapshotIds.size() == 1,
"Invalid set of snapshot ids to remove. Expected one value but received: %s",
snapshotIds);
Long snapshotId = Iterables.getOnlyElement(snapshotIds);
return new MetadataUpdate.RemoveSnapshot(snapshotId);
}
private static MetadataUpdate readSetSnapshotRef(JsonNode node) {
String refName = JsonUtil.getString(REF_NAME, node);
long snapshotId = JsonUtil.getLong(SNAPSHOT_ID, node);
SnapshotRefType type =
SnapshotRefType.valueOf(JsonUtil.getString(TYPE, node).toUpperCase(Locale.ENGLISH));
Integer minSnapshotsToKeep = JsonUtil.getIntOrNull(MIN_SNAPSHOTS_TO_KEEP, node);
Long maxSnapshotAgeMs = JsonUtil.getLongOrNull(MAX_SNAPSHOT_AGE_MS, node);
Long maxRefAgeMs = JsonUtil.getLongOrNull(MAX_REF_AGE_MS, node);
return new MetadataUpdate.SetSnapshotRef(
refName, snapshotId, type, minSnapshotsToKeep, maxSnapshotAgeMs, maxRefAgeMs);
}
private static MetadataUpdate readRemoveSnapshotRef(JsonNode node) {
String refName = JsonUtil.getString(REF_NAME, node);
return new MetadataUpdate.RemoveSnapshotRef(refName);
}
private static MetadataUpdate readSetProperties(JsonNode node) {
Map<String, String> updated = JsonUtil.getStringMap(UPDATED, node);
return new MetadataUpdate.SetProperties(updated);
}
private static MetadataUpdate readRemoveProperties(JsonNode node) {
Set<String> removed = JsonUtil.getStringSet(REMOVED, node);
return new MetadataUpdate.RemoveProperties(removed);
}
private static MetadataUpdate readSetLocation(JsonNode node) {
String location = JsonUtil.getString(LOCATION, node);
return new MetadataUpdate.SetLocation(location);
}
}