Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,8 @@ before_install:
- cd ..

env:
- HADOOP_PROFILE=default TEST_CODECS=uncompressed
- HADOOP_PROFILE=hadoop-2 TEST_CODECS=gzip,snappy
- HADOOP_PROFILE=hadoop-1 TEST_CODECS=uncompressed
- HADOOP_PROFILE=default TEST_CODECS=gzip,snappy

install: mvn install --batch-mode -DskipTests=true -Dmaven.javadoc.skip=true -Dsource.skip=true > mvn_install.log || mvn install --batch-mode -DskipTests=true -Dmaven.javadoc.skip=true -Dsource.skip=true > mvn_install.log || (cat mvn_install.log && false)
script: mvn test -P $HADOOP_PROFILE
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 {

Copy link
Copy Markdown

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.

Copy link
Copy Markdown

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?

Copy link
Copy Markdown
Member

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.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's add getLength() as well.
That's the only extra information we need to read the footer. It's missing in FSDataInputStream and that would simplify some code where we have to pass the FileStatus object along.
it is always available since we get the stream with FileStatus.open().

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The 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.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Opened PARQUET-674.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The 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;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The 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..

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

agreed. We should document this class.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The 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;

}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The 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
Expand Up @@ -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;
Expand All @@ -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;
Expand All @@ -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;

Expand Down Expand Up @@ -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 {
Expand All @@ -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);
}
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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));

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The 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.
Possibly we just need length and Name to SeekableInputStream and call it ScannableFile ?
It is something that has a length (so that we can locate the footer) and where we and load predefined chunks from, in one go.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The 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) {
Expand Down Expand Up @@ -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();
Expand All @@ -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();
Expand Down Expand Up @@ -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();
Expand Down Expand Up @@ -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;
}
Expand All @@ -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.
}
Expand Down Expand Up @@ -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;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,8 @@
import org.apache.parquet.hadoop.metadata.FileMetaData;
import org.apache.parquet.hadoop.metadata.GlobalMetaData;
import org.apache.parquet.hadoop.metadata.ParquetMetadata;
import org.apache.parquet.hadoop.util.HadoopStreams;
import org.apache.parquet.io.SeekableInputStream;
import org.apache.parquet.io.ParquetEncodingException;
import org.apache.parquet.schema.MessageType;
import org.apache.parquet.schema.PrimitiveType.PrimitiveTypeName;
Expand Down Expand Up @@ -495,13 +497,24 @@ public void appendFile(Configuration conf, Path file) throws IOException {
public void appendRowGroups(FSDataInputStream file,
List<BlockMetaData> rowGroups,
boolean dropColumns) throws IOException {
appendRowGroups(HadoopStreams.wrap(file), rowGroups, dropColumns);
}

public void appendRowGroups(SeekableInputStream file,
List<BlockMetaData> rowGroups,
boolean dropColumns) throws IOException {
for (BlockMetaData block : rowGroups) {
appendRowGroup(file, block, dropColumns);
}
}

public void appendRowGroup(FSDataInputStream from, BlockMetaData rowGroup,
boolean dropColumns) throws IOException {
appendRowGroup(from, rowGroup, dropColumns);
}

public void appendRowGroup(SeekableInputStream from, BlockMetaData rowGroup,
boolean dropColumns) throws IOException {
startBlock(rowGroup.getRowCount());

Map<String, ColumnChunkMetaData> columnsToCopy =
Expand Down Expand Up @@ -596,8 +609,8 @@ protected byte[] initialValue() {
* @param length the number of bytes to copy
* @throws IOException
*/
private static void copy(FSDataInputStream from, FSDataOutputStream to,
long start, long length) throws IOException{
private static void copy(SeekableInputStream from, FSDataOutputStream to,
long start, long length) throws IOException{
if (DEBUG) LOG.debug(
"Copying " + length + " bytes at " + start + " to " + to.getPos());
from.seek(start);
Expand Down
Loading