in core/src/main/java/org/apache/iceberg/TableMetadataParser.java [339:584]
public static TableMetadata fromJson(String metadataLocation, JsonNode node) {
Preconditions.checkArgument(
node.isObject(), "Cannot parse metadata from a non-object: %s", node);
int formatVersion = JsonUtil.getInt(FORMAT_VERSION, node);
Preconditions.checkArgument(
formatVersion <= TableMetadata.SUPPORTED_TABLE_FORMAT_VERSION,
"Cannot read unsupported version %s",
formatVersion);
String uuid = JsonUtil.getStringOrNull(TABLE_UUID, node);
String location = JsonUtil.getString(LOCATION, node);
long lastSequenceNumber;
if (formatVersion > 1) {
lastSequenceNumber = JsonUtil.getLong(LAST_SEQUENCE_NUMBER, node);
} else {
lastSequenceNumber = TableMetadata.INITIAL_SEQUENCE_NUMBER;
}
int lastAssignedColumnId = JsonUtil.getInt(LAST_COLUMN_ID, node);
List<Schema> schemas;
int currentSchemaId;
Schema schema = null;
JsonNode schemaArray = node.get(SCHEMAS);
if (schemaArray != null) {
Preconditions.checkArgument(
schemaArray.isArray(), "Cannot parse schemas from non-array: %s", schemaArray);
// current schema ID is required when the schema array is present
currentSchemaId = JsonUtil.getInt(CURRENT_SCHEMA_ID, node);
// parse the schema array
ImmutableList.Builder<Schema> builder = ImmutableList.builder();
for (JsonNode schemaNode : schemaArray) {
Schema current = SchemaParser.fromJson(schemaNode);
if (current.schemaId() == currentSchemaId) {
schema = current;
}
builder.add(current);
}
Preconditions.checkArgument(
schema != null,
"Cannot find schema with %s=%s from %s",
CURRENT_SCHEMA_ID,
currentSchemaId,
SCHEMAS);
schemas = builder.build();
} else {
Preconditions.checkArgument(
formatVersion == 1, "%s must exist in format v%s", SCHEMAS, formatVersion);
schema = SchemaParser.fromJson(JsonUtil.get(SCHEMA, node));
currentSchemaId = schema.schemaId();
schemas = ImmutableList.of(schema);
}
JsonNode specArray = node.get(PARTITION_SPECS);
List<PartitionSpec> specs;
int defaultSpecId;
if (specArray != null) {
Preconditions.checkArgument(
specArray.isArray(), "Cannot parse partition specs from non-array: %s", specArray);
// default spec ID is required when the spec array is present
defaultSpecId = JsonUtil.getInt(DEFAULT_SPEC_ID, node);
// parse the spec array
ImmutableList.Builder<PartitionSpec> builder = ImmutableList.builder();
for (JsonNode spec : specArray) {
UnboundPartitionSpec unboundSpec = PartitionSpecParser.fromJson(spec);
if (unboundSpec.specId() == defaultSpecId) {
builder.add(unboundSpec.bind(schema));
} else {
builder.add(unboundSpec.bindUnchecked(schema));
}
}
specs = builder.build();
} else {
Preconditions.checkArgument(
formatVersion == 1, "%s must exist in format v%s", PARTITION_SPECS, formatVersion);
// partition spec is required for older readers, but is always set to the default if the spec
// array is set. it is only used to default the spec map is missing, indicating that the
// table metadata was written by an older writer.
defaultSpecId = TableMetadata.INITIAL_SPEC_ID;
specs =
ImmutableList.of(
PartitionSpecParser.fromJsonFields(
schema, TableMetadata.INITIAL_SPEC_ID, JsonUtil.get(PARTITION_SPEC, node)));
}
Integer lastAssignedPartitionId = JsonUtil.getIntOrNull(LAST_PARTITION_ID, node);
if (lastAssignedPartitionId == null) {
Preconditions.checkArgument(
formatVersion == 1, "%s must exist in format v%s", LAST_PARTITION_ID, formatVersion);
lastAssignedPartitionId =
specs.stream()
.mapToInt(PartitionSpec::lastAssignedFieldId)
.max()
.orElse(PartitionSpec.unpartitioned().lastAssignedFieldId());
}
// parse the sort orders
JsonNode sortOrderArray = node.get(SORT_ORDERS);
List<SortOrder> sortOrders;
int defaultSortOrderId;
if (sortOrderArray != null) {
defaultSortOrderId = JsonUtil.getInt(DEFAULT_SORT_ORDER_ID, node);
ImmutableList.Builder<SortOrder> sortOrdersBuilder = ImmutableList.builder();
for (JsonNode sortOrder : sortOrderArray) {
sortOrdersBuilder.add(SortOrderParser.fromJson(schema, sortOrder, defaultSortOrderId));
}
sortOrders = sortOrdersBuilder.build();
} else {
Preconditions.checkArgument(
formatVersion == 1, "%s must exist in format v%s", SORT_ORDERS, formatVersion);
SortOrder defaultSortOrder = SortOrder.unsorted();
sortOrders = ImmutableList.of(defaultSortOrder);
defaultSortOrderId = defaultSortOrder.orderId();
}
Map<String, String> properties;
if (node.has(PROPERTIES)) {
// parse properties map
properties = JsonUtil.getStringMap(PROPERTIES, node);
} else {
properties = ImmutableMap.of();
}
Long currentSnapshotId = JsonUtil.getLongOrNull(CURRENT_SNAPSHOT_ID, node);
if (currentSnapshotId == null) {
// This field is optional, but internally we set this to -1 when not set
currentSnapshotId = -1L;
}
long lastRowId;
if (formatVersion >= 3) {
lastRowId = JsonUtil.getLong(NEXT_ROW_ID, node);
} else {
lastRowId = TableMetadata.INITIAL_ROW_ID;
}
long lastUpdatedMillis = JsonUtil.getLong(LAST_UPDATED_MILLIS, node);
List<EncryptedKey> keys;
if (node.has(ENCRYPTION_KEYS)) {
keys = JsonUtil.getObjectList(ENCRYPTION_KEYS, node, EncryptedKeyParser::fromJson);
} else {
keys = List.of();
}
Map<String, SnapshotRef> refs;
if (node.has(REFS)) {
refs = refsFromJson(node.get(REFS));
} else if (currentSnapshotId != -1L) {
// initialize the main branch if there are no refs
refs =
ImmutableMap.of(
SnapshotRef.MAIN_BRANCH, SnapshotRef.branchBuilder(currentSnapshotId).build());
} else {
refs = ImmutableMap.of();
}
List<Snapshot> snapshots;
if (node.has(SNAPSHOTS)) {
JsonNode snapshotArray = JsonUtil.get(SNAPSHOTS, node);
Preconditions.checkArgument(
snapshotArray.isArray(), "Cannot parse snapshots from non-array: %s", snapshotArray);
snapshots = Lists.newArrayListWithExpectedSize(snapshotArray.size());
Iterator<JsonNode> iterator = snapshotArray.elements();
while (iterator.hasNext()) {
snapshots.add(SnapshotParser.fromJson(iterator.next()));
}
} else {
snapshots = ImmutableList.of();
}
List<StatisticsFile> statisticsFiles;
if (node.has(STATISTICS)) {
statisticsFiles = statisticsFilesFromJson(node.get(STATISTICS));
} else {
statisticsFiles = ImmutableList.of();
}
List<PartitionStatisticsFile> partitionStatisticsFiles;
if (node.has(PARTITION_STATISTICS)) {
partitionStatisticsFiles = partitionStatsFilesFromJson(node.get(PARTITION_STATISTICS));
} else {
partitionStatisticsFiles = ImmutableList.of();
}
ImmutableList.Builder<HistoryEntry> entries = ImmutableList.builder();
if (node.has(SNAPSHOT_LOG)) {
Iterator<JsonNode> logIterator = node.get(SNAPSHOT_LOG).elements();
while (logIterator.hasNext()) {
JsonNode entryNode = logIterator.next();
entries.add(
new SnapshotLogEntry(
JsonUtil.getLong(TIMESTAMP_MS, entryNode),
JsonUtil.getLong(SNAPSHOT_ID, entryNode)));
}
}
ImmutableList.Builder<MetadataLogEntry> metadataEntries = ImmutableList.builder();
if (node.has(METADATA_LOG)) {
Iterator<JsonNode> logIterator = node.get(METADATA_LOG).elements();
while (logIterator.hasNext()) {
JsonNode entryNode = logIterator.next();
metadataEntries.add(
new MetadataLogEntry(
JsonUtil.getLong(TIMESTAMP_MS, entryNode),
JsonUtil.getString(METADATA_FILE, entryNode)));
}
}
return new TableMetadata(
metadataLocation,
formatVersion,
uuid,
location,
lastSequenceNumber,
lastUpdatedMillis,
lastAssignedColumnId,
currentSchemaId,
schemas,
defaultSpecId,
specs,
lastAssignedPartitionId,
defaultSortOrderId,
sortOrders,
properties,
currentSnapshotId,
snapshots,
null,
entries.build(),
metadataEntries.build(),
refs,
statisticsFiles,
partitionStatisticsFiles,
lastRowId,
keys,
ImmutableList.of() /* no changes from the file */);
}