/** * 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.hadoop.zebra.io; import java.io.Closeable; import java.io.DataInput; import java.io.DataInputStream; import java.io.DataOutput; import java.io.DataOutputStream; import java.io.FileNotFoundException; import java.io.IOException; import java.io.OutputStream; import java.io.PrintStream; import java.io.StringReader; import java.util.ArrayList; import java.util.HashSet; import java.util.List; import java.util.Random; import java.util.Set; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.apache.hadoop.conf.Configuration; import org.apache.hadoop.fs.FSDataInputStream; import org.apache.hadoop.fs.FSDataOutputStream; import org.apache.hadoop.fs.FileStatus; import org.apache.hadoop.fs.FileSystem; import org.apache.hadoop.fs.Path; import org.apache.hadoop.fs.PathFilter; import org.apache.hadoop.io.BytesWritable; import org.apache.hadoop.io.Writable; import org.apache.hadoop.io.WritableUtils; import org.apache.hadoop.zebra.tfile.TFile; import org.apache.hadoop.zebra.tfile.Utils; import org.apache.hadoop.zebra.tfile.MetaBlockAlreadyExists; import org.apache.hadoop.zebra.tfile.MetaBlockDoesNotExist; import org.apache.hadoop.zebra.tfile.Utils.Version; import org.apache.hadoop.zebra.io.ColumnGroup.Reader.CGRangeSplit; import org.apache.hadoop.zebra.io.ColumnGroup.Reader.CGRowSplit; import org.apache.hadoop.zebra.io.ColumnGroup.Reader.CGScanner; import org.apache.hadoop.zebra.types.CGSchema; import org.apache.hadoop.zebra.mapreduce.BasicTableOutputFormat; import org.apache.hadoop.zebra.parser.ParseException; import org.apache.hadoop.zebra.types.Partition; import org.apache.hadoop.zebra.types.Projection; import org.apache.hadoop.zebra.types.ZebraConf; import org.apache.hadoop.zebra.schema.Schema; import org.apache.hadoop.zebra.parser.TableSchemaParser; import org.apache.hadoop.zebra.pig.TableStorer; import org.apache.hadoop.zebra.types.TypesUtils; import org.apache.hadoop.zebra.types.SortInfo; import org.apache.pig.data.Tuple; /** * A materialized table that consists of one or more tightly coupled Column * Groups. * * The following Configuration parameters can customize the behavior of * BasicTable. * <ul> * <li><b>table.output.tfile.minBlock.size</b> (int) Minimum compression block * size for underlying TFile (default to 1024*1024). * <li><b>table.output.tfile.compression</b> (String) Compression method (one of * "none", "lzo", "gz") (default is "gz"). @see * {@link TFile#getSupportedCompressionAlgorithms()} * <li><b>table.input.split.minSize</b> (int) Minimum split size (default to * 64*1024). * </ul> */ public class BasicTable { static Log LOG = LogFactory.getLog(BasicTable.class); // name of the BasicTable schema file private final static String BT_SCHEMA_FILE = ".btschema"; // schema version private final static Version SCHEMA_VERSION = new Version((short) 1, (short) 1); // name of the BasicTable meta-data file private final static String BT_META_FILE = ".btmeta"; private final static String DELETED_CG_PREFIX = ".deleted-"; public final static String DELETED_CG_SEPARATOR_PER_TABLE = ","; // no public ctor for instantiating a BasicTable object private BasicTable() { // no-op } /** * Deletes the data for column group specified by cgName. * When the readers try to read the fields that were stored in the * column group get null since the underlying data is removed. * <br> <br> * * Effect on the readers that are currently reading from the table while * a column group is droped is unspecified. Suggested practice is to * drop column groups when there are no readers or writes for the table. * <br> <br> * * Column group names are usually specified in the "storage hint" while * creating a table. If no name is specified, system assigns a simple name. * These names could be obtained through "dumpInfo()" and other methods. * <br> <br> * * Dropping a column group that has already been removed is a no-op no * exception is thrown. * <br> <br> * * Note that this feature is experimental now and subject to changes in the * future. * * @param path path to BasicTable * @param conf Configuration determines file system and other parameters. * @param cgName name of the column group to drop. * @throws IOException IOException could occur for various reasons. E.g. * a user does not have permissions to write to table directory. * */ public static void dropColumnGroup(Path path, Configuration conf, String cgName) throws IOException { FileSystem fs = FileSystem.get(conf); int triedCount = 0; int numCGs = SchemaFile.getNumCGs(path, conf); SchemaFile schemaFile = null; /* Retry up to numCGs times accounting for other CG deleting threads or processes.*/ while (triedCount ++ < numCGs) { try { schemaFile = new SchemaFile(path, null, conf); break; } catch (FileNotFoundException e) { LOG.info("Try " + triedCount + " times : " + e.getMessage()); } catch (Exception e) { throw new IOException ("Cannot construct SchemaFile : " + e.getMessage()); } } if (schemaFile == null) { throw new IOException ("Cannot construct SchemaFile"); } int cgIdx = schemaFile.getCGByName(cgName); if (cgIdx < 0) { throw new IOException(path + " : Could not find a column group with the name '" + cgName + "'"); } Path cgPath = new Path(path, schemaFile.getName(cgIdx)); //Clean up any previous unfinished attempts to drop column groups? if (schemaFile.isCGDeleted(cgIdx)) { // Clean up unfinished delete if it exists. so that clean up can // complete if the previous deletion was interrupted for some reason. if (fs.exists(cgPath)) { LOG.info(path + " : " + " clearing unfinished deletion of column group " + cgName + "."); fs.delete(cgPath, true); } LOG.info(path + " : column group " + cgName + " is already deleted."); return; } // try to delete the column group: // first check if the user has enough permissions to list the directory fs.listStatus(cgPath); //verify if the user has enough permissions by trying to create //a temporary file in cg. OutputStream out = fs.create( new Path(cgPath, ".tmp" + DELETED_CG_PREFIX + cgName), true); out.close(); //First try to create a file indicating a column group is deleted. try { Path deletedCGPath = new Path(path, DELETED_CG_PREFIX + cgName); // create without overriding. out = fs.create(deletedCGPath, false); // should we write anything? out.close(); } catch (IOException e) { // one remote possibility is that another user // already deleted CG. SchemaFile tempSchema = new SchemaFile(path, null, conf); if (tempSchema.isCGDeleted(cgIdx)) { LOG.info(path + " : " + cgName + " is deleted by someone else. That is ok."); return; } // otherwise, it is some other error. throw e; } // At this stage, the CG is marked deleted. Now just try to // delete the actual directory: if (!fs.delete(cgPath, true)) { String msg = path + " : Could not detete column group " + cgName + ". It is marked deleted."; LOG.warn(msg); throw new IOException(msg); } LOG.info("Dropped " + cgName + " from " + path); } /** * BasicTable reader. */ public static class Reader implements Closeable { private Path path; private boolean closed = true; private SchemaFile schemaFile; private Projection projection; boolean inferredMapping; private MetaFile.Reader metaReader; private BasicTableStatus status; private int firstValidCG = -1; /// First column group that exists. private int rowSplitCGIndex = -1; Partition partition; ColumnGroup.Reader[] colGroups; Tuple[] cgTuples; private synchronized void checkInferredMapping() throws ParseException, IOException { if (!inferredMapping) { for (int i = 0; i < colGroups.length; ++i) { if (colGroups[i] != null) { colGroups[i].setProjection(partition.getProjection(i)); } if (partition.isCGNeeded(i)) { if (isCGDeleted(i)) { // this is a deleted column group. Warn about it. LOG.warn("Trying to read from deleted column group " + schemaFile.getName(i) + ". NULL is returned for corresponding columns. " + "Table at " + path); } else { cgTuples[i] = TypesUtils.createTuple(colGroups[i].getSchema()); } } else cgTuples[i] = null; } partition.setSource(cgTuples); inferredMapping = true; } else { // the projection is not changed, so we do not need to recalculate the // mapping } } /** * Returns true if a column group is deleted. */ private boolean isCGDeleted(int nx) { return colGroups[nx] == null; } /** * Create a BasicTable reader. * * @param path * The directory path to the BasicTable. * @param conf * Optional configuration parameters. * @throws IOException */ public Reader(Path path, Configuration conf) throws IOException { this(path, null, conf); } public Reader(Path path, String[] deletedCGs, Configuration conf) throws IOException { try { boolean mapper = (deletedCGs != null); this.path = path; schemaFile = new SchemaFile(path, deletedCGs, conf); metaReader = MetaFile.createReader(new Path(path, BT_META_FILE), conf); // create column group readers int numCGs = schemaFile.getNumOfPhysicalSchemas(); Schema schema; colGroups = new ColumnGroup.Reader[numCGs]; cgTuples = new Tuple[numCGs]; // set default projection that contains everything schema = schemaFile.getLogical(); projection = new Projection(schema); String storage = schemaFile.getStorageString(); String comparator = schemaFile.getComparator(); partition = new Partition(schema, projection, storage, comparator); for (int nx = 0; nx < numCGs; nx++) { if (!schemaFile.isCGDeleted(nx)) { colGroups[nx] = new ColumnGroup.Reader(new Path(path, partition.getCGSchema(nx).getName()), conf, mapper); if (firstValidCG < 0) { firstValidCG = nx; } } if (colGroups[nx] != null && partition.isCGNeeded(nx)) cgTuples[nx] = TypesUtils.createTuple(colGroups[nx].getSchema()); else cgTuples[nx] = null; } closed = false; } catch (Exception e) { throw new IOException("BasicTable.Reader constructor failed : " + e.getMessage()); } finally { if (closed) { /** * Construction fails. */ if (colGroups != null) { for (int i = 0; i < colGroups.length; ++i) { if (colGroups[i] != null) { try { colGroups[i].close(); } catch (Exception e) { // ignore error } } } } if (metaReader != null) { try { metaReader.close(); } catch (Exception e) { // no-op } } } } } /** * Is the Table sorted? * * @return Whether the table is sorted. */ public boolean isSorted() { return schemaFile.isSorted(); } /** * @return the list of sorted columns */ public SortInfo getSortInfo() { return schemaFile.getSortInfo(); } /** * @return the name of i-th column group */ public String getName(int i) { return schemaFile.getName(i); } /** * Set the projection for the reader. This will affect calls to * {@link #getScanner(RangeSplit, boolean)}, * {@link #getScanner(BytesWritable, BytesWritable, boolean)}, * {@link #getStatus()}, {@link #getSchema()}. * * @param projection * The projection on the BasicTable for subsequent read operations. * For this version of implementation, the projection is a comma * separated list of column names, such as * "FirstName, LastName, Sex, Department". If we want select all * columns, pass projection==null. * @throws IOException */ public synchronized void setProjection(String projection) throws ParseException, IOException { if (projection == null) { this.projection = new Projection(schemaFile.getLogical()); partition = new Partition(schemaFile.getLogical(), this.projection, schemaFile .getStorageString(), schemaFile.getComparator()); } else { /** * the typed schema from projection which is untyped or actually typed * as "bytes" */ this.projection = new Projection(schemaFile.getLogical(), projection); partition = new Partition(schemaFile.getLogical(), this.projection, schemaFile .getStorageString(), schemaFile.getComparator()); } inferredMapping = false; } /** * Get the status of the BasicTable. */ public BasicTableStatus getStatus() throws IOException { if (status == null) buildStatus(); return status; } /** * Given a split range, calculate how the file data that fall into the range * are distributed among hosts. * * @param split * The range-based split. Can be null to indicate the whole TFile. * @return An object that conveys how blocks fall in the split are * distributed across hosts. * @see #rangeSplit(int) */ public BlockDistribution getBlockDistribution(RangeSplit split) throws IOException { BlockDistribution bd = new BlockDistribution(); if (firstValidCG >= 0) { for (int nx = 0; nx < colGroups.length; nx++) { if (partition.isCGNeeded(nx) && !isCGDeleted(nx)) { bd.add(colGroups[nx].getBlockDistribution(split == null ? null : split.getCGRangeSplit())); } } } return bd; } /** * Given a row-based split, calculate how the file data that fall into the split * are distributed among hosts. * * @param split The row-based split. <i>Cannot</i> be null. * @return An object that conveys how blocks fall into the split are * distributed across hosts. */ public BlockDistribution getBlockDistribution(RowSplit split) throws IOException { BlockDistribution bd = new BlockDistribution(); int cgIdx = split.getCGIndex(); bd.add(colGroups[cgIdx].getBlockDistribution(split.getCGRowSplit())); return bd; } /** * Collect some key samples and use them to partition the table. Only * applicable to sorted BasicTable. The returned {@link KeyDistribution} * object also contains information on how data are distributed for each * key-partitioned bucket. * * @param n * Targeted size of the sampling. * @param nTables * Number of tables in union * @return KeyDistribution object. * @throws IOException */ public KeyDistribution getKeyDistribution(int n, int nTables, BlockDistribution lastBd) throws IOException { if (firstValidCG >= 0) { // pick the largest CG as in the row split case return colGroups[getRowSplitCGIndex()].getKeyDistribution(n, nTables, lastBd); } return null; } /** * Get a scanner that reads all rows whose row keys fall in a specific * range. Only applicable to sorted BasicTable. * * @param beginKey * The begin key of the scan range. If null, start from the first * row in the table. * @param endKey * The end key of the scan range. If null, scan till the last row * in the table. * @param closeReader * close the underlying Reader object when we close the scanner. * Should be set to true if we have only one scanner on top of the * reader, so that we should release resources after the scanner is * closed. * @return A scanner object. * @throws IOException */ public synchronized TableScanner getScanner(BytesWritable beginKey, BytesWritable endKey, boolean closeReader) throws IOException { try { checkInferredMapping(); } catch (Exception e) { throw new IOException("getScanner failed : " + e.getMessage()); } return new BTScanner(beginKey, endKey, closeReader, partition); } /** * Get a scanner that reads a consecutive number of rows as defined in the * {@link RangeSplit} object, which should be obtained from previous calls * of {@link #rangeSplit(int)}. * * @param split * The split range. If null, get a scanner to read the complete * table. * @param closeReader * close the underlying Reader object when we close the scanner. * Should be set to true if we have only one scanner on top of the * reader, so that we should release resources after the scanner is * closed. * @return A scanner object. * @throws IOException */ public synchronized TableScanner getScanner(RangeSplit split, boolean closeReader) throws IOException, ParseException { checkInferredMapping(); return new BTScanner(split, partition, closeReader); } /** * Get a scanner that reads a consecutive number of rows as defined in the * {@link RowSplit} object. * * @param closeReader * close the underlying Reader object when we close the scanner. * Should be set to true if we have only one scanner on top of the * reader, so that we should release resources after the scanner is * closed. * @param rowSplit split based on row numbers. * * @return A scanner object. * @throws IOException */ public synchronized TableScanner getScanner(boolean closeReader, RowSplit rowSplit) throws IOException, ParseException, ParseException { checkInferredMapping(); return new BTScanner(rowSplit, closeReader, partition); } /** * Get the schema of the table. The schema may be different from * {@link BasicTable.Reader#getSchema(Path, Configuration)} if a projection * has been set on the table. * * @return The schema of the BasicTable. */ public Schema getSchema() { return projection.getSchema(); } /** * Get the BasicTable schema without loading the full table index. * * @param path * The path to the BasicTable. * @deletedCGs * The deleted column groups from front end; null if unavailable from front end * @param conf * @return The logical Schema of the table (all columns). * @throws IOException */ public static Schema getSchema(Path path, Configuration conf) throws IOException { // fake an empty deleted cg list as getSchema does not care about deleted cgs SchemaFile schF = new SchemaFile(path, new String[0], conf); return schF.getLogical(); } /** * Get the path to the table. * * @return The path string to the table. */ public String getPath() { return path.toString(); } /** * Get the path filter used by the table. */ public PathFilter getPathFilter(Configuration conf) { ColumnGroup.CGPathFilter filter = new ColumnGroup.CGPathFilter(); ColumnGroup.CGPathFilter.setConf(conf); return filter; } /** * Split the table into at most n parts. * * @param n Maximum number of parts in the output list. * @return A list of RangeSplit objects, each of which can be used to * construct TableScanner later. */ public List<RangeSplit> rangeSplit(int n) throws IOException { // use the first non-deleted column group to do split, other column groups will be split exactly the same way. List<RangeSplit> ret; if (firstValidCG >= 0) { List<CGRangeSplit> cgSplits = colGroups[firstValidCG].rangeSplit(n); int numSlices = cgSplits.size(); ret = new ArrayList<RangeSplit>(numSlices); for (int slice = 0; slice < numSlices; slice++) { CGRangeSplit oneSliceSplit = cgSplits.get(slice); ret.add(new BasicTable.Reader.RangeSplit(oneSliceSplit)); } return ret; } else { // all column groups are dropped. ret = new ArrayList<RangeSplit>(1); // add a dummy split ret.add(new BasicTable.Reader.RangeSplit(new CGRangeSplit(0, 0))); return ret; } } /** * We already use FileInputFormat to create byte offset-based input splits. * Their information is encoded in starts, lengths and paths. This method is * to wrap this information to form RowSplit objects at basic table level. * * @param starts array of starting byte of fileSplits. * @param lengths array of length of fileSplits. * @param paths array of path of fileSplits. * @param splitCGIndex index of column group that is used to create fileSplits. * @return A list of RowSplit objects, each of which can be used to * construct a TableScanner later. * */ public List<RowSplit> rowSplit(long[] starts, long[] lengths, Path[] paths, int splitCGIndex, int[] batchSizes, int numBatches) throws IOException { List<RowSplit> ret; List<CGRowSplit> cgSplits = colGroups[splitCGIndex].rowSplit(starts, lengths, paths, batchSizes, numBatches); int numSlices = cgSplits.size(); ret = new ArrayList<RowSplit>(numSlices); for (int slice = 0; slice < numSlices; slice++) { CGRowSplit cgRowSplit = cgSplits.get(slice); ret.add(new BasicTable.Reader.RowSplit(splitCGIndex, cgRowSplit)); } return ret; } /** * Rearrange the files according to the column group index ordering * * @param filestatus array of FileStatus to be rearraged on */ public void rearrangeFileIndices(FileStatus[] fileStatus) throws IOException { colGroups[getRowSplitCGIndex()].rearrangeFileIndices(fileStatus); } /** * Get index of the column group that will be used for row-based split. * */ public int getRowSplitCGIndex() throws IOException { // Try to find the largest non-deleted and used column group by projection; // Try to find the largest non-deleted and used column group by projection; if (rowSplitCGIndex == -1) { int largestCGIndex = -1; long largestCGSize = -1; for (int i=0; i<colGroups.length; i++) { if (!partition.isCGNeeded(i) || isCGDeleted(i)) { continue; } ColumnGroup.Reader reader = colGroups[i]; BasicTableStatus btStatus = reader.getStatus(); long size = btStatus.getSize(); if (size > largestCGSize) { largestCGIndex = i; largestCGSize = size; } } /* We do have a largest non-deleted and used column group, and we use it to do split. */ if (largestCGIndex >= 0) { rowSplitCGIndex = largestCGIndex; } else if (firstValidCG >= 0) { /* If all projection columns are either deleted or non-existing, then we use the first non-deleted column group to do split if it exists. */ rowSplitCGIndex = firstValidCG; } } return rowSplitCGIndex; } /** * Close the BasicTable for reading. Resources are released. */ @Override public void close() throws IOException { if (!closed) { try { closed = true; metaReader.close(); for (int i = 0; i < colGroups.length; ++i) { if (colGroups[i] != null) { colGroups[i].close(); } } } finally { try { metaReader.close(); } catch (Exception e) { // no-op } for (int i = 0; i < colGroups.length; ++i) { try { colGroups[i].close(); } catch (Exception e) { // no-op } } } } } String getBTSchemaString() { return schemaFile.getBTSchemaString(); } String getStorageString() { return schemaFile.getStorageString(); } public String getDeletedCGs() { return schemaFile.getDeletedCGs(); } public static String getDeletedCGs(Path path, Configuration conf) throws IOException { SchemaFile schF = new SchemaFile(path, new String[0], conf); return schF.getDeletedCGs(); } private void buildStatus() throws IOException { status = new BasicTableStatus(); if (firstValidCG >= 0) { status.beginKey = colGroups[firstValidCG].getStatus().getBeginKey(); status.endKey = colGroups[firstValidCG].getStatus().getEndKey(); status.rows = colGroups[firstValidCG].getStatus().getRows(); } else { status.beginKey = new BytesWritable(new byte[0]); status.endKey = status.beginKey; status.rows = 0; } status.size = 0; for (int nx = 0; nx < colGroups.length; nx++) { if (colGroups[nx] != null) { status.size += colGroups[nx].getStatus().getSize(); } } } /** * Obtain an input stream for reading a meta block. * * @param name * The name of the meta block. * @return The input stream for reading the meta block. * @throws IOException * @throws MetaBlockDoesNotExist */ public DataInputStream getMetaBlock(String name) throws MetaBlockDoesNotExist, IOException { return metaReader.getMetaBlock(name); } /** * A range-based split on the metaReadertable.The content of the split is * implementation-dependent. */ public static class RangeSplit implements Writable { //CGRangeSplit[] slice; CGRangeSplit slice; RangeSplit(CGRangeSplit split) { slice = split; } /** * Default constructor. */ public RangeSplit() { // no-op } /** * @see Writable#readFields(DataInput) */ @Override public void readFields(DataInput in) throws IOException { for (int nx = 0; nx < 1; nx++) { CGRangeSplit cgrs = new CGRangeSplit(); cgrs.readFields(in); slice = cgrs; } } /** * @see Writable#write(DataOutput) */ @Override public void write(DataOutput out) throws IOException { //Utils.writeVInt(out, slice.length); //for (CGRangeSplit split : slice) { // split.write(out); //} slice.write(out); } //CGRangeSplit get(int index) { // return slice[index]; //} CGRangeSplit getCGRangeSplit() { return slice; } } /** * A row-based split on the zebra table; */ public static class RowSplit implements Writable { int cgIndex; // column group index where split lies on; CGRowSplit slice; RowSplit(int cgidx, CGRowSplit split) { this.cgIndex = cgidx; this.slice = split; } /** * Default constructor. */ public RowSplit() { // no-op } @Override public String toString() { StringBuilder sb = new StringBuilder(); sb.append("{cgIndex = " + cgIndex + "}\n"); sb.append(slice.toString()); return sb.toString(); } /** * @see Writable#readFields(DataInput) */ @Override public void readFields(DataInput in) throws IOException { this.cgIndex = Utils.readVInt(in); CGRowSplit cgrs = new CGRowSplit(); cgrs.readFields(in); this.slice = cgrs; } /** * @see Writable#write(DataOutput) */ @Override public void write(DataOutput out) throws IOException { Utils.writeVInt(out, cgIndex); slice.write(out); } int getCGIndex() { return cgIndex; } CGRowSplit getCGRowSplit() { return slice; } } /** * BasicTable scanner class */ private class BTScanner implements TableScanner { private Projection schema; private CGScanner[] cgScanners; private int opCount = 0; Random random = new Random(System.nanoTime()); // checking for consistency once every 1000 times. private static final int VERIFY_FREQ = 1000; private boolean sClosed = false; private boolean closeReader; private Partition partition; private synchronized boolean checkIntegrity() { return ((++opCount % VERIFY_FREQ) == 0) && (cgScanners.length > 1); } public BTScanner(BytesWritable beginKey, BytesWritable endKey, boolean closeReader, Partition partition) throws IOException { init(null, null, beginKey, endKey, closeReader, partition); } public BTScanner(RangeSplit split, Partition partition, boolean closeReader) throws IOException { init(null, split, null, null, closeReader, partition); } public BTScanner(RowSplit rowSplit, boolean closeReader, Partition partition) throws IOException { init(rowSplit, null, null, null, closeReader, partition); } /** * Creates new CGRowSplit. If the startRow in rowSplit is not set * (i.e. < 0), it sets the startRow and numRows based on 'startByte' * and 'numBytes' from given rowSplit. */ private CGRowSplit makeCGRowSplit(RowSplit rowSplit) throws IOException { CGRowSplit inputCGSplit = rowSplit.getCGRowSplit(); int cgIdx = rowSplit.getCGIndex(); CGRowSplit cgSplit = new CGRowSplit(); // Find the row range : if (isCGDeleted(cgIdx)) { throw new IOException("CG " + cgIdx + " is deleted."); } //fill the row numbers. colGroups[cgIdx].fillRowSplit(cgSplit, inputCGSplit); return cgSplit; } // Helper function for initialization. private CGScanner createCGScanner(int cgIndex, CGRowSplit cgRowSplit, RangeSplit rangeSplit, BytesWritable beginKey, BytesWritable endKey) throws IOException, ParseException, ParseException { if (cgRowSplit != null) { return colGroups[cgIndex].getScanner(false, cgRowSplit); } if (beginKey != null || endKey != null) { return colGroups[cgIndex].getScanner(beginKey, endKey, false); } return colGroups[cgIndex].getScanner ((rangeSplit == null ? null : rangeSplit.getCGRangeSplit()), false); } /** * If rowRange is not null, scanners will be created based on the * row range. <br> * If RangeSplit is not null, scaller will be based on the range, <br> * otherwise, these are based on keys. */ private void init(RowSplit rowSplit, RangeSplit rangeSplit, BytesWritable beginKey, BytesWritable endKey, boolean closeReader, Partition partition) throws IOException { this.partition = partition; boolean anyScanner = false; CGRowSplit cgRowSplit = null; if (rowSplit != null) { cgRowSplit = makeCGRowSplit(rowSplit); } try { schema = partition.getProjection(); cgScanners = new CGScanner[colGroups.length]; for (int i = 0; i < colGroups.length; ++i) { if (!isCGDeleted(i) && partition.isCGNeeded(i)) { anyScanner = true; cgScanners[i] = createCGScanner(i, cgRowSplit, rangeSplit, beginKey, endKey); } else cgScanners[i] = null; } if (!anyScanner && firstValidCG >= 0) { // if no CG is needed explicitly by projection but the "countRow" still needs to access some column group cgScanners[firstValidCG] = createCGScanner(firstValidCG, cgRowSplit, rangeSplit, beginKey, endKey); } this.closeReader = closeReader; sClosed = false; } catch (Exception e) { throw new IOException("BTScanner constructor failed : " + e.getMessage()); } finally { if (sClosed) { if (cgScanners != null) { for (int i = 0; i < cgScanners.length; ++i) { if (cgScanners[i] != null) { try { cgScanners[i].close(); cgScanners[i] = null; } catch (Exception e) { // no-op } } } } } } } @Override public boolean advance() throws IOException { boolean first = false, cur, firstAdvance = true; for (int nx = 0; nx < cgScanners.length; nx++) { if (cgScanners[nx] != null) { cur = cgScanners[nx].advanceCG(); if (!firstAdvance) { if (cur != first) { throw new IOException( "advance() failed: Column Groups are not evenly positioned."); } } else { firstAdvance = false; first = cur; } } } return first; } @Override public boolean atEnd() throws IOException { boolean ret = true; int i; for (i = 0; i < cgScanners.length; i++) { if (cgScanners[i] != null) { ret = cgScanners[i].atEnd(); break; } } if (i == cgScanners.length) { return true; } if (!checkIntegrity()) { return ret; } while (true) { int index = random.nextInt(cgScanners.length); if (cgScanners[index] != null) { if (cgScanners[index].atEnd() != ret) { throw new IOException( "atEnd() failed: Column Groups are not evenly positioned."); } break; } } return ret; } @Override public void getKey(BytesWritable key) throws IOException { int i; for (i = 0; i < cgScanners.length; i++) { if (cgScanners[i] != null) { cgScanners[i].getCGKey(key); break; } } if (i == cgScanners.length) return; if (!checkIntegrity()) { return; } while (true) { int index = random.nextInt(cgScanners.length); if (cgScanners[index] != null) { BytesWritable key2 = new BytesWritable(); cgScanners[index].getCGKey(key2); if (key.equals(key2)) { return; } break; } } throw new IOException( "getKey() failed: Column Groups are not evenly positioned."); } @Override public void getValue(Tuple row) throws IOException { if (row.size() < projection.getSchema().getNumColumns()) { throw new IOException("Mismatched tuple object"); } for (int i = 0; i < cgScanners.length; ++i) { if (cgScanners[i] != null) { if (partition.isCGNeeded(i)) { if (cgTuples[i] == null) throw new AssertionError("cgTuples["+i+"] is null"); cgScanners[i].getCGValue(cgTuples[i]); } } } try { partition.read(row); } catch (Exception e) { throw new IOException("getValue() failed: " + e.getMessage()); } } @Override public boolean seekTo(BytesWritable key) throws IOException { boolean first = false, cur, firstset = false; for (int nx = 0; nx < cgScanners.length; nx++) { if (cgScanners[nx] == null) continue; cur = cgScanners[nx].seekTo(key); if (firstset) { if (cur != first) { throw new IOException( "seekTo() failed: Column Groups are not evenly positioned."); } } else { first = cur; firstset = true; } } return first; } @Override public void seekToEnd() throws IOException { for (int nx = 0; nx < cgScanners.length; nx++) { if (cgScanners[nx] == null) continue; cgScanners[nx].seekToEnd(); } } @Override public String getProjection() { return schema.toString(); } @Override public Schema getSchema() { return schema.getSchema(); } @Override public void close() throws IOException { if (sClosed) return; sClosed = true; try { for (int nx = 0; nx < cgScanners.length; nx++) { if (cgScanners[nx] == null) continue; cgScanners[nx].close(); cgScanners[nx] = null; } if (closeReader) { BasicTable.Reader.this.close(); } } finally { for (int nx = 0; nx < cgScanners.length; nx++) { if (cgScanners[nx] == null) continue; try { cgScanners[nx].close(); cgScanners[nx] = null; } catch (Exception e) { // no-op } } if (closeReader) { try { BasicTable.Reader.this.close(); } catch (Exception e) { // no-op } } } } } } /** * BasicTable writer. */ public static class Writer implements Closeable { private SchemaFile schemaFile; private MetaFile.Writer metaWriter; private boolean closed = true; ColumnGroup.Writer[] colGroups; Partition partition; boolean sorted; private boolean finished; Tuple[] cgTuples; private Path actualOutputPath; private Configuration writerConf; /** * Create a BasicTable writer. The semantics are as follows: * <ol> * <li>If path does not exist: * <ul> * <li>create the path directory, and initialize the directory for future * row insertion.. * </ul> * <li>If path exists and the directory is empty: initialize the directory * for future row insertion. * <li>If path exists and contains what look like a complete BasicTable, * IOException will be thrown. * </ol> * This constructor never removes a valid/complete BasicTable. * * @param path * The path to the Basic Table, either not existent or must be a * directory. * @param btSchemaString * The schema of the Basic Table. For this version of * implementation, the schema of a table is a comma or * semicolon-separated list of column names, such as * "FirstName, LastName; Sex, Department". * @param sortColumns * String of comma-separated sorted columns: null for unsorted tables * @param comparator * Name of the comparator used in sorted tables * @param conf * Optional Configuration objects. * * @throws IOException * @see Schema */ public Writer(Path path, String btSchemaString, String btStorageString, String sortColumns, String comparator, Configuration conf) throws IOException { try { actualOutputPath = path; writerConf = conf; schemaFile = new SchemaFile(path, btSchemaString, btStorageString, sortColumns, comparator, conf); partition = schemaFile.getPartition(); int numCGs = schemaFile.getNumOfPhysicalSchemas(); colGroups = new ColumnGroup.Writer[numCGs]; cgTuples = new Tuple[numCGs]; sorted = schemaFile.isSorted(); for (int nx = 0; nx < numCGs; nx++) { colGroups[nx] = new ColumnGroup.Writer( new Path(path, schemaFile.getName(nx)), schemaFile.getPhysicalSchema(nx), sorted, comparator, schemaFile.getName(nx), schemaFile.getSerializer(nx), schemaFile.getCompressor(nx), schemaFile.getOwner(nx), schemaFile.getGroup(nx), schemaFile.getPerm(nx), false, conf); cgTuples[nx] = TypesUtils.createTuple(colGroups[nx].getSchema()); } metaWriter = MetaFile.createWriter(new Path(path, BT_META_FILE), conf); partition.setSource(cgTuples); closed = false; } catch (Exception e) { throw new IOException("ColumnGroup.Writer constructor failed : " + e.getMessage()); } finally { ; if (!closed) return; if (metaWriter != null) { try { metaWriter.close(); } catch (Exception e) { // no-op } } if (colGroups != null) { for (int i = 0; i < colGroups.length; ++i) { if (colGroups[i] != null) { try { colGroups[i].close(); } catch (Exception e) { // no-op } } } } } } /** * a wrapper to support backward compatible constructor */ public Writer(Path path, String btSchemaString, String btStorageString, Configuration conf) throws IOException { this(path, btSchemaString, btStorageString, null, null, conf); } /** * Reopen an already created BasicTable for writing. Exception will be * thrown if the table is already closed, or is in the process of being * closed. */ public Writer(Path path, Configuration conf) throws IOException { try { actualOutputPath = path; writerConf = conf; if (ZebraConf.getOutputSchema(conf) != null) { schemaFile = new SchemaFile(conf); // Read out schemaFile from conf, instead of from hdfs; } else { // This is only for io test cases and it cannot happen for m/r and pig cases; schemaFile = new SchemaFile(path, new String[0], conf); // fake an empty deleted cg list as no cg should have been deleted now } int numCGs = schemaFile.getNumOfPhysicalSchemas(); partition = schemaFile.getPartition(); sorted = schemaFile.isSorted(); colGroups = new ColumnGroup.Writer[numCGs]; cgTuples = new Tuple[numCGs]; Path tmpWorkPath = new Path(path, "_temporary"); for (int nx = 0; nx < numCGs; nx++) { CGSchema cgschema = new CGSchema(schemaFile.getPhysicalSchema(nx), sorted, schemaFile.getComparator(), schemaFile.getName(nx), schemaFile.getSerializer(nx), schemaFile.getCompressor(nx), schemaFile.getOwner(nx), schemaFile.getGroup(nx), schemaFile.getPerm(nx)); colGroups[nx] = new ColumnGroup.Writer( new Path(path, partition.getCGSchema(nx).getName()), new Path(tmpWorkPath, partition.getCGSchema(nx).getName()), cgschema,conf); cgTuples[nx] = TypesUtils.createTuple(colGroups[nx].getSchema()); } partition.setSource(cgTuples); metaWriter = MetaFile.createWriter(new Path(path, BT_META_FILE), conf); closed = false; } catch (Exception e) { throw new IOException("ColumnGroup.Writer failed : " + e.getMessage()); } finally { if (!closed) return; if (metaWriter != null) { try { metaWriter.close(); } catch (Exception e) { // no-op } } if (colGroups != null) { for (int i = 0; i < colGroups.length; ++i) { if (colGroups[i] != null) { try { colGroups[i].close(); } catch (Exception e) { // no-op } } } } } } /** * Release resources used by the object. Unlike close(), finish() does not * make the table immutable. */ public void finish() throws IOException { if (finished) return; finished = true; try { for (int nx = 0; nx < colGroups.length; nx++) { if (colGroups[nx] != null) { colGroups[nx].finish(); } } metaWriter.finish(); } finally { try { metaWriter.finish(); } catch (Exception e) { // no-op } for (int i = 0; i < colGroups.length; ++i) { try { colGroups[i].finish(); } catch (Exception e) { // no-op } } } } /** * Close the BasicTable for writing. No more inserters can be obtained after * close(). */ @Override public void close() throws IOException { cleanupTempDir(); if (closed) return; closed = true; if (!finished) finish(); try { ColumnGroup.CGIndex firstCGIndex = null, cgIndex; int first = -1; for (int nx = 0; nx < colGroups.length; nx++) { if (colGroups[nx] != null) { colGroups[nx].close(); if (first == -1) { first = nx; firstCGIndex = colGroups[nx].index; } else { cgIndex = colGroups[nx].index; if (cgIndex.size() != firstCGIndex.size()) throw new IOException("Column Group "+colGroups[nx].path.getName()+ " has different number of files than in column group " + colGroups[first].path.getName()); int size = firstCGIndex.size(); for (int i = 0; i < size; i++) { if (!cgIndex.get(i).name.equals(firstCGIndex.get(i).name)) throw new IOException("File["+i+"] in Column Group "+colGroups[nx].path.getName()+ " has a different name: "+cgIndex.get(i).name+" than " + firstCGIndex.get(i).name + " in column group " + colGroups[first].path.getName()); if (cgIndex.get(i).rows != firstCGIndex.get(i).rows) throw new IOException("File "+cgIndex.get(i).name+"Column Group "+colGroups[nx].path.getName()+ " has a different number of rows, " + cgIndex.get(i).rows + ", than " + firstCGIndex.get(i).rows + " in column group " + colGroups[first].path.getName()); } } } } metaWriter.close(); } finally { try { metaWriter.close(); } catch (Exception e) { // no-op } for (int i = 0; i < colGroups.length; ++i) { try { colGroups[i].close(); } catch (Exception e) { // no-op } } } } /** * Removes the temporary directory underneath * $path/_temporary used to create intermediate data * during recrd writing */ private void cleanupTempDir() throws IOException { FileSystem fileSys = actualOutputPath.getFileSystem(writerConf); Path pathToRemove = new Path(actualOutputPath, "_temporary"); if (fileSys.exists(pathToRemove)) { if(!fileSys.delete(pathToRemove, true)) { LOG.error("Failed to delete the temporary output" + " directory: " + pathToRemove.toString()); } } } /** * Get the schema of the table. * * @return the Schema object. */ public Schema getSchema() { return schemaFile.getLogical(); } /** * @return sortness */ public boolean isSorted() { return sorted; } /** * Get the list of sorted columns. * @return the list of sorted columns */ public SortInfo getSortInfo() { return schemaFile.getSortInfo(); } /** * Get a inserter with a given name. * * @param name * the name of the inserter. If multiple calls to getInserter with * the same name has been called, we expect they are the result of * speculative execution and at most one of them will succeed. * @param finishWriter * finish the underlying Writer object upon the close of the * Inserter. Should be set to true if there is only one inserter * operate on the table, so we should call finish() after the * Inserter is closed. * * @return A inserter object. * @throws IOException */ public TableInserter getInserter(String name, boolean finishWriter) throws IOException { return this.getInserter(name, finishWriter, true); } /** * Get a inserter with a given name. * * @param name * the name of the inserter. If multiple calls to getInserter with * the same name has been called, we expect they are the result of * speculative execution and at most one of them will succeed. * @param finishWriter * finish the underlying Writer object upon the close of the * Inserter. Should be set to true if there is only one inserter * operate on the table, so we should call finish() after the * Inserter is closed. * @param checktype * whether or not do type check. * * @return A inserter object. * @throws IOException */ public TableInserter getInserter(String name, boolean finishWriter, boolean checkType) throws IOException { if (closed) { throw new IOException("BasicTable closed"); } return new BTInserter(name, finishWriter, partition, checkType); } /** * Obtain an output stream for creating a Meta Block with the specific name. * This method can only be called after we insert all rows into the table. * All Meta Blocks must be created by a single process prior to closing the * table. No more inserter can be created after this call. * * @param name * The name of the Meta Block * @return The output stream. Close the stream to conclude the writing. * @throws IOException * @throws MetaBlockAlreadyExists */ public DataOutputStream createMetaBlock(String name) throws MetaBlockAlreadyExists, IOException { return metaWriter.createMetaBlock(name); } private class BTInserter implements TableInserter { private TableInserter cgInserters[]; private boolean sClosed = true; private boolean finishWriter; private Partition partition = null; BTInserter(String name, boolean finishWriter, Partition partition) throws IOException { this(name, finishWriter, partition, true); } BTInserter(String name, boolean finishWriter, Partition partition, boolean checkType) throws IOException { try { cgInserters = new ColumnGroup.Writer.CGInserter[colGroups.length]; for (int nx = 0; nx < colGroups.length; nx++) { cgInserters[nx] = colGroups[nx].getInserter(name, false, checkType); } this.finishWriter = finishWriter; this.partition = partition; sClosed = false; } catch (Exception e) { throw new IOException("BTInsert constructor failed :" + e.getMessage()); } finally { if (sClosed) { if (cgInserters != null) { for (int i = 0; i < cgInserters.length; ++i) { if (cgInserters[i] != null) { try { cgInserters[i].close(); } catch (Exception e) { // no-op } } } } } } } @Override public Schema getSchema() { return Writer.this.getSchema(); } @Override public void insert(BytesWritable key, Tuple row) throws IOException { if (sClosed) { throw new IOException("Inserter already closed"); } // break the input row into sub-tuples, then insert them into the // corresponding CGs int curTotal = 0; try { partition.insert(key, row); } catch (Exception e) { throw new IOException("insert failed : " + e.getMessage()); } for (int nx = 0; nx < colGroups.length; nx++) { Tuple subTuple = cgTuples[nx]; int numCols = subTuple.size(); cgInserters[nx].insert(key, subTuple); curTotal += numCols; } } @Override public void close() throws IOException { if (sClosed) return; sClosed = true; try { for (TableInserter ins : cgInserters) { ins.close(); } if (finishWriter) { BasicTable.Writer.this.finish(); } } finally { for (TableInserter ins : cgInserters) { try { ins.close(); } catch (Exception e) { // no-op } } if (finishWriter) { try { BasicTable.Writer.this.finish(); } catch (Exception e) { // no-op } } } } } } /** * Drop a Basic Table, all files consisting of the BasicTable will be removed. * * @param path * the path to the Basic Table. * @param conf * The configuration object. * @throws IOException */ public static void drop(Path path, Configuration conf) throws IOException { FileSystem fs = path.getFileSystem(conf); fs.delete(path, true); } static class SchemaFile { private Version version; String comparator; Schema logical; Schema[] physical; Partition partition; boolean sorted; SortInfo sortInfo = null; String storage; CGSchema[] cgschemas; // Array indicating if a physical schema is already dropped // It is probably better to create "CGProperties" class and // store multiple properties like name there. boolean[] cgDeletedFlags; // ctor for reading public SchemaFile(Path path, String[] deletedCGs, Configuration conf) throws IOException { readSchemaFile(path, deletedCGs, conf); } // ctor for reading from a job configuration object; we do not need a table path; // all information is held in the job configuration object. public SchemaFile(Configuration conf) throws IOException { String logicalStr = ZebraConf.getOutputSchema(conf); storage = ZebraConf.getOutputStorageHint(conf); String sortColumns = ZebraConf.getOutputSortColumns(conf) != null ? ZebraConf.getOutputSortColumns(conf) : ""; comparator = ZebraConf.getOutputComparator(conf) != null ? ZebraConf.getOutputComparator(conf) : ""; version = SCHEMA_VERSION; try { logical = new Schema(logicalStr); } catch (Exception e) { throw new IOException("Schema build failed :" + e.getMessage()); } try { partition = new Partition(logicalStr, storage, comparator, sortColumns); } catch (Exception e) { throw new IOException("Partition constructor failed :" + e.getMessage()); } cgschemas = partition.getCGSchemas(); physical = new Schema[cgschemas.length]; //cgDeletedFlags = new boolean[physical.length]; for (int nx = 0; nx < cgschemas.length; nx++) { physical[nx] = cgschemas[nx].getSchema(); } this.sortInfo = partition.getSortInfo(); this.sorted = partition.isSorted(); this.comparator = (this.sortInfo == null ? null : this.sortInfo.getComparator()); if (this.comparator == null) this.comparator = ""; String[] sortColumnStr = sortColumns.split(","); if (sortColumnStr.length > 0) { sortInfo = SortInfo.parse(SortInfo.toSortString(sortColumnStr), logical, comparator); } } public Schema[] getPhysicalSchema() { return physical; } // ctor for writing public SchemaFile(Path path, String btSchemaStr, String btStorageStr, String sortColumns, String btComparator, Configuration conf) throws IOException { storage = btStorageStr; try { partition = new Partition(btSchemaStr, btStorageStr, btComparator, sortColumns); } catch (Exception e) { throw new IOException("Partition constructor failed :" + e.getMessage()); } this.sortInfo = partition.getSortInfo(); this.sorted = partition.isSorted(); this.comparator = (this.sortInfo == null ? null : this.sortInfo.getComparator()); if (this.comparator == null) this.comparator = ""; logical = partition.getSchema(); cgschemas = partition.getCGSchemas(); physical = new Schema[cgschemas.length]; for (int nx = 0; nx < cgschemas.length; nx++) { physical[nx] = cgschemas[nx].getSchema(); } cgDeletedFlags = new boolean[physical.length]; version = SCHEMA_VERSION; // write out the schema createSchemaFile(path, conf); } public String getComparator() { return comparator; } public Partition getPartition() { return partition; } public boolean isSorted() { return sorted; } public SortInfo getSortInfo() { return sortInfo; } public Schema getLogical() { return logical; } public int getNumOfPhysicalSchemas() { return physical.length; } public Schema getPhysicalSchema(int nx) { return physical[nx]; } public String getName(int nx) { return cgschemas[nx].getName(); } public String getSerializer(int nx) { return cgschemas[nx].getSerializer(); } public String getCompressor(int nx) { return cgschemas[nx].getCompressor(); } /** * Returns the index for CG with the given name. -1 indicates that there is * no CG with the name. */ int getCGByName(String cgName) { for(int i=0; i<physical.length; i++) { if (cgName.equals(getName(i))) { return i; } } return -1; } /** Returns if the CG at the given index is delete */ boolean isCGDeleted(int idx) { return cgDeletedFlags[idx]; } public String getOwner(int nx) { return cgschemas[nx].getOwner(); } public String getGroup(int nx) { return cgschemas[nx].getGroup(); } public short getPerm(int nx) { return cgschemas[nx].getPerm(); } /** * @return the string representation of the physical schema. */ public String getBTSchemaString() { return logical.toString(); } /** * @return the string representation of the storage hints */ public String getStorageString() { return storage; } private void createSchemaFile(Path path, Configuration conf) throws IOException { // TODO: overwrite existing schema file, or need a flag? FSDataOutputStream outSchema = path.getFileSystem(conf).create(makeSchemaFilePath(path), true); version.write(outSchema); WritableUtils.writeString(outSchema, comparator); WritableUtils.writeString(outSchema, logical.toString()); WritableUtils.writeString(outSchema, storage); WritableUtils.writeVInt(outSchema, physical.length); for (int nx = 0; nx < physical.length; nx++) { WritableUtils.writeString(outSchema, physical[nx].toString()); } WritableUtils.writeVInt(outSchema, sorted ? 1 : 0); WritableUtils.writeVInt(outSchema, sortInfo == null ? 0 : sortInfo.size()); if (sortInfo != null && sortInfo.size() > 0) { String[] sortedCols = sortInfo.getSortColumnNames(); for (int i = 0; i < sortInfo.size(); i++) { WritableUtils.writeString(outSchema, sortedCols[i]); } } outSchema.close(); } private void readSchemaFile(Path path, String[] deletedCGs, Configuration conf) throws IOException { Path pathSchema = makeSchemaFilePath(path); if (!path.getFileSystem(conf).exists(pathSchema)) { throw new IOException("BT Schema file doesn't exist: " + pathSchema); } // read schema file FSDataInputStream in = path.getFileSystem(conf).open(pathSchema); version = new Version(in); // verify compatibility against SCHEMA_VERSION if (!version.compatibleWith(SCHEMA_VERSION)) { new IOException("Incompatible versions, expecting: " + SCHEMA_VERSION + "; found in file: " + version); } comparator = WritableUtils.readString(in); String logicalStr = WritableUtils.readString(in); try { logical = new Schema(logicalStr); } catch (Exception e) { ; throw new IOException("Schema build failed :" + e.getMessage()); } storage = WritableUtils.readString(in); try { partition = new Partition(logicalStr, storage, comparator); } catch (Exception e) { throw new IOException("Partition constructor failed :" + e.getMessage()); } cgschemas = partition.getCGSchemas(); int numCGs = WritableUtils.readVInt(in); physical = new Schema[numCGs]; cgDeletedFlags = new boolean[physical.length]; TableSchemaParser parser; String cgschemastr; try { for (int nx = 0; nx < numCGs; nx++) { cgschemastr = WritableUtils.readString(in); parser = new TableSchemaParser(new StringReader(cgschemastr)); physical[nx] = parser.RecordSchema(null); } } catch (Exception e) { throw new IOException("parser.RecordSchema failed :" + e.getMessage()); } sorted = WritableUtils.readVInt(in) == 1 ? true : false; if (deletedCGs == null) setCGDeletedFlags(path, conf); else { for (String deletedCG : deletedCGs) { for (int i = 0; i < cgschemas.length; i++) { if (cgschemas[i].getName().equals(deletedCG)) cgDeletedFlags[i] = true; } } } if (version.compareTo(new Version((short)1, (short)0)) > 0) { int numSortColumns = WritableUtils.readVInt(in); if (numSortColumns > 0) { String[] sortColumnStr = new String[numSortColumns]; for (int i = 0; i < numSortColumns; i++) { sortColumnStr[i] = WritableUtils.readString(in); } sortInfo = SortInfo.parse(SortInfo.toSortString(sortColumnStr), logical, comparator); } } in.close(); } private static int getNumCGs(Path path, Configuration conf) throws IOException { Path pathSchema = makeSchemaFilePath(path); if (!path.getFileSystem(conf).exists(pathSchema)) { throw new IOException("BT Schema file doesn't exist: " + pathSchema); } // read schema file FSDataInputStream in = path.getFileSystem(conf).open(pathSchema); Version version = new Version(in); // verify compatibility against SCHEMA_VERSION if (!version.compatibleWith(SCHEMA_VERSION)) { new IOException("Incompatible versions, expecting: " + SCHEMA_VERSION + "; found in file: " + version); } // read comparator WritableUtils.readString(in); // read logicalStr WritableUtils.readString(in); // read storage WritableUtils.readString(in); int numCGs = WritableUtils.readVInt(in); in.close(); return numCGs; } private static Path makeSchemaFilePath(Path parent) { return new Path(parent, BT_SCHEMA_FILE); } /** * Sets cgDeletedFlags array by checking presense of * ".deleted-CGNAME" directory in the table top level * directory. */ void setCGDeletedFlags(Path path, Configuration conf) throws IOException { Set<String> deletedCGs = new HashSet<String>(); for (FileStatus file : path.getFileSystem(conf).listStatus(path)) { if (!file.isDir()) { String fname = file.getPath().getName(); if (fname.startsWith(DELETED_CG_PREFIX)) { deletedCGs.add(fname.substring(DELETED_CG_PREFIX.length())); } } } for(int i=0; i<physical.length; i++) { cgDeletedFlags[i] = deletedCGs.contains(getName(i)); } } String getDeletedCGs() { StringBuilder sb = new StringBuilder(); // comma separated boolean first = true; for (int i = 0; i < physical.length; i++) { if (cgDeletedFlags[i]) { if (first) first = false; else { sb.append(DELETED_CG_SEPARATOR_PER_TABLE); } sb.append(getName(i)); } } return sb.toString(); } } static public void dumpInfo(String file, PrintStream out, Configuration conf) throws IOException { dumpInfo(file, out, conf, 0); } static public void dumpInfo(String file, PrintStream out, Configuration conf, int indent) throws IOException { IOutils.indent(out, indent); out.println("Basic Table : " + file); Path path = new Path(file); try { BasicTable.Reader reader = new BasicTable.Reader(path, conf); String schemaStr = reader.getBTSchemaString(); String storageStr = reader.getStorageString(); IOutils.indent(out, indent); out.printf("Schema : %s\n", schemaStr); IOutils.indent(out, indent); out.printf("Storage Information : %s\n", storageStr); SortInfo sortInfo = reader.getSortInfo(); if (sortInfo != null && sortInfo.size() > 0) { IOutils.indent(out, indent); String[] sortedCols = sortInfo.getSortColumnNames(); out.println("Sorted Columns :"); for (int nx = 0; nx < sortedCols.length; nx++) { if (nx > 0) out.printf(" , "); out.printf("%s", sortedCols[nx]); } out.printf("\n"); } IOutils.indent(out, indent); out.println("Column Groups within the Basic Table :"); for (int nx = 0; nx < reader.colGroups.length; nx++) { IOutils.indent(out, indent); out.printf("\nColumn Group [%d] :", nx); if (reader.colGroups[nx] != null) { ColumnGroup.dumpInfo(reader.colGroups[nx].path, out, conf, indent); } else { // print basic info for deleted column groups. out.printf("\nColum Group : DELETED"); out.printf("\nName : %s", reader.schemaFile.getName(nx)); out.printf("\nSchema : %s\n", reader.schemaFile.cgschemas[nx].getSchema().toString()); } } } catch (Exception e) { throw new IOException("BasicTable.Reader failed : " + e.getMessage()); } finally { // no-op } } public static void main(String[] args) { System.out.printf("BasicTable Dumper\n"); if (args.length == 0) { System.out .println("Usage: java ... org.apache.hadoop.zebra.io.BasicTable path [path ...]"); System.exit(0); } Configuration conf = new Configuration(); for (String file : args) { try { dumpInfo(file, System.out, conf); } catch (IOException e) { e.printStackTrace(System.err); } } } }