/* Copyright (C) SYSTAP, LLC DBA Blazegraph 2006-2016. All rights reserved. Contact: SYSTAP, LLC DBA Blazegraph 2501 Calvert ST NW #106 Washington, DC 20008 licenses@blazegraph.com This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; version 2 of the License. This program 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 General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ package com.bigdata.sparse; import java.io.Externalizable; import java.io.IOException; import java.io.ObjectInput; import java.io.ObjectOutput; import java.io.UnsupportedEncodingException; import java.util.Date; import com.bigdata.btree.keys.IKeyBuilder; import com.bigdata.btree.keys.KeyBuilder; import com.bigdata.btree.keys.SuccessorUtil; /** * A schema for a sparse row store. Note that more than one schema may be used * with the same index. The name of the schema is always encoded as the first * component of the key. * * @todo support optional strong typing for column values? * @todo support optional required columns? */ public class Schema implements Externalizable { /** * */ private static final long serialVersionUID = -7619134292028385179L; private String name; private String primaryKey; private KeyType primaryKeyType; private transient byte[] schemaBytes; /** * De-serialization ctor. */ public Schema() { } /** * * @param name * The schema name. * @param primaryKey * The name of the column whose value is the (application * defined) primary key. * @param primaryKeyType * The data type for the primary key. */ public Schema(final String name, final String primaryKey, final KeyType primaryKeyType) { NameChecker.assertSchemaName(name); NameChecker.assertColumnName(primaryKey); if (primaryKeyType == null) throw new IllegalArgumentException(); this.name = name; this.primaryKey = primaryKey; this.primaryKeyType = primaryKeyType; // eager computation of the encoded scheme name. getSchemaBytes(); } /** * The name of the schema. */ public String getName() { return name; } /** * The name of the column whose value is the primary key. */ public String getPrimaryKeyName() { return primaryKey; } /** * The data type that is used for the primary key when forming the total * key. */ public KeyType getPrimaryKeyType() { return primaryKeyType; } /** * The Unicode sort key encoding of the schema name. */ final protected byte[] getSchemaBytes() { if (schemaBytes == null) { if (SparseRowStore.schemaNameUnicodeClean) { /* * One time encoding of the schema name as UTF8. */ try { schemaBytes = name.getBytes(SparseRowStore.UTF8); } catch (UnsupportedEncodingException e) { throw new RuntimeException(e); } } else { /* * One time encoding of the schema name as a Unicode sort key. */ schemaBytes = asSortKey(name); } } return schemaBytes; } // /** // * The length of the value that will be returned by {@link #getSchemaBytes()} // */ // final protected int getSchemaBytesLength() { // // return getSchemaBytes().length; // // } /* * Key builder stuff. */ /** * Helper method appends a typed value to the compound key (this is used to * get the primary key into the compound key). * <p> * Note: This automatically appends the <code>nul</code> terminator byte * if {@link KeyType#isFixedLength()} is <code>false</code>. That byte * flags the end of the primary key for variable length primary keys. * * @param keyType * The target data type. * @param v * The value. * * @return The {@link #keyBuilder}. * * @see KeyDecoder */ final protected IKeyBuilder appendPrimaryKey(final IKeyBuilder keyBuilder, final Object v, final boolean successor) { final KeyType keyType = getPrimaryKeyType(); if (successor) { switch (keyType) { case Integer: return keyBuilder.append(successor(keyBuilder,((Number) v).intValue())); case Long: return keyBuilder.append(successor(keyBuilder,((Number) v).longValue())); case Float: return keyBuilder.append(successor(keyBuilder,((Number) v).floatValue())); case Double: return keyBuilder.append(successor(keyBuilder, ((Number) v) .doubleValue())); case Unicode: { final String tmp = v.toString(); if (SparseRowStore.primaryKeyUnicodeClean) { try { keyBuilder.append( SuccessorUtil.successor(tmp .getBytes(SparseRowStore.UTF8))) .appendNul(); } catch (UnsupportedEncodingException e) { throw new RuntimeException(e); } } else { // primary key in backwards compatibility mode. keyBuilder .appendText(tmp, true/* unicode */, true/* successor */) .appendNul(); } return keyBuilder; } case ASCII: return keyBuilder.appendText(v.toString(), false/*unicode*/, true/*successor*/).appendNul(); case Date: return keyBuilder.append(successor(keyBuilder,((Date) v).getTime())); // case UnsignedBytes: // return keyBuilder.append((byte[])v).appendNul(); default: throw new UnsupportedOperationException(); } } else { switch (keyType) { case Integer: return keyBuilder.append(((Number) v).intValue()); case Long: return keyBuilder.append(((Number) v).longValue()); case Float: return keyBuilder.append(((Number) v).floatValue()); case Double: return keyBuilder.append(((Number) v).doubleValue()); case Unicode: { final String tmp = v.toString(); if (SparseRowStore.primaryKeyUnicodeClean) { try { keyBuilder.append(tmp.getBytes(SparseRowStore.UTF8)) .appendNul(); } catch (UnsupportedEncodingException e) { throw new RuntimeException(e); } } else { // primary key in backwards compatibility mode. keyBuilder.appendText(v.toString(), true/* unicode */, false/* successor */).appendNul(); } return keyBuilder; } case ASCII: return keyBuilder.appendText(v.toString(),false/*unicode*/,false/*successor*/).appendNul(); case Date: return keyBuilder.append(((Date) v).getTime()); // case UnsignedBytes: // return keyBuilder.append((byte[]) v); default: throw new UnsupportedOperationException(); } } // return keyBuilder; } /** * Return the successor of a primary key object. * * @param v * The object. * * @return The successor. * * @throws UnsupportedOperationException * if the primary key type is {@link KeyType#Unicode}. See * {@link #toKey(Object)}, which correctly forms the successor * key in all cases. */ final private Object successor(final IKeyBuilder keyBuilder, final Object v) { final KeyType keyType = getPrimaryKeyType(); switch(keyType) { case Integer: return SuccessorUtil.successor(((Number)v).intValue()); case Long: return SuccessorUtil.successor(((Number)v).longValue()); case Float: return SuccessorUtil.successor(((Number)v).floatValue()); case Double: return SuccessorUtil.successor(((Number)v).doubleValue()); case Unicode: case ASCII: // case UnsignedBytes: /* * Note: See toKey() for how to correctly form the sort key for the * successor of a Unicode value. */ throw new UnsupportedOperationException(); // return SuccessorUtil.successor(v.toString()); // return SuccessorUtil.successor(v.toString()); case Date: return SuccessorUtil.successor(((Date) v).getTime()); default: throw new UnsupportedOperationException(); } // // return keyBuilder; } /** * Forms the key in {@link #keyBuilder} that should be used as the first key * (inclusive) for a range query that will visit all index entries for the * specified primary key. * * @param primaryKey * The primary key. * * @return The {@link #keyBuilder}, which will have the schema and the * primary key already formatted in its buffer. * * @see KeyDecoder */ final protected IKeyBuilder fromKey(final IKeyBuilder keyBuilder, final Object primaryKey) { keyBuilder.reset(); // append the (encoded) schema name. keyBuilder.append(getSchemaBytes()); keyBuilder.appendSigned(primaryKeyType.getByteCode()); keyBuilder.appendNul(); if (primaryKey != null) { // append the (encoded) primary key. appendPrimaryKey(keyBuilder, primaryKey, false/* successor */); } return keyBuilder; } /** * The prefix that identifies all tuples in the logical row for this schema * having the indicated value for their primary key. * * @param primaryKey * The value of the primary key for the logical row. * * @return */ final public byte[] getPrefix(final IKeyBuilder keyBuilder, final Object primaryKey) { return fromKey(keyBuilder, primaryKey).getKey(); } /* * Note: use SuccessorUtil.successor(key.clone()) instead. */ // /** // * Forms the key in {@link #keyBuilder} that should be used as the last key // * (exclusive) for a range query that will visit all index entries for the // * specified primary key. // * // * @param primaryKey // * The primary key. // * // * @return The {@link #keyBuilder}, which will have the schema and the // * successor of the primary key already formatted in its buffer. // * // * @see KeyDecoder // * // * @throws IllegalArgumentException // * if either argument is <code>null</code>. // */ // final protected IKeyBuilder toKey(IKeyBuilder keyBuilder, Object primaryKey) { // // if (primaryKey == null) // throw new IllegalArgumentException(); // // keyBuilder.reset(); // // // append the (encoded) schema name. // keyBuilder.append(getSchemaBytes()); // keyBuilder.append(primaryKeyType.getByteCode()); // keyBuilder.appendNul(); // // // append successor of the (encoded) primary key. // appendPrimaryKey(keyBuilder, primaryKey, true/* successor */); // // return keyBuilder; // // } /** * Encodes a key for the {@link Schema}. * * @param keyBuilder * @param primaryKey * The primary key for the logical row (required). * @param col * The column name (required). * @param timestamp * The timestamp (required). * * @return The encoded key. * * @throws IllegalArgumentException * if <i>keyBuilder</i> is <code>null</code>. * @throws IllegalArgumentException * if <i>primaryKey</i> is <code>null</code>. * @throws IllegalArgumentException * if <i>col</i> is not valid as the name of a column. */ public byte[] getKey(final IKeyBuilder keyBuilder, final Object primaryKey, final String col, final long timestamp) { if (keyBuilder == null) throw new IllegalArgumentException(); if (primaryKey == null) throw new IllegalArgumentException(); NameChecker.assertColumnName(col); // encode the schema name and the primary key. fromKey(keyBuilder, primaryKey); /* * The column name. Note that the column name is NOT stored with Unicode * compression so that we can decode it without loss. */ try { keyBuilder.append(col.getBytes(SparseRowStore.UTF8)).appendNul(); } catch(UnsupportedEncodingException ex) { throw new RuntimeException(ex); } keyBuilder.append(timestamp); final byte[] key = keyBuilder.getKey(); return key; } private final static transient short VERSION0 = 0x0; public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException { final short version = in.readShort(); if (version != VERSION0) throw new IOException("Unknown version=" + version); name = in.readUTF(); primaryKey = in.readUTF(); primaryKeyType = KeyType.getKeyType(in.readByte()); // eager computation of the encoded scheme name. getSchemaBytes(); } public void writeExternal(ObjectOutput out) throws IOException { out.writeShort(VERSION0); out.writeUTF(name); out.writeUTF(primaryKey); out.writeByte(primaryKeyType.getByteCode()); } public String toString() { return "Schema{name=" + getName() + ",primaryKeyName=" + getPrimaryKeyName() + ",primaryKeyType=" + getPrimaryKeyType() + "}"; } /** * Used for historical compatibility to unbox an application key (convert it * to an unsigned byte[]). */ static private final IKeyBuilder _keyBuilder = KeyBuilder.newUnicodeInstance(); /** * Utility method for historical compatibility converts an application key * to a sort key (an unsigned byte[] that imposes the same sort order). * <p> * Note: This method is thread-safe. * * @param val * An application key. * * @return The unsigned byte[] equivalent of that key. This will be * <code>null</code> iff the <i>key</i> is <code>null</code>. If the * <i>key</i> is a byte[], then the byte[] itself will be returned. */ private static final byte[] asSortKey(final Object val) { if (val == null) { return null; } if (val instanceof byte[]) { return (byte[]) val; } /* * Synchronize on the keyBuilder to avoid concurrent modification of its * state. */ synchronized (_keyBuilder) { return _keyBuilder.getSortKey(val); } } }