-
Notifications
You must be signed in to change notification settings - Fork 1.6k
PARQUET-400: Replace CompatibilityUtil with SeekableInputStream. #349
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
ba08b3f
c80580c
506a556
730a9e2
5dc50a5
abaa695
2cb6934
b543013
02d3709
823ca00
1bcb8a8
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,106 @@ | ||
| /* | ||
| * 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.parquet.io; | ||
|
|
||
| import java.io.EOFException; | ||
| import java.io.IOException; | ||
| import java.io.InputStream; | ||
| import java.nio.ByteBuffer; | ||
|
|
||
| /** | ||
| * {@code SeekableInputStream} is an interface with the methods needed by | ||
| * Parquet to read data from a file or Hadoop data stream. | ||
| */ | ||
| public abstract class SeekableInputStream extends InputStream { | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Let's add getLength() as well.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. A seekable stream is a fairly normal construct, while a stream that knows its own length is irregular. I think we are probably better off having a slightly higher-level concept of a stream provider that knows the length of streams it opens. That would basically encapsulate FileStatus and FileSystem so you can pass a single object that can open parallel streams for a single Parquet file. How about doing this as a follow up? This issue is a blocker for 1.9.0 so I'd like to get it in. We can discuss the right way to pass around the length but also work toward getting 1.9.0 out. I'll open an issue for this.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Opened PARQUET-674.
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. fair enough |
||
|
|
||
| /** | ||
| * Return the current position in the InputStream. | ||
| * | ||
| * @return current position in bytes from the start of the stream | ||
| * @throws IOException If the underlying stream throws IOException | ||
| */ | ||
| public abstract long getPos() throws IOException; | ||
|
|
||
| /** | ||
| * Seek to a new position in the InputStream. | ||
| * | ||
| * @param newPos the new position to seek to | ||
| * @throws IOException If the underlying stream throws IOException | ||
| */ | ||
| public abstract void seek(long newPos) throws IOException; | ||
|
|
||
| /** | ||
| * Read a byte array of data, from position 0 to the end of the array. | ||
| * <p> | ||
| * This method is equivalent to {@code read(bytes, 0, bytes.length)}. | ||
| * <p> | ||
| * This method will block until len bytes are available to copy into the | ||
| * array, or will throw {@link EOFException} if the stream ends before the | ||
| * array is full. | ||
| * | ||
| * @param bytes a byte array to fill with data from the stream | ||
| * @throws IOException If the underlying stream throws IOException | ||
| * @throws EOFException If the stream has fewer bytes left than are needed to | ||
| * fill the array, {@code bytes.length} | ||
| */ | ||
| public abstract void readFully(byte[] bytes) throws IOException; | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. lets document the contract of this method a bit - I did that here: https://github.com/apache/parquet-mr/pull/346/files#diff-8c94893b24edb4a7971eb0cbd167754aR41. We can either reuse that or tweak it..
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. agreed. We should document this class.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Added javadoc for the whole class. |
||
|
|
||
| /** | ||
| * Read {@code len} bytes of data into an array, at position {@code start}. | ||
| * <p> | ||
| * This method will block until len bytes are available to copy into the | ||
| * array, or will throw {@link EOFException} if the stream ends before the | ||
| * array is full. | ||
| * | ||
| * @param bytes a byte array to fill with data from the stream | ||
| * @throws IOException If the underlying stream throws IOException | ||
| * @throws EOFException If the stream has fewer than {@code len} bytes left | ||
| */ | ||
| public abstract void readFully(byte[] bytes, int start, int len) throws IOException; | ||
|
|
||
| /** | ||
| * Read {@code buf.remaining()} bytes of data into a {@link ByteBuffer}. | ||
| * <p> | ||
| * This method will copy available bytes into the buffer, reading at most | ||
| * {@code buf.remaining()} bytes. The number of bytes actually copied is | ||
| * returned by the method, or -1 is returned to signal that the end of the | ||
| * underlying stream has been reached. | ||
| * | ||
| * @param buf a byte array to fill with data from the stream | ||
| * @return the number of bytes read or -1 if the stream ended | ||
| * @throws IOException If the underlying stream throws IOException | ||
| */ | ||
| public abstract int read(ByteBuffer buf) throws IOException; | ||
|
|
||
| /** | ||
| * Read {@code buf.remaining()} bytes of data into a {@link ByteBuffer}. | ||
| * <p> | ||
| * This method will block until {@code buf.remaining()} bytes are available | ||
| * to copy into the buffer, or will throw {@link EOFException} if the stream | ||
| * ends before the buffer is full. | ||
| * | ||
| * @param buf a byte array to fill with data from the stream | ||
| * @throws IOException If the underlying stream throws IOException | ||
| * @throws EOFException If the stream has fewer bytes left than are needed to | ||
| * fill the buffer, {@code buf.remaining()} | ||
| */ | ||
| public abstract void readFully(ByteBuffer buf) throws IOException; | ||
|
|
||
| } | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. this is a great addition, this should please people who want more decoupling from hadoop as well. |
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -54,7 +54,6 @@ | |
| import java.util.concurrent.Future; | ||
|
|
||
| import org.apache.hadoop.conf.Configuration; | ||
| import org.apache.hadoop.fs.FSDataInputStream; | ||
| import org.apache.hadoop.fs.FileStatus; | ||
| import org.apache.hadoop.fs.FileSystem; | ||
| import org.apache.hadoop.fs.Path; | ||
|
|
@@ -66,7 +65,6 @@ | |
| import org.apache.parquet.column.page.DictionaryPageReadStore; | ||
| import org.apache.parquet.filter2.compat.FilterCompat; | ||
| import org.apache.parquet.filter2.compat.RowGroupFilter; | ||
| import org.apache.parquet.hadoop.util.CompatibilityUtil; | ||
|
|
||
| import org.apache.parquet.Log; | ||
| import org.apache.parquet.bytes.BytesInput; | ||
|
|
@@ -91,6 +89,8 @@ | |
| import org.apache.parquet.hadoop.metadata.FileMetaData; | ||
| import org.apache.parquet.hadoop.metadata.ParquetMetadata; | ||
| import org.apache.parquet.hadoop.util.HiddenFileFilter; | ||
| import org.apache.parquet.hadoop.util.HadoopStreams; | ||
| import org.apache.parquet.io.SeekableInputStream; | ||
| import org.apache.parquet.hadoop.util.counters.BenchmarkCounter; | ||
| import org.apache.parquet.io.ParquetDecodingException; | ||
|
|
||
|
|
@@ -432,7 +432,7 @@ public static final ParquetMetadata readFooter(Configuration configuration, File | |
| */ | ||
| public static final ParquetMetadata readFooter(Configuration configuration, FileStatus file, MetadataFilter filter) throws IOException { | ||
| FileSystem fileSystem = file.getPath().getFileSystem(configuration); | ||
| FSDataInputStream in = fileSystem.open(file.getPath()); | ||
| SeekableInputStream in = HadoopStreams.wrap(fileSystem.open(file.getPath())); | ||
| try { | ||
| return readFooter(file.getLen(), file.getPath().toString(), in, filter); | ||
| } finally { | ||
|
|
@@ -449,7 +449,7 @@ public static final ParquetMetadata readFooter(Configuration configuration, File | |
| * @return the metadata blocks in the footer | ||
| * @throws IOException if an error occurs while reading the file | ||
| */ | ||
| public static final ParquetMetadata readFooter(long fileLen, String filePath, FSDataInputStream f, MetadataFilter filter) throws IOException { | ||
| public static final ParquetMetadata readFooter(long fileLen, String filePath, SeekableInputStream f, MetadataFilter filter) throws IOException { | ||
| if (Log.DEBUG) { | ||
| LOG.debug("File length " + fileLen); | ||
| } | ||
|
|
@@ -493,7 +493,7 @@ public static ParquetFileReader open(Configuration conf, Path file, ParquetMetad | |
| } | ||
|
|
||
| private final CodecFactory codecFactory; | ||
| private final FSDataInputStream f; | ||
| private final SeekableInputStream f; | ||
| private final FileStatus fileStatus; | ||
| private final Map<ColumnPath, ColumnDescriptor> paths = new HashMap<ColumnPath, ColumnDescriptor>(); | ||
| private final FileMetaData fileMetaData; // may be null | ||
|
|
@@ -531,7 +531,7 @@ public ParquetFileReader( | |
| this.conf = configuration; | ||
| this.fileMetaData = fileMetaData; | ||
| FileSystem fs = filePath.getFileSystem(configuration); | ||
| this.f = fs.open(filePath); | ||
| this.f = HadoopStreams.wrap(fs.open(filePath)); | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. eventually we could replace the "FileStatus file" parameter with something that provides a SeekableInputStream.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm not sure we need both the stream and the file to be one object, but I'm not sure what the right design for it is. I think it's pretty safe to say we'll want a seekable stream and we can add a ScannableFile that inherits from that stream later when we design a Hadoop-free API. |
||
| this.fileStatus = fs.getFileStatus(filePath); | ||
| this.blocks = blocks; | ||
| for (ColumnDescriptor col : columns) { | ||
|
|
@@ -562,7 +562,7 @@ public ParquetFileReader(Configuration conf, Path file, MetadataFilter filter) t | |
| this.conf = conf; | ||
| FileSystem fs = file.getFileSystem(conf); | ||
| this.fileStatus = fs.getFileStatus(file); | ||
| this.f = fs.open(file); | ||
| this.f = HadoopStreams.wrap(fs.open(file)); | ||
| this.footer = readFooter(fileStatus.getLen(), fileStatus.getPath().toString(), f, filter); | ||
| this.fileMetaData = footer.getFileMetaData(); | ||
| this.blocks = footer.getBlocks(); | ||
|
|
@@ -585,7 +585,7 @@ public ParquetFileReader(Configuration conf, Path file, ParquetMetadata footer) | |
| this.conf = conf; | ||
| FileSystem fs = file.getFileSystem(conf); | ||
| this.fileStatus = fs.getFileStatus(file); | ||
| this.f = fs.open(file); | ||
| this.f = HadoopStreams.wrap(fs.open(file)); | ||
| this.footer = footer; | ||
| this.fileMetaData = footer.getFileMetaData(); | ||
| this.blocks = footer.getBlocks(); | ||
|
|
@@ -772,7 +772,7 @@ DictionaryPage readDictionary(ColumnChunkMetaData meta) throws IOException { | |
| } | ||
|
|
||
| private static DictionaryPage readCompressedDictionary( | ||
| PageHeader pageHeader, FSDataInputStream fin) throws IOException { | ||
| PageHeader pageHeader, SeekableInputStream fin) throws IOException { | ||
| DictionaryPageHeader dictHeader = pageHeader.getDictionary_page_header(); | ||
|
|
||
| int uncompressedPageSize = pageHeader.getUncompressed_page_size(); | ||
|
|
@@ -940,15 +940,15 @@ public BytesInput readAsBytesInput(int size) throws IOException { | |
| */ | ||
| private class WorkaroundChunk extends Chunk { | ||
|
|
||
| private final FSDataInputStream f; | ||
| private final SeekableInputStream f; | ||
|
|
||
| /** | ||
| * @param descriptor the descriptor of the chunk | ||
| * @param byteBuf contains the data of the chunk at offset | ||
| * @param offset where the chunk starts in data | ||
| * @param f the file stream positioned at the end of this chunk | ||
| */ | ||
| private WorkaroundChunk(ChunkDescriptor descriptor, ByteBuffer byteBuf, int offset, FSDataInputStream f) { | ||
| private WorkaroundChunk(ChunkDescriptor descriptor, ByteBuffer byteBuf, int offset, SeekableInputStream f) { | ||
| super(descriptor, byteBuf, offset); | ||
| this.f = f; | ||
| } | ||
|
|
@@ -964,7 +964,7 @@ protected PageHeader readPageHeader() throws IOException { | |
| // to allow reading older files (using dictionary) we need this. | ||
| // usually 13 to 19 bytes are missing | ||
| // if the last page is smaller than this, the page header itself is truncated in the buffer. | ||
| this.byteBuf.rewind(); // resetting the buffer to the position before we got the error | ||
| this.byteBuf.position(initialPos); // resetting the buffer to the position before we got the error | ||
| LOG.info("completing the column chunk to read the page header"); | ||
| pageHeader = Util.readPageHeader(new SequenceInputStream(this, f)); // trying again from the buffer + remainder of the stream. | ||
| } | ||
|
|
@@ -1050,11 +1050,14 @@ public void addChunk(ChunkDescriptor descriptor) { | |
| * @return the chunks | ||
| * @throws IOException | ||
| */ | ||
| public List<Chunk> readAll(FSDataInputStream f) throws IOException { | ||
| public List<Chunk> readAll(SeekableInputStream f) throws IOException { | ||
| List<Chunk> result = new ArrayList<Chunk>(chunks.size()); | ||
| f.seek(offset); | ||
|
|
||
| // Allocate the bytebuffer based on whether the FS can support it. | ||
| ByteBuffer chunksByteBuffer = allocator.allocate(length); | ||
| CompatibilityUtil.getBuf(f, chunksByteBuffer, length); | ||
| f.readFully(chunksByteBuffer); | ||
|
|
||
| // report in a counter the data we just scanned | ||
| BenchmarkCounter.incrementBytesRead(length); | ||
| int currentChunkOffset = 0; | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Lets add some javadoc on this abstract class and its purpose etc.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Wondering if SeekableInputStream is the right name? Maybe ParquetInputStream?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This api is not really Parquet specific. It allows to seek and read blocks of the file.
Being in a parquet package is enough "parquet" in the name I think.