Skip to content

Commit e823511

Browse files
committed
Add Indexes and Stats format reader and writer
Add reader and writer for the Index and Statistics File Format.
1 parent 45c90c4 commit e823511

13 files changed

Lines changed: 1045 additions & 0 deletions

build.gradle

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -217,9 +217,11 @@ project(':iceberg-core') {
217217
exclude group: 'org.tukaani' // xz compression is not supported
218218
}
219219

220+
implementation 'io.airlift:aircompressor'
220221
implementation 'org.apache.httpcomponents.client5:httpclient5'
221222
implementation "com.fasterxml.jackson.core:jackson-databind"
222223
implementation "com.fasterxml.jackson.core:jackson-core"
224+
implementation "com.fasterxml.jackson.datatype:jackson-datatype-jdk8"
223225
implementation "com.github.ben-manes.caffeine:caffeine"
224226
implementation "org.roaringbitmap:RoaringBitmap"
225227
compileOnly("org.apache.hadoop:hadoop-client") {
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one
3+
* or more contributor license agreements. See the NOTICE file
4+
* distributed with this work for additional information
5+
* regarding copyright ownership. The ASF licenses this file
6+
* to you under the Apache License, Version 2.0 (the
7+
* "License"); you may not use this file except in compliance
8+
* with the License. You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing,
13+
* software distributed under the License is distributed on an
14+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
* KIND, either express or implied. See the License for the
16+
* specific language governing permissions and limitations
17+
* under the License.
18+
*/
19+
20+
package org.apache.iceberg.stats;
21+
22+
import com.fasterxml.jackson.annotation.JsonCreator;
23+
import com.fasterxml.jackson.annotation.JsonProperty;
24+
import java.util.List;
25+
import java.util.Objects;
26+
import java.util.Optional;
27+
import org.apache.iceberg.relocated.com.google.common.collect.ImmutableList;
28+
29+
public class BlobMetadata {
30+
private final String type;
31+
private final List<Integer> columns;
32+
private final long fileOffset;
33+
private final long length;
34+
private final Optional<String> compressionCodec;
35+
36+
@JsonCreator
37+
public BlobMetadata(
38+
@JsonProperty("type") String type,
39+
@JsonProperty("columns") List<Integer> columns,
40+
@JsonProperty("offset") long fileOffset,
41+
@JsonProperty("length") long length,
42+
@JsonProperty("compression_codec") Optional<String> compressionCodec) {
43+
this.type = Objects.requireNonNull(type, "type is null");
44+
this.columns = ImmutableList.copyOf(Objects.requireNonNull(columns, "columns is null"));
45+
this.fileOffset = fileOffset;
46+
this.length = length;
47+
this.compressionCodec = Objects.requireNonNull(compressionCodec, "compressionCodec is null");
48+
}
49+
50+
public String getType() {
51+
return type;
52+
}
53+
54+
public List<Integer> getColumns() {
55+
return columns;
56+
}
57+
58+
public long getFileOffset() {
59+
return fileOffset;
60+
}
61+
62+
public long getLength() {
63+
return length;
64+
}
65+
66+
public Optional<String> getCompressionCodec() {
67+
return compressionCodec;
68+
}
69+
}
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one
3+
* or more contributor license agreements. See the NOTICE file
4+
* distributed with this work for additional information
5+
* regarding copyright ownership. The ASF licenses this file
6+
* to you under the Apache License, Version 2.0 (the
7+
* "License"); you may not use this file except in compliance
8+
* with the License. You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing,
13+
* software distributed under the License is distributed on an
14+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
* KIND, either express or implied. See the License for the
16+
* specific language governing permissions and limitations
17+
* under the License.
18+
*/
19+
20+
package org.apache.iceberg.stats;
21+
22+
import com.fasterxml.jackson.annotation.JsonCreator;
23+
import com.fasterxml.jackson.annotation.JsonProperty;
24+
import java.util.List;
25+
import java.util.Map;
26+
import java.util.Objects;
27+
import org.apache.iceberg.relocated.com.google.common.collect.ImmutableList;
28+
import org.apache.iceberg.relocated.com.google.common.collect.ImmutableMap;
29+
30+
public class FileMetadata {
31+
private final List<BlobMetadata> blobs;
32+
private final Map<String, String> properties;
33+
34+
@JsonCreator
35+
public FileMetadata(
36+
@JsonProperty("blobs") List<BlobMetadata> blobs,
37+
@JsonProperty("properties") Map<String, String> properties) {
38+
this.blobs = ImmutableList.copyOf(Objects.requireNonNull(blobs, "blobs is null"));
39+
this.properties = ImmutableMap.copyOf(Objects.requireNonNull(properties, "properties is null"));
40+
}
41+
42+
public List<BlobMetadata> getBlobs() {
43+
return blobs;
44+
}
45+
46+
public Map<String, String> getProperties() {
47+
return properties;
48+
}
49+
}
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one
3+
* or more contributor license agreements. See the NOTICE file
4+
* distributed with this work for additional information
5+
* regarding copyright ownership. The ASF licenses this file
6+
* to you under the Apache License, Version 2.0 (the
7+
* "License"); you may not use this file except in compliance
8+
* with the License. You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing,
13+
* software distributed under the License is distributed on an
14+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
* KIND, either express or implied. See the License for the
16+
* specific language governing permissions and limitations
17+
* under the License.
18+
*/
19+
20+
package org.apache.iceberg.stats;
21+
22+
public final class StandardBlobTypes {
23+
private StandardBlobTypes() {
24+
}
25+
26+
/**
27+
* 8-bytes integer stored little-endian and representing number of distinct values
28+
*/
29+
public static final String NDV_LONG_LITTLE_ENDIAN = "ndv-long-little-endian";
30+
31+
/**
32+
* A serialized form of a "compact" Theta sketch produced by the <a href="https://datasketches.apache.org/">Apache DataSketches</a> library
33+
*/
34+
public static final String APACHE_DATASKETCHES_THETA_V1 = "apache-datasketches-theta-v1";
35+
}
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one
3+
* or more contributor license agreements. See the NOTICE file
4+
* distributed with this work for additional information
5+
* regarding copyright ownership. The ASF licenses this file
6+
* to you under the Apache License, Version 2.0 (the
7+
* "License"); you may not use this file except in compliance
8+
* with the License. You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing,
13+
* software distributed under the License is distributed on an
14+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
* KIND, either express or implied. See the License for the
16+
* specific language governing permissions and limitations
17+
* under the License.
18+
*/
19+
20+
package org.apache.iceberg.stats;
21+
22+
import java.util.Objects;
23+
24+
public enum StatsCompressionCodec {
25+
LZ4("lz4"),
26+
ZSTD("zstd"),
27+
/**/;
28+
29+
private final String codecName;
30+
31+
StatsCompressionCodec(String codecName) {
32+
this.codecName = codecName;
33+
}
34+
35+
public String getCodecName() {
36+
return codecName;
37+
}
38+
39+
public static StatsCompressionCodec forName(String codecName) {
40+
Objects.requireNonNull(codecName, "codecName is null");
41+
for (StatsCompressionCodec value : values()) {
42+
if (value.getCodecName().equals(codecName)) {
43+
return value;
44+
}
45+
}
46+
throw new IllegalArgumentException("Unrecognized codec name " + codecName);
47+
}
48+
}
Lines changed: 136 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,136 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one
3+
* or more contributor license agreements. See the NOTICE file
4+
* distributed with this work for additional information
5+
* regarding copyright ownership. The ASF licenses this file
6+
* to you under the Apache License, Version 2.0 (the
7+
* "License"); you may not use this file except in compliance
8+
* with the License. You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing,
13+
* software distributed under the License is distributed on an
14+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
* KIND, either express or implied. See the License for the
16+
* specific language governing permissions and limitations
17+
* under the License.
18+
*/
19+
20+
package org.apache.iceberg.stats;
21+
22+
import com.fasterxml.jackson.databind.ObjectMapper;
23+
import com.fasterxml.jackson.datatype.jdk8.Jdk8Module;
24+
import io.airlift.compress.Compressor;
25+
import io.airlift.compress.lz4.Lz4Compressor;
26+
import io.airlift.compress.lz4.Lz4Decompressor;
27+
import io.airlift.compress.zstd.ZstdCompressor;
28+
import io.airlift.compress.zstd.ZstdDecompressor;
29+
import java.io.IOException;
30+
import java.io.OutputStream;
31+
import java.math.BigInteger;
32+
import java.util.Arrays;
33+
import org.apache.commons.codec.binary.Base16;
34+
import org.apache.iceberg.relocated.com.google.common.base.Preconditions;
35+
36+
final class StatsFormat {
37+
private StatsFormat() {
38+
}
39+
40+
static final int CURRENT_FORMAT_VERSION = 1;
41+
42+
private static final byte[] MAGIC = new Base16().decode("50464953");
43+
static final int MAGIC_AS_NUMBER_LE = new BigInteger(swap(MAGIC)).intValueExact();
44+
45+
static final int SUPPORTED_FLAGS = 0b1;
46+
static final int FLAG_COMPRESSED = 0b1;
47+
48+
// TODO use LZ4 with frames, see https://trinodb.slack.com/archives/CP1MUNEUX/p1649676596198729
49+
@Deprecated
50+
static final int LZ4_UNCOMPRESSED_MAX_SIZE = 1024 * 1024;
51+
52+
// Not using JsonUtil.mapper() to avoid changing all JSON parsing
53+
static final ObjectMapper OBJECT_MAPPER = new ObjectMapper()
54+
.registerModule(new Jdk8Module());
55+
56+
static byte[] getMagic() {
57+
return MAGIC.clone();
58+
}
59+
60+
static void writeIntegerLittleEndian(OutputStream outputStream, int value) throws IOException {
61+
outputStream.write(0xFF & value);
62+
outputStream.write(0xFF & value >> 8);
63+
outputStream.write(0xFF & value >> 16);
64+
outputStream.write(0xFF & value >> 24);
65+
}
66+
67+
static int readIntegerLittleEndian(byte[] data, int offset) {
68+
return Byte.toUnsignedInt(data[offset]) |
69+
(Byte.toUnsignedInt(data[offset + 1]) << 8) |
70+
(Byte.toUnsignedInt(data[offset + 2]) << 16) |
71+
(Byte.toUnsignedInt(data[offset + 3]) << 24);
72+
}
73+
74+
static byte[] compressFooterPayload(byte[] payload) {
75+
return compress(StatsCompressionCodec.LZ4, payload, 0, payload.length);
76+
}
77+
78+
static byte[] decompressFooterPayload(byte[] footer, int offset, int length) {
79+
return decompress(StatsCompressionCodec.LZ4, footer, offset, length);
80+
}
81+
82+
static byte[] compressBlob(StatsCompressionCodec codec, byte[] data, int dataOffset, int dataLength) {
83+
return compress(codec, data, dataOffset, dataLength);
84+
}
85+
86+
static byte[] decompressBlob(StatsCompressionCodec codec, byte[] data, int dataOffset, int dataLength) {
87+
return decompress(codec, data, dataOffset, dataLength);
88+
}
89+
90+
private static byte[] compress(StatsCompressionCodec codec, byte[] input, int inputOffset, int inputLength) {
91+
Compressor compressor = getCompressor(codec);
92+
byte[] output = new byte[compressor.maxCompressedLength(inputLength)];
93+
int length = compressor.compress(input, inputOffset, inputLength, output, 0, output.length);
94+
return length == output.length ? output : Arrays.copyOf(output, length);
95+
}
96+
97+
private static Compressor getCompressor(StatsCompressionCodec codec) {
98+
switch (codec) {
99+
case LZ4:
100+
return new Lz4Compressor();
101+
case ZSTD:
102+
return new ZstdCompressor();
103+
}
104+
throw new IllegalArgumentException("Unsupported codec: " + codec);
105+
}
106+
107+
private static byte[] decompress(StatsCompressionCodec codec, byte[] input, int inputOffset, int inputLength) {
108+
switch (codec) {
109+
case LZ4: {
110+
byte[] decompressed = new byte[LZ4_UNCOMPRESSED_MAX_SIZE];
111+
int decompressedLength =
112+
new Lz4Decompressor().decompress(input, inputOffset, inputLength, decompressed, 0, decompressed.length);
113+
return Arrays.copyOf(decompressed, decompressedLength);
114+
}
115+
116+
case ZSTD: {
117+
byte[] decompressed =
118+
new byte[Math.toIntExact(ZstdDecompressor.getDecompressedSize(input, inputOffset, inputLength))];
119+
int decompressedLength =
120+
new ZstdDecompressor().decompress(input, inputOffset, inputLength, decompressed, 0, decompressed.length);
121+
Preconditions.checkState(decompressedLength == decompressed.length, "Invalid decompressed length");
122+
return decompressed;
123+
}
124+
}
125+
126+
throw new UnsupportedOperationException("Unsupported codec: " + codec);
127+
}
128+
129+
private static byte[] swap(byte[] bytes) {
130+
byte[] swapped = new byte[bytes.length];
131+
for (int i = 0; i < swapped.length; i++) {
132+
swapped[i] = bytes[swapped.length - i - 1];
133+
}
134+
return swapped;
135+
}
136+
}

0 commit comments

Comments
 (0)