/* * XXL: The eXtensible and fleXible Library for data processing * * Copyright (C) 2000-2014 Prof. Dr. Bernhard Seeger Head of the Database Research Group Department * of Mathematics and Computer Science University of Marburg Germany * * This library is free software; you can redistribute it and/or modify it under the terms of the * GNU Lesser General Public License as published by the Free Software Foundation; either version 3 * of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without * even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License along with this library; * If not, see <http://www.gnu.org/licenses/>. * * http://code.google.com/p/xxl/ */ package xxl.core.indexStructures.builder.BPlusTree; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.rmi.NoSuchObjectException; import java.sql.ResultSetMetaData; import java.util.Arrays; import java.util.SortedSet; import xxl.core.collections.containers.Container; import xxl.core.collections.containers.MapContainer; import xxl.core.collections.containers.io.BlockFileContainer; import xxl.core.collections.containers.io.BufferedContainer; import xxl.core.indexStructures.BPlusIndexedSet; import xxl.core.indexStructures.BPlusTree; import xxl.core.indexStructures.builder.IndexBuilder; import xxl.core.indexStructures.builder.IndexConfiguration; import xxl.core.io.Buffer; import xxl.core.io.LRUBuffer; import xxl.core.io.converters.meta.ExtendedResultSetMetaData; import xxl.core.io.converters.meta.KeyFunctionFactory; import xxl.core.io.converters.MeasuredTupleConverter; import xxl.core.io.converters.MeasuredConverter; import xxl.core.io.propertyList.PropertyList; import xxl.core.io.propertyList.PropertyListReader; import xxl.core.io.propertyList.json.JSONReader; import xxl.core.relational.JavaType; import xxl.core.relational.metaData.TupleMetaData; import xxl.core.util.ConvertUtils; import xxl.core.util.FileUtils; /** * This implementation of {@link IndexConfiguration} base class allows to setup all requirements for * building a {@link BPlusTree}. It contains the two nested classes {@link Creator} and * {@link Loader} which handle either creating or reloading the wrapper class * {@link BPlusIndexedSet} which is an implementation of Java's {@link SortedSet}. Use * {@link Creator} to setup a completely new BPlusTree. <br/> * <br/> * <b>Example</b> This example shows how to create a indexed set which manages <i>timestamp</i> * data. <code><pre> * BPlusConfiguration.Creator requirements = new BPlusConfiguration.Creator(new PrimitiveType(JavaType.TIMESTAMP, "TimestampTable")); * BPlusIndexedSet myTree = requirements.storeAtFileSystem("myStorage/myTree").getBuilder().create(); * // add some data * myTree.save(); * </pre></code> * * <b>Example</b> This example shows how to reload the indexed set from the previous example. * <code><pre> * BPlusTreeBuilder builder = new BPlusConfiguration.Loader("myStorage/myTree/TimestampTableMeta.json").getBuilder(); * BPlusIndexedSet<Long> myRestoredTree = builder.create(); * </pre></code> * * @author Marcus Pinnecke (pinnecke@mathematik.uni-marburg.de) * * @see Creator Create a BPlus tree indexed set with <i>Creator</i> * @see Loader Loading a BPluss tree index set with <i>Loader</i> */ public abstract class BPlusConfiguration extends IndexConfiguration { /** * A concrete implementation of {@link IndexConfiguration} for setting up the requirements of a * {@link BPlusTree} according to your needs. With an object of * <code>BPlusTreeConfiguration</code> type you can define whatever your BPlusTree should be kept * in memory or stored on the hard drive, the block size (in Byte), the buffer size, the needed * {@link Container Containers} and which table schema you want to manage including a set of key * which should be used for the indexing job. If you don't make the use of any setter methods a * BPlusTree with the following configuration will be created by <i>default</i>: * <ul> * <li>The block size used it set to 4096 Bytes (4 KB)</li> * <li>A single (unshared) {@link LRUBuffer} with size of 20 items is used</li> * <li>The converter container is a {@link MapContainer}</li> * <li>The tree will be kept in main memory</li> * <li>The first column of your table will be the (single) index key</li> * </ul> * If you want to change this configuration just use the corresponding <i>setter</i> methods. <br/> * <br/> * <b>Note:</b> Ensure that there is no index out of bounds when setting up a compounded key, see * {@link ResultSetMetaData} behavior for attribute index design in contrast to "<i>normal</i>" * item index of e.g. an array. Please note that the first item index is one not zero for table * columns. * * @author Marcus Pinnecke (pinnecke@mathematik.uni-marburg.de) * * @see BPlusTreeBuilder Easily build a BPlus tree with a given BPlusTreeConfiguration * @see IndexConfiguration The base class for all index configurations * @see BPlusTree The BPlus tree index structure itself * */ public static class Creator extends BPlusConfiguration { /** * Constructs a new <code>BPlusTreeConfiguration</code>.<br/> * <br/> * <b>Example</b> <code><pre> * TupleMetaData myTupleMetaData = new TupleMetaData("Students", new ColumnMetaInfo [] { * new ColumnMetaInfo (RelationalType.INTEGER, "ID") * new ColumnMetaInfo (RelationalType.VARCHAR, 20, "name")}); * * BPlusTreeConfiguration configuration = new BPlusTreeConfiguration(new TupleType(myTupleMetaData)); * </pre></code> * * @param primitiveType The meta data for your table which should be managed * * @see TupleMetaData * @see ManagedType */ public Creator(ManagedType primitiveType) { mManagedType = primitiveType; } /** * Returns a builder for a concrete <code>BPlusTree</code> which satisfies your requirements * which you have specified. Please node that the method {@link IndexBuilder#create()} returns * the <code>BPlusTree</code> ready to use. * * @see IndexBuilder * */ @Override public BPlusTreeBuilder getBuilder() { return new BPlusTreeBuilder(this); } /** * Returns an array of indices which should be used as compounded key for indexing. Please node * that this returns an integer array in which the first item is at position zero whereas the * first <i>column</i> index for the table is one not zero. * * <br/> * <br/> * <b>Note</b>: Because this only used by the BPlus tree builder the visibility of this method * is restricted to package wide visibility. * * @return The indices of the compounded key */ int[] getCompoundKeyIndices() { return mManagedType.getCompoundKeyIndices(); } /** * Returns the content type of the data managed by the tree. This is a string which represents * the type of data. The string starts with "primitive/" (for primitive types) or "complex/" * (for e.g. tuples) followed by a identifier. * * @see ManagedType#getContentType() * @return the current content type */ public String getContentType() { return mManagedType.getContentType(); } /** * Returns the meta data for the table to manage. * * <br/> * <br/> * <b>Note</b>: Because this only used by the BPlus tree builder the visibility of this method * is restricted to package wide visibility. * * @return the given meta data * * @see ExtendedResultSetMetaData */ public ExtendedResultSetMetaData getMetaData() { return mManagedType.getMetaData(); } /** * Returns the table name which is given by the user by constructor call of {@link Creator}. * * @return the table name */ public String getTableName() { return mManagedType.getTableName(); } /** * Sets the block size used by the BPlusTree. * * @param blockSize the block size */ public void setBlockSize(int blockSize) { mBlockSize = blockSize; } /** * Sets the buffer used for the BPlus tree. By default it is a single (unshared) * {@link LRUBuffer} with a capacity of INDEX_REQUIREMENTS_DEFAULT_LRU_BUFFER_SIZE items. When * setting a new buffer feel free to use a shared buffered for multiple trees. * * @param buffer A buffer which should be used by the BPlus tree * @return The current <code>BPlusTreeConfiguration</code> instance including the effect of this * method call. With this it is possible to set the configuration in one single line * like * * <code><pre>BPlusTreeConfiguration con = new BPlusTreeConfiguration(..).setA().setB()...</pre></code> * * instead of setting each property in a single call like <code><pre> * BPlusTreeConfiguration con = new BPlusTreeConfiguration(..); * con.setA(); * con.setB(); * ... * </pre></code> */ public Creator setBuffer(Buffer buffer) { mBuffer = buffer; setBuffer(null); updateBufferContainer(); return this; } /** * Set the indices of the column which should be used as the compounded key for indexing. The * tuples are compared lexicographically in descending order of their key indices given by * <code>compoundKeyIndices</code>. The order of the table columns is the order in which you * define it in <code>compoundKeyIndices</code> array. Thus, the first column (according to the * first array item value) is compared with the first column of another tuple (according to the * first array item value). * * <br/> * <br/> * If there is more than one key column, lets say two, the second columns are compared if there * is equality in the first column for both tuples. * * <br/> * <br/> * Please mark that e.g. a <code>compoundKeyIndices</code> array <code>[1,2]</code> first * compares the <i>first</i> column and after this (if necessary) the <i>second</i> column. * Whereas <code>[2,1]</code> forces to compare at first the <i>second</i> column and after this * (if necessary) the <i>first</i> column. Here, it must be ensured that the components of a * tuple, which form a key, have to be <b>comparable</b> and also that there will be no * duplicate values for a (compounded) key, so that a (compounded) key uniquely identifies a * tuple. * * <br/> * <br/> * <b>Please note:</b> The first column has the index one not zero whereas the first array item * has the index zero. * * @param compoundKeyIndices An array that contains the column indices. Please note that the * order of the columns matters for comparing and that each column index is in bounds of * the table column count. * * @return The current <code>BPlusTreeConfiguration</code> instance including the effect of this * method call. With this it is possible to set the configuration in one single line * like * * <code><pre>BPlusTreeConfiguration con = new BPlusTreeConfiguration(..).setA().setB()...</pre></code> * * instead of setting each property in a single call like <code><pre> * BPlusTreeConfiguration con = new BPlusTreeConfiguration(..); * con.setA(); * con.setB(); * ... * </pre></code> */ public Creator setCompoundKey(int... compoundKeyIndices) { mManagedType.setCompoundKey(compoundKeyIndices); return this; } /** * Sets the converter container used for the BPlus tree. * * @param converterContainer * @return The current <code>BPlusTreeConfiguration</code> instance including the effect of this * method call. With this it is possible to set the configuration in one single line * like * * <code><pre>BPlusTreeConfiguration con = new BPlusTreeConfiguration(..).setA().setB()...</pre></code> * * instead of setting each property in a single call like <code><pre> * BPlusTreeConfiguration con = new BPlusTreeConfiguration(..); * con.setA(); * con.setB(); * ... * </pre></code> * * @see Container */ public Creator setConverterContainer(Container converterContainer) { mConverterContainer = converterContainer; updateBufferContainer(); return this; } /** * Sets the file container used to manage file output. By default a matching one is generated * automatically when calling {@link #storeAt(String)}. * * @param fileContainer The file container. * * @return The current <code>BPlusTreeConfiguration</code> instance including the effect of this * method call. With this it is possible to set the configuration in one single line * like * * <code><pre>BPlusTreeConfiguration con = new BPlusTreeConfiguration(..).setA().setB()...</pre></code> * * instead of setting each property in a single call like <code><pre> * BPlusTreeConfiguration con = new BPlusTreeConfiguration(..); * con.setA(); * con.setB(); * ... * </pre></code> */ public Creator setFileContainer(Container fileContainer) { mFileContainer = fileContainer; return this; } /** * If you want to store the BPlus tree on a persistent storage instead of the default main * memory usage you have to set a directory path in which the BPlus tree stores it's files. * Please ensure that <code>storeDir</code> is a valid and accessible directory on the file * system. If it is necessary from the perspective of BPlus tree to write down the data there * will be a couple of files generated including a single file which contains the given meta * data information. The file name is taken from the table name given in meta data when calling * the constructor. The file extensions depends on the content and will be managed by the BPlus * tree itself. * * @param storeDir A valid and accessible directory at the hard drive or network storage. * * @return The current <code>BPlusTreeConfiguration</code> instance including the effect of this * method call. With this it is possible to set the configuration in one single line * like * * <code><pre>BPlusTreeConfiguration con = new BPlusTreeConfiguration(..).setA().setB()...</pre></code> * * instead of setting each property in a single call like <code><pre> * BPlusTreeConfiguration con = new BPlusTreeConfiguration(..); * con.setA(); * con.setB(); * ... * </pre></code> * */ public Creator storeAt(String storeDir) { mLocation = Location.LOCATION_FILESYSTEM; if (!new File(storeDir).isDirectory()) throw new IllegalArgumentException( "Given path to store BPlus data to hard drive is not a directory (\"" + storeDir + "\")"); if (storeDir.charAt(storeDir.length() - 1) != '/') storeDir += '/'; try { mFileSystemFilePath = storeDir + mManagedType.getTableName(); } catch (Exception e) { throw new RuntimeException("Unable to set file path (" + e.getMessage() + ")"); } mFileContainer = new BlockFileContainer(mFileSystemFilePath, mBlockSize); return this; } /** * Prints the content of this configuration into a string. It contains * <ul> * <li>The block size</li> * <li>File path where the tree should be stored</li> * <li>A flag which indicates if the tree should be hold in main memory or at hard drive</li> * <li>The content type</li> * <li>The table name</li> * </ul> * If the tree should manage tuples instead of primitive data types there are some additionally * information printed <li>The compound key indices</li> <li>A dump of all column meta data</li> * </ul> */ public String toString() { String dump = "BPlusTreeConfiguration: \n\tBlockSize: " + mBlockSize + "\n\tFilePath: " + mFileSystemFilePath + "\n\tLocation: " + mLocation; dump += "\n\tContent Type: " + mManagedType.getContentType(); dump += "\n\tTable Name: " + mManagedType.getTableName(); if (mManagedType instanceof TupleType) { TupleType tt = (TupleType) mManagedType; dump += "\n\tCompound Key Indices: " + Arrays.toString(tt.getCompoundKeyIndices()); ExtendedResultSetMetaData metaData = tt.getMetaData(); try { for (int i = 0; i < metaData.getColumnCount(); i++) { dump += "\n\t** COLUMN #" + (i + 1); dump += "\n\tCatalog Name: " + metaData.getCatalogName(i + 1); dump += "\n\tClass Name: " + metaData.getColumnClassName(i + 1); dump += "\n\tDisplay Size: " + metaData.getColumnDisplaySize(i + 1); dump += "\n\tColumn Label: " + metaData.getColumnLabel(i + 1); dump += "\n\tColumn Name: " + metaData.getColumnName(i + 1); dump += "\n\tColumn Type: " + metaData.getColumnType(i + 1); try { if (ConvertUtils.toJavaType(ConvertUtils .toRelationalType(metaData.getColumnType(i + 1))) == JavaType.STRING) dump += "\n\tColumn Type Name: " + metaData.getColumnTypeName(i + 1); } catch (Exception e) { e.printStackTrace(); } dump += "\n\tContent Length: " + metaData.getContentLength(i + 1); dump += "\n\tPrecision: " + metaData.getPrecision(i + 1); dump += "\n\tScale: " + metaData.getScale(i + 1); dump += "\n\tSchema: " + metaData.getSchemaName(i + 1); dump += "\n\tTable Name: " + metaData.getTableName(i + 1); dump += "\n\tAuto Increment: " + metaData.isAutoIncrement(i + 1); dump += "\n\tCase Sensitive: " + metaData.isCaseSensitive(i + 1); dump += "\n\tCurrency: " + metaData.isCurrency(i + 1); dump += "\n\tDef Writable: " + metaData.isDefinitelyWritable(i + 1); dump += "\n\tNullable: " + metaData.isNullable(i + 1); dump += "\n\tReadOnly: " + metaData.isReadOnly(i + 1); dump += "\n\tSearchable: " + metaData.isSearchable(i + 1); dump += "\n\tSigned: " + metaData.isSigned(i + 1); dump += "\n\tWritable: " + metaData.isWritable(i + 1); dump += "\n"; } } catch (Exception e) { e.printStackTrace(); } } else if (mManagedType instanceof PrimitiveType) { } else throw new UnsupportedOperationException( "Unknown managed type during dumping detected!"); return dump; } } /** * This class is used to reload a {@link BPlusIndexedSet} (wrapper for {@link BPlusTree}) from a * previous session. <br/> * <br/> * <b>Example</b> This example shows how to reload the indexed set from the previous session which * stores the data of a <i>timestamp</i> managing index set to "<code>myStorage/myTree/</code>". * <code><pre> * BPlusTreeBuilder builder = new BPlusConfiguration.Loader("myStorage/myTree/TimestampTableMeta.json").getBuilder(); * BPlusIndexedSet<Long> myRestoredTree = builder.create(); * </pre></code> * * @author Marcus Pinnecke (pinnecke@mathematik.uni-marburg.de) * */ public static class Loader { /* * The builder member which creates the BPlusTree after setting up the required (restored) * information */ BPlusTreeBuilder mBuilder = null; /** * Reloads the {@link BPlusIndexedSet} from the file. Please ensure that <code>metaFile</code> * points to meta data. This file should be named like the given <b>table name</b> (which was * setup to {@link Creator} the last time) and ended with extension <b>.json</b>. * * @param metaFile The file pointing to the meta data * @throws FileNotFoundException If the file was not found * @throws NoSuchObjectException If the file is not well formed or invalid */ public Loader(File metaFile) throws FileNotFoundException, NoSuchObjectException { mBuilder = loadFromPropertyList(loadFromFile(metaFile), FileUtils.getFilePath(metaFile.getAbsolutePath())); } /** * Reloads the {@link BPlusIndexedSet} from a given {@link PropertyList} instance. Please ensure * that <code>metaInformation</code> contains the required information. * * @param metaInformation A property list containing the meta information * @param storeTreeFilePath Where the tree is (and should be) stored * @throws NoSuchObjectException If the property list is not well formed or invalid */ public Loader(PropertyList metaInformation, String storeTreeFilePath) throws NoSuchObjectException { mBuilder = loadFromPropertyList(metaInformation, storeTreeFilePath); } /** * Reloads the {@link BPlusIndexedSet} from the given path. Please ensure that you give the * <i>full</i> path to the set's meta data. This file should be named like the given <b>table * name</b> (which was setup to {@link Creator} the last time) and ended with extension * <b>.json</b>. * * @param fullMetaFilenamePath The full path to the {@link BPlusIndexedSet} meta file * @throws FileNotFoundException If the file was not found * @throws NoSuchObjectException If the file is not well formed or invalid */ public Loader(String fullMetaFilenamePath) throws FileNotFoundException, NoSuchObjectException { if (fullMetaFilenamePath == null || fullMetaFilenamePath.isEmpty()) throw new IllegalArgumentException( "Given file path variable is empty or null"); File file = new File(fullMetaFilenamePath); mBuilder = loadFromPropertyList(loadFromFile(file), FileUtils.getFilePath(file.getAbsolutePath())); } /** * Returns a builder which allows you to create a {@link BPlusTree}. * * @return A {@link BPlusTreeBuilder} instance */ public BPlusTreeBuilder getBuilder() { return mBuilder; } /* * Parses <i>file</i> by JSONReader into a PropertyList. The returned value should contain the * meta information about the (stored) tree. Actually this logic is done by another method. */ private PropertyList loadFromFile(File file) throws FileNotFoundException { if (file == null) throw new NullPointerException("Load from file: file is null"); if (!file.exists()) throw new FileNotFoundException("File \"" + file.getAbsolutePath() + "\" not found!"); if (!file.isFile() || !file.canRead()) throw new IllegalArgumentException("File \"" + file.getAbsolutePath() + "\" is no file or not readable."); PropertyListReader reader = null; switch (FileUtils.getFileExtension(file.getName()).toLowerCase()) { case JSONReader.FILE_EXTENSION: reader = new JSONReader(); break; default: throw new UnsupportedOperationException( "Unknown file extension for \"" + file.getAbsolutePath() + "\". There is no reader available for this format."); } return reader.read(new FileInputStream(file)); } /* * Restores (stored) tree by creating a builder (with given information), enables the reloading * flag an returns this builder. The builder information is provided by <i>metaInformation</i> */ private BPlusTreeBuilder loadFromPropertyList(PropertyList metaInformation, String storeTreeFilePath) throws NoSuchObjectException { if (metaInformation == null) throw new NullPointerException( "Load from meta information from property list - object is null"); BPlusTreeBuilder builder = BPlusTreeBuilder.unserialize(metaInformation, storeTreeFilePath); return builder.enableReload(); } } /* * The file container used by the BPlus tree */ Container mFileContainer = null; /* * The data type descriptor which contains type specific functions (e.g. StringConverter for a * String type) */ protected ManagedType mManagedType; /** * Returns the measured tuple converter which should be used by the BPlus tree and is needed to * calculate the size for each component of the tuple. * * <br/> * <br/> * <b>Note</b>: Because this only used by the BPlus tree builder the visibility of this method is * restricted to package wide visibility. * * @return The converter * * @see MeasuredTupleConverter */ MeasuredConverter getDataConverter() { return mManagedType.getDataConverter(); } /** * Returns the file container if the BPlus tree is stored on a storage medium or throws an * <code>IllegalArgumentException</code> if the location is the main memory. <br/> * <br/> * <b>Note</b>: Because this only used by the BPlus tree builder the visibility of this method is * restricted to package wide visibility. * */ Container getFileContainer() { if (mLocation.equals(Location.LOCATION_MAIN_MEMORY)) throw new IllegalArgumentException( "There is no file container set because the tree is stored at main memory."); else return mFileContainer; } /** * Returns the tuple key function factory which contains ready to use functions according to the * meta data and the compound key indices. * * <br/> * <br/> * <b>Note</b>: Because this only used by the BPlus tree builder the visibility of this method is * restricted to package wide visibility. * * @return the key function factory */ KeyFunctionFactory getKeyFunctionFactory() { return mManagedType.getKeyFunctionFactory(); } /************************************************************************************************** * * The following class implements the creation of a new and empty BPlusTree * *************************************************************************************************/ /** * Returns the managed type defined for this configuration. The managed type specifies if a simple * data type is managed (e.g. Integer) or a complex type (which is actually a tuple). The managed * type contains further information for the content, see {@link ManagedType}. * * @return The type information for the data managed by the tree */ public ManagedType getManagedType() { return mManagedType; } /************************************************************************************************** * * The following class implements the restoring of a stored BPlusTree * *************************************************************************************************/ /** * Sets the buffered container uses by the BPlusTree. * * @param bufferedContainer The buffered container */ public void setBufferedContainer(BufferedContainer bufferedContainer) { overrideBufferContainer(bufferedContainer); } /** * This package wide visible method enables the reloading of a tree. * * @param systemFilePath The folder where the tree files are stored. * @return this instance of BPlusConfiguration with reload mode enabled. */ BPlusConfiguration setReloadMode(String systemFilePath) { mLocation = Location.LOCATION_FILESYSTEM; mFileSystemFilePath = systemFilePath; return this; } }