Skip to content
Open
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
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
/*
* 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.column.page;

import org.apache.parquet.column.ColumnDescriptor;

/**
* contains all the dictionary readers for all the columns of the corresponding row group
*/
public interface DictionaryPageReadStore {

/**
*
* @param descriptor the descriptor of the column
* @return the dictionary page reader for that column
*/
DictionaryPageReader getDictionaryPageReader(ColumnDescriptor descriptor);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
/*
* 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.column.page;

/**
* Reader for a dictionary page from a given column chunk
*/
public interface DictionaryPageReader {

/**
* @return the dictionary page in that chunk or null if none
*/
DictionaryPage readDictionaryPage();

/**
* @return the dictionary size
*/
int getDictionarySize();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
/*
* 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.column.page;

import org.apache.parquet.column.ColumnDescriptor;
import org.apache.parquet.column.Dictionary;

import java.io.IOException;

/**
* Read a dictionary from dictionary page
*/
public class DictionaryReader {
private final ColumnDescriptor column;
private final DictionaryPageReader pageReader;

public DictionaryReader(ColumnDescriptor column, DictionaryPageReadStore dictionaryPageReadStore) {
this.column = column;
this.pageReader = dictionaryPageReadStore.getDictionaryPageReader(column);
}

public Dictionary readDictionary() throws IOException {
DictionaryPage dictionaryPage = pageReader.readDictionaryPage();
return dictionaryPage.getEncoding().initDictionary(column, dictionaryPage);
}

public int getDictionarySize() {
return pageReader.getDictionarySize();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
/*
* 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.hadoop;

import java.io.IOException;
import java.util.HashMap;
import java.util.Map;

import org.apache.parquet.Ints;
import org.apache.parquet.Log;
import org.apache.parquet.column.ColumnDescriptor;
import org.apache.parquet.column.page.DictionaryPage;
import org.apache.parquet.column.page.DictionaryPageReadStore;
import org.apache.parquet.column.page.DictionaryPageReader;
import org.apache.parquet.hadoop.CodecFactory.BytesDecompressor;
import org.apache.parquet.hadoop.ColumnChunkPageReadStore.ColumnChunkPageReader;

class ColumnChunkDictionaryPageReadStore implements DictionaryPageReadStore {
/**
* DictionaryPageReader for a single column chunk. A column chunk contains
* several pages, first of which could be a dictionary page.
*
* This implementation is provided with a ColumnChunkPageReader delegate
*/
static final class ColumnChunkDictionaryPageReader implements DictionaryPageReader {

private final ColumnChunkPageReader columnChunkPageReader;

ColumnChunkDictionaryPageReader(ColumnChunkPageReader columnChunkPageReader) {
this.columnChunkPageReader = columnChunkPageReader;
}

@Override
public int getDictionarySize() {
return columnChunkPageReader.getCompressedDictionaryPage().getDictionarySize();
}

@Override
public DictionaryPage readDictionaryPage() {
return columnChunkPageReader.readDictionaryPage();
}
}

private final Map<ColumnDescriptor, ColumnChunkDictionaryPageReader> readers;

ColumnChunkDictionaryPageReadStore(Map<ColumnDescriptor, ColumnChunkDictionaryPageReader> readers) {
this.readers = readers;
}

@Override
public DictionaryPageReader getDictionaryPageReader(ColumnDescriptor column) {
if (!readers.containsKey(column)) {
throw new IllegalArgumentException(column + " is not in the store: " + readers.keySet());
}
return readers.get(column);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,10 @@ public DictionaryPage readDictionaryPage() {
throw new RuntimeException(e); // TODO: cleanup
}
}

public DictionaryPage getCompressedDictionaryPage() {
return compressedDictionaryPage;
}
}

private final Map<ColumnDescriptor, ColumnChunkPageReader> readers = new HashMap<ColumnDescriptor, ColumnChunkPageReader>();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,20 +56,26 @@
import org.apache.parquet.Log;
import org.apache.parquet.bytes.BytesInput;
import org.apache.parquet.column.ColumnDescriptor;
import org.apache.parquet.column.Dictionary;
import org.apache.parquet.column.Encoding;
import org.apache.parquet.column.page.DataPage;
import org.apache.parquet.column.page.DataPageV1;
import org.apache.parquet.column.page.DataPageV2;
import org.apache.parquet.column.page.DictionaryPageReadStore;
import org.apache.parquet.column.page.DictionaryPageReader;
import org.apache.parquet.column.page.DictionaryPage;
import org.apache.parquet.column.page.PageReadStore;
import org.apache.parquet.hadoop.metadata.ColumnPath;
import org.apache.parquet.format.DataPageHeader;
import org.apache.parquet.format.DataPageHeaderV2;
import org.apache.parquet.format.DictionaryPageHeader;
import org.apache.parquet.format.PageHeader;
import org.apache.parquet.format.PageType;
import org.apache.parquet.format.Util;
import org.apache.parquet.format.converter.ParquetMetadataConverter;
import org.apache.parquet.format.converter.ParquetMetadataConverter.MetadataFilter;
import org.apache.parquet.hadoop.CodecFactory.BytesDecompressor;
import org.apache.parquet.hadoop.ColumnChunkDictionaryPageReadStore.ColumnChunkDictionaryPageReader;
import org.apache.parquet.hadoop.ColumnChunkPageReadStore.ColumnChunkPageReader;
import org.apache.parquet.hadoop.metadata.BlockMetaData;
import org.apache.parquet.hadoop.metadata.ColumnChunkMetaData;
Expand All @@ -78,6 +84,7 @@
import org.apache.parquet.hadoop.util.HiddenFileFilter;
import org.apache.parquet.hadoop.util.counters.BenchmarkCounter;
import org.apache.parquet.io.ParquetDecodingException;
import org.apache.parquet.schema.MessageType;

/**
* Internal implementation of the Parquet file reader as a block container
Expand Down Expand Up @@ -517,7 +524,74 @@ public PageReadStore readNextRowGroup() throws IOException {
return columnChunkPageReadStore;
}

/**
* Reads the requested predicate columns dictionaries from the row group at the current file position.
* Not reading all the requested columns, only read predicate columns.
* eg. select columnA, columnB, columnC from table where columnC > 10
* only reads columnC's dictionary
* @param requestedSchema the predicate columns to read
* @throws IOException if an error occurs while reading
* @return the DictionaryPageReadStore which can provide DictionaryPageReaders for each column.
*/
public DictionaryPageReadStore getCurrentRowGroupDictionaries(MessageType requestedSchema) 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.

@julienledem Unfortunately, I don't think this API works very well for the uses cases we have right now. The issue @zhenxiao is seeing is that he wants to identify the row groups not filtered prior to processing them. This interface requires that you do both in lock step. The filter api has the same problem in that it basically needs to do up front row group filtering without loading any column data. We would have to be able to advance the dictionary separately from the row group, which would make this interface rather clunky.

Overall, I think the static interface was closer to what we currently need (though I can see how this approach can be used to filter while processing). Maybe we just need to separate out the reading of the dictionary as a utility that gets used by this.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

@julienledem @danielcweeks as @danielcweeks explained, the use case is,
read footer, for each row group read blockMetaData and Dictionary for each predicate column chunks, decide whether to read the row group or not based on stats and dictionary, keep the list of to-be-scanned row groups, and then start scanning the necessary row groups.
In this case advance dictionary is separate from advancing row group data.
I am thinking about the following options:

  1. advance blockId in both nextRowGroup() and nextRowGroupDictionaries(), while, they should not be called interleaving or together, only one of them could be called in the application
  2. have a user level advanceBlock() which will advance blockId, then user could decide when to call nextRowGroup(), when to advanceBlock(), and when to read dictionaries, this could be a little bit too flexible, and existing applications calling nextRowGroup() needs to be updated
  3. have a static method in ParquetFileReader, pass BlockMetaData, configuration, path as input parameters
    Agree with @danielcweeks option#3 is better. what do you think?

if (currentBlock == blocks.size()) {
return null;
}
BlockMetaData block = blocks.get(currentBlock);
if (block.getRowCount() == 0) {
throw new RuntimeException("Illegal row group of 0 rows");
}

Map<ColumnDescriptor, ColumnChunkDictionaryPageReader> dictionaryReaders = new HashMap<ColumnDescriptor, ColumnChunkDictionaryPageReader>();
for (ColumnChunkMetaData columnChunkMetaData : block.getColumns()) {
for (ColumnDescriptor columnDescriptor : requestedSchema.getColumns()) {
if (columnChunkMetaData.getPath().equals(ColumnPath.get(columnDescriptor.getPath())) &&
isOnlyDictionaryEncodingPages(columnChunkMetaData.getEncodings())) {
long startingPosition = columnChunkMetaData.getStartingPos();
this.f.seek(startingPosition);

int totalSize = (int) columnChunkMetaData.getTotalSize();
byte[] buffer = new byte[totalSize];
// TODO: set read size to be (page header size + compressed dictionary page size)
this.f.readFully(buffer);

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.

Possibly you could check if you can read only the dictionary page instead of the entire column chunk.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

@julienledem seems like could not decide the compressed page size until reading the page header, so at this time(before reading page header), do not know the actual size to read. I will leave todo here


ChunkDescriptor chunkDescriptor = new ChunkDescriptor(columnDescriptor,
columnChunkMetaData,
startingPosition,
totalSize);
Chunk chunk = new Chunk(chunkDescriptor, buffer, 0);
DictionaryPage compressedPage = chunk.readDictionaryPage();
if (compressedPage == null) {
dictionaryReaders.put(columnDescriptor, null);
break;
}

BytesDecompressor decompressor = codecFactory.getDecompressor(columnChunkMetaData.getCodec());
ColumnChunkPageReader columnChunkPageReader = new ColumnChunkPageReader(decompressor, new ArrayList<DataPage>(), compressedPage);
ColumnChunkDictionaryPageReader dictionaryPageReader = new ColumnChunkDictionaryPageReader(columnChunkPageReader);
dictionaryReaders.put(columnDescriptor, dictionaryPageReader);
}
}
}
return new ColumnChunkDictionaryPageReadStore(dictionaryReaders);
}

// are all the value pages dictionary encoded
private boolean isOnlyDictionaryEncodingPages(Set<Encoding> encodings) {
// if values have more than one encodings, definitely not dictionary encoding only
if (encodings.size() > 3) {
return false;
}

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

@julienledem I tested against both PAGE_V1 and PAGE_V2, this heuristics seems working OK, an ideal solution is to use PageEncodingStats, this is a heuristics workaround. I put todo here as a next step

// heuristics: definition level, repetition level are not dictionary encoded
// if there is dictionary encoding, it must be values
// TODO: use PageEncodingStats to determine whether all value pages are dictionary encoded
for (Encoding encoding : encodings) {
if (encoding.usesDictionary()) {
return true;
}
}
return false;
}

@Override
public void close() throws IOException {
Expand All @@ -535,6 +609,8 @@ private class Chunk extends ByteArrayInputStream {

private final ChunkDescriptor descriptor;

private PageHeader pageHeader;

/**
*
* @param descriptor descriptor for the chunk
Expand All @@ -551,6 +627,26 @@ protected PageHeader readPageHeader() throws IOException {
return Util.readPageHeader(this);
}

/**
* Read only dictionary page in a given column chunk.
* readDictionaryPage should not be used interleaving with readAllPages
* either read only dictionary page, or read all pages
* @return dictionary page
*/
public DictionaryPage readDictionaryPage() throws IOException {
this.pageHeader = readPageHeader();
int uncompressedPageSize = pageHeader.getUncompressed_page_size();
int compressedPageSize = pageHeader.getCompressed_page_size();
if (pageHeader.type == PageType.DICTIONARY_PAGE) {
DictionaryPageHeader dicHeader = pageHeader.getDictionary_page_header();
return new DictionaryPage(this.readAsBytesInput(compressedPageSize),
uncompressedPageSize,
dicHeader.getNum_values(),
converter.getEncoding(dicHeader.getEncoding()));
}
return null;
}

/**
* Read all of the pages in a given column chunk.
* @return the list of pages
Expand All @@ -560,24 +656,14 @@ public ColumnChunkPageReader readAllPages() throws IOException {
DictionaryPage dictionaryPage = null;
long valuesCountReadSoFar = 0;
while (valuesCountReadSoFar < descriptor.metadata.getValueCount()) {
PageHeader pageHeader = readPageHeader();
int uncompressedPageSize = pageHeader.getUncompressed_page_size();
int compressedPageSize = pageHeader.getCompressed_page_size();
DictionaryPage nextDictionaryPage = readDictionaryPage();
if (dictionaryPage != null && nextDictionaryPage != null) {
throw new ParquetDecodingException("more than one dictionary page in column " + descriptor.col);
}
dictionaryPage = nextDictionaryPage;
int uncompressedPageSize = this.pageHeader.getUncompressed_page_size();
int compressedPageSize = this.pageHeader.getCompressed_page_size();
switch (pageHeader.type) {
case DICTIONARY_PAGE:
// there is only one dictionary page per column chunk
if (dictionaryPage != null) {
throw new ParquetDecodingException("more than one dictionary page in column " + descriptor.col);
}
DictionaryPageHeader dicHeader = pageHeader.getDictionary_page_header();
dictionaryPage =
new DictionaryPage(
this.readAsBytesInput(compressedPageSize),
uncompressedPageSize,
dicHeader.getNum_values(),
converter.getEncoding(dicHeader.getEncoding())
);
break;
case DATA_PAGE:
DataPageHeader dataHeaderV1 = pageHeader.getData_page_header();
pagesInChunk.add(
Expand Down Expand Up @@ -616,6 +702,8 @@ public ColumnChunkPageReader readAllPages() throws IOException {
));
valuesCountReadSoFar += dataHeaderV2.getNum_values();
break;
case DICTIONARY_PAGE:
throw new ParquetDecodingException("more than one dictionary page in column " + descriptor.col);
default:
if (DEBUG) LOG.debug("skipping page of type " + pageHeader.getType() + " of size " + compressedPageSize);
this.skip(compressedPageSize);
Expand Down Expand Up @@ -801,5 +889,4 @@ public long endPos() {
}

}

}
Loading