/* * 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.ignite.internal.processors.query.h2.database; import java.nio.charset.Charset; import java.nio.charset.StandardCharsets; import java.util.Arrays; import java.util.Comparator; import java.util.List; import org.apache.ignite.internal.pagemem.PageUtils; import org.h2.result.SortOrder; import org.h2.table.IndexColumn; import org.h2.value.Value; import org.h2.value.ValueBoolean; import org.h2.value.ValueByte; import org.h2.value.ValueBytes; import org.h2.value.ValueDate; import org.h2.value.ValueDouble; import org.h2.value.ValueFloat; import org.h2.value.ValueInt; import org.h2.value.ValueLong; import org.h2.value.ValueNull; import org.h2.value.ValueShort; import org.h2.value.ValueString; import org.h2.value.ValueStringFixed; import org.h2.value.ValueStringIgnoreCase; import org.h2.value.ValueTime; import org.h2.value.ValueTimestamp; import org.h2.value.ValueUuid; /** * Helper class for in-page indexes. */ public class InlineIndexHelper { private static final Charset CHARSET = StandardCharsets.UTF_8; /** PageContext for use in IO's */ private static final ThreadLocal<List<InlineIndexHelper>> currentIndex = new ThreadLocal<>(); /** */ public static final List<Integer> AVAILABLE_TYPES = Arrays.asList( Value.BOOLEAN, Value.BYTE, Value.SHORT, Value.INT, Value.LONG, Value.LONG, Value.FLOAT, Value.DOUBLE, Value.DATE, Value.TIME, Value.TIMESTAMP, Value.UUID, Value.STRING, Value.STRING_FIXED, Value.STRING_IGNORECASE, Value.BYTES ); /** */ private final int type; /** */ private final int colIdx; /** */ private final int sortType; /** */ private final short size; /** * @param type Index type (see {@link Value}). * @param colIdx Index column index. * @param sortType Column sort type (see {@link IndexColumn#sortType}). */ public InlineIndexHelper(int type, int colIdx, int sortType) { this.type = type; this.colIdx = colIdx; this.sortType = sortType; switch (type) { case Value.BOOLEAN: case Value.BYTE: this.size = 1; break; case Value.SHORT: this.size = 2; break; case Value.INT: this.size = 4; break; case Value.LONG: this.size = 8; break; case Value.FLOAT: this.size = 4; break; case Value.DOUBLE: this.size = 8; break; case Value.DATE: this.size = 8; break; case Value.TIME: this.size = 8; break; case Value.TIMESTAMP: this.size = 16; break; case Value.UUID: this.size = 16; break; case Value.STRING: case Value.STRING_FIXED: case Value.STRING_IGNORECASE: case Value.BYTES: this.size = -1; break; default: throw new UnsupportedOperationException("no get operation for fast index type " + type); } } /** * @return Index type. */ public int type() { return type; } /** * @return Column index. */ public int columnIndex() { return colIdx; } /** * @return Sort type. */ public int sortType() { return sortType; } /** * @return Page context for current thread. */ public static List<InlineIndexHelper> getCurrentInlineIndexes() { return currentIndex.get(); } /** * Sets page context for current thread. */ public static void setCurrentInlineIndexes(List<InlineIndexHelper> inlineIdxs) { currentIndex.set(inlineIdxs); } /** * Clears current context. */ public static void clearCurrentInlineIndexes() { currentIndex.remove(); } /** * @return Value size. */ public short size() { return size; } /** * @param pageAddr Page address. * @param off Offset. * @return Full size in page. */ public int fullSize(long pageAddr, int off) { int type = PageUtils.getByte(pageAddr, off); if (type == Value.NULL) return 1; if (size > 0) return size + 1; else return PageUtils.getShort(pageAddr, off + 1) + 3; } /** * @param pageAddr Page address. * @param off Offset. * @return Value. */ public Value get(long pageAddr, int off, int maxSize) { if (size > 0 && size + 1 > maxSize) return null; if (maxSize < 1) return null; int type = PageUtils.getByte(pageAddr, off); if (type == Value.UNKNOWN) return null; if (type == Value.NULL) return ValueNull.INSTANCE; if (this.type != type) throw new UnsupportedOperationException("invalid fast index type " + type); switch (this.type) { case Value.BOOLEAN: return ValueBoolean.get(PageUtils.getByte(pageAddr, off + 1) != 0); case Value.BYTE: return ValueByte.get(PageUtils.getByte(pageAddr, off + 1)); case Value.SHORT: return ValueShort.get(PageUtils.getShort(pageAddr, off + 1)); case Value.INT: return ValueInt.get(PageUtils.getInt(pageAddr, off + 1)); case Value.LONG: return ValueLong.get(PageUtils.getLong(pageAddr, off + 1)); case Value.FLOAT: { return ValueFloat.get(Float.intBitsToFloat(PageUtils.getInt(pageAddr, off + 1))); } case Value.DOUBLE: { return ValueDouble.get(Double.longBitsToDouble(PageUtils.getLong(pageAddr, off + 1))); } case Value.TIME: return ValueTime.fromNanos(PageUtils.getLong(pageAddr, off + 1)); case Value.DATE: return ValueDate.fromDateValue(PageUtils.getLong(pageAddr, off + 1)); case Value.TIMESTAMP: return ValueTimestamp.fromDateValueAndNanos(PageUtils.getLong(pageAddr, off + 1), PageUtils.getLong(pageAddr, off + 9)); case Value.UUID: return ValueUuid.get(PageUtils.getLong(pageAddr, off + 1), PageUtils.getLong(pageAddr, off + 9)); case Value.STRING: return ValueString.get(new String(readBytes(pageAddr, off), CHARSET)); case Value.STRING_FIXED: return ValueStringFixed.get(new String(readBytes(pageAddr, off), CHARSET)); case Value.STRING_IGNORECASE: return ValueStringIgnoreCase.get(new String(readBytes(pageAddr, off), CHARSET)); case Value.BYTES: return ValueBytes.get(readBytes(pageAddr, off)); default: throw new UnsupportedOperationException("no get operation for fast index type " + type); } } /** Read variable length bytearray */ private static byte[] readBytes(long pageAddr, int off) { int size = PageUtils.getShort(pageAddr, off + 1) & 0x7FFF; return PageUtils.getBytes(pageAddr, off + 3, size); } /** * @param pageAddr Page address. * @param off Offset. * @return {@code True} if string is not truncated on save. */ protected boolean isValueFull(long pageAddr, int off) { switch (type) { case Value.BOOLEAN: case Value.BYTE: case Value.INT: case Value.SHORT: case Value.LONG: return true; case Value.STRING: case Value.STRING_FIXED: case Value.STRING_IGNORECASE: case Value.BYTES: return (PageUtils.getShort(pageAddr, off + 1) & 0x8000) == 0; default: throw new UnsupportedOperationException("no get operation for fast index type " + type); } } /** * @param pageAddr Page address. * @param off Offset. * @param maxSize Maximum size to read. * @param v Value to compare. * @param comp Comparator. * @return Compare result (-2 means we can't compare). */ public int compare(long pageAddr, int off, int maxSize, Value v, Comparator<Value> comp) { Value v1 = get(pageAddr, off, maxSize); if (v1 == null) return -2; int c = comp.compare(v1, v); c = c != 0 ? c > 0 ? 1 : -1 : 0; if (size > 0) return fixSort(c, sortType()); if (isValueFull(pageAddr, off) || canRelyOnCompare(c, v1, v)) return fixSort(c, sortType()); return -2; } /** * @param pageAddr Page address. * @param off Offset. * @param val Value. * @return NUmber of bytes saved. */ public int put(long pageAddr, int off, Value val, int maxSize) { if (size > 0 && size + 1 > maxSize) return 0; if (size < 0 && maxSize < 4) { // can't fit vartype field PageUtils.putByte(pageAddr, off, (byte)Value.UNKNOWN); return 0; } if (val.getType() == Value.NULL) { PageUtils.putByte(pageAddr, off, (byte)Value.NULL); return 1; } if (val.getType() != type) throw new UnsupportedOperationException("value type doesn't match"); switch (type) { case Value.BOOLEAN: PageUtils.putByte(pageAddr, off, (byte)val.getType()); PageUtils.putByte(pageAddr, off + 1, (byte)(val.getBoolean() ? 1 : 0)); return size + 1; case Value.BYTE: PageUtils.putByte(pageAddr, off, (byte)val.getType()); PageUtils.putByte(pageAddr, off + 1, val.getByte()); return size + 1; case Value.SHORT: PageUtils.putByte(pageAddr, off, (byte)val.getType()); PageUtils.putShort(pageAddr, off + 1, val.getShort()); return size + 1; case Value.INT: PageUtils.putByte(pageAddr, off, (byte)val.getType()); PageUtils.putInt(pageAddr, off + 1, val.getInt()); return size + 1; case Value.LONG: PageUtils.putByte(pageAddr, off, (byte)val.getType()); PageUtils.putLong(pageAddr, off + 1, val.getLong()); return size + 1; case Value.FLOAT: { PageUtils.putByte(pageAddr, off, (byte)val.getType()); PageUtils.putInt(pageAddr, off + 1, Float.floatToIntBits(val.getFloat())); return size + 1; } case Value.DOUBLE: { PageUtils.putByte(pageAddr, off, (byte)val.getType()); PageUtils.putLong(pageAddr, off + 1, Double.doubleToLongBits(val.getDouble())); return size + 1; } case Value.TIME: PageUtils.putByte(pageAddr, off, (byte)val.getType()); PageUtils.putLong(pageAddr, off + 1, ((ValueTime)val).getNanos()); return size + 1; case Value.DATE: PageUtils.putByte(pageAddr, off, (byte)val.getType()); PageUtils.putLong(pageAddr, off + 1, ((ValueDate)val).getDateValue()); return size + 1; case Value.TIMESTAMP: PageUtils.putByte(pageAddr, off, (byte)val.getType()); PageUtils.putLong(pageAddr, off + 1, ((ValueTimestamp)val).getDateValue()); PageUtils.putLong(pageAddr, off + 9, ((ValueTimestamp)val).getTimeNanos()); return size + 1; case Value.UUID: PageUtils.putByte(pageAddr, off, (byte)val.getType()); PageUtils.putLong(pageAddr, off + 1, ((ValueUuid)val).getHigh()); PageUtils.putLong(pageAddr, off + 9, ((ValueUuid)val).getLow()); return size + 1; case Value.STRING: case Value.STRING_FIXED: case Value.STRING_IGNORECASE: { short size; byte[] s = val.getString().getBytes(CHARSET); if (s.length + 3 <= maxSize) size = (short)s.length; else { s = trimUTF8(s, maxSize - 3); size = (short)(s.length | 0x8000); } if (s == null) { // Can't fit anything to PageUtils.putByte(pageAddr, off, (byte)Value.UNKNOWN); return 0; } else { PageUtils.putByte(pageAddr, off, (byte)val.getType()); PageUtils.putShort(pageAddr, off + 1, size); PageUtils.putBytes(pageAddr, off + 3, s); return s.length + 3; } } case Value.BYTES: { byte[] s; short size; PageUtils.putByte(pageAddr, off, (byte)val.getType()); if (val.getBytes().length + 3 <= maxSize) { size = (short)val.getBytes().length; PageUtils.putShort(pageAddr, off + 1, size); PageUtils.putBytes(pageAddr, off + 3, val.getBytes()); } else { size = (short)((maxSize - 3) | 0x8000); PageUtils.putShort(pageAddr, off + 1, size); PageUtils.putBytes(pageAddr, off + 3, Arrays.copyOfRange(val.getBytes(), 0, maxSize - 3)); } return size + 3; } default: throw new UnsupportedOperationException("no get operation for fast index type " + type); } } /** * Convert String to byte[] with size limit, according to UTF-8 encoding. * * @param bytes byte[]. * @param limit Size limit. * @return byte[]. */ public static byte[] trimUTF8(byte[] bytes, int limit) { if (bytes.length <= limit) return bytes; for (int i = limit; i > 0; i--) { if ((bytes[i] & 0xc0) != 0x80) { byte[] res = new byte[i]; System.arraycopy(bytes, 0, res, 0, i); return res; } } return null; } /** * @param c Compare result. * @param shortVal Short value. * @param v2 Second value; * @return {@code true} if we can rely on compare result. */ protected boolean canRelyOnCompare(int c, Value shortVal, Value v2) { switch (type) { case Value.STRING: case Value.STRING_FIXED: case Value.STRING_IGNORECASE: case Value.BYTES: if (shortVal.getType() == Value.NULL || v2.getType() == Value.NULL) return true; if (c == 0 && shortVal.getType() != Value.NULL && v2.getType() != Value.NULL) return false; int l1; int l2; if (type == Value.BYTES) { l1 = shortVal.getBytes().length; l2 = v2.getBytes().length; } else { l1 = shortVal.getString().length(); l2 = v2.getString().length(); } if (c < 0 && l1 <= l2) { // Can't rely on compare, should use full value. return false; } return true; default: return true; } } /** * Perform sort order correction. * * @param c Compare result. * @param sortType Sort type. * @return Fixed compare result. */ public static int fixSort(int c, int sortType) { return sortType == SortOrder.ASCENDING ? c : -c; } }