/** * Copyright (C) 2014-2016 LinkedIn Corp. (pinot-core@linkedin.com) * * Licensed 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 com.linkedin.pinot.core.realtime.impl.dictionary; import com.linkedin.pinot.core.segment.index.readers.Dictionary; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import javax.annotation.Nonnull; /** * The class <code>BaseOnHeapMutableDictionary</code> is the implementation of the mutable dictionary required by * REALTIME consuming segments. * <p>The implementation needs to be thread safe for single writer multiple readers scenario. * <p>We can assume the readers always first get the dictionary id for a value, then use the dictionary id to fetch the * value later, but not reversely. So whenever we return a valid dictionary id for a value, we need to ensure the value * can be fetched by the dictionary id returned. */ public abstract class BaseOnHeapMutableDictionary implements Dictionary { private static final int SHIFT_OFFSET = 13; // INITIAL_DICTIONARY_SIZE = 8192 private static final int INITIAL_DICTIONARY_SIZE = 1 << SHIFT_OFFSET; private static final int MASK = 0xFFFFFFFF >>> (Integer.SIZE - SHIFT_OFFSET); private final Map<Object, Integer> _valueToDictId = new ConcurrentHashMap<>(INITIAL_DICTIONARY_SIZE); private final Object[][] _dictIdToValue = new Object[INITIAL_DICTIONARY_SIZE][]; private int _entriesIndexed = 0; /** * For performance, we don't validate the dictId passed in. It should be returned by index() or indexOf(). */ @Nonnull @Override public Object get(int dictId) { return _dictIdToValue[dictId >>> SHIFT_OFFSET][dictId & MASK]; } @Override public String getStringValue(int dictId) { return get(dictId).toString(); } @Override public int length() { return _entriesIndexed; } @Override public void readIntValues(int[] dictIds, int startPos, int limit, int[] outValues, int outStartPos) { int endPos = startPos + limit; for (int i = startPos; i < endPos; i++) { outValues[outStartPos++] = getIntValue(dictIds[i]); } } @Override public void readLongValues(int[] dictIds, int startPos, int limit, long[] outValues, int outStartPos) { int endPos = startPos + limit; for (int i = startPos; i < endPos; i++) { outValues[outStartPos++] = getLongValue(dictIds[i]); } } @Override public void readFloatValues(int[] dictIds, int startPos, int limit, float[] outValues, int outStartPos) { int endPos = startPos + limit; for (int i = startPos; i < endPos; i++) { outValues[outStartPos++] = getFloatValue(dictIds[i]); } } @Override public void readDoubleValues(int[] dictIds, int startPos, int limit, double[] outValues, int outStartPos) { int endPos = startPos + limit; for (int i = startPos; i < endPos; i++) { outValues[outStartPos++] = getDoubleValue(dictIds[i]); } } @Override public void readStringValues(int[] dictIds, int startPos, int limit, String[] outValues, int outStartPos) { int endPos = startPos + limit; for (int i = startPos; i < endPos; i++) { outValues[outStartPos++] = getStringValue(dictIds[i]); } } public boolean isEmpty() { return _entriesIndexed == 0; } public abstract void index(@Nonnull Object rawValue); public abstract boolean inRange(@Nonnull String lower, @Nonnull String upper, int dictIdToCompare, boolean includeLower, boolean includeUpper); @Nonnull public abstract Object getMinVal(); @Nonnull public abstract Object getMaxVal(); @Nonnull public abstract Object getSortedValues(); /** * Index a single value. * <p>This method will only be called by a single writer thread. * * @param value single value already converted to correct type. */ protected void indexValue(@Nonnull Object value) { if (!_valueToDictId.containsKey(value)) { int arrayIndex = _entriesIndexed >>> SHIFT_OFFSET; int arrayOffset = _entriesIndexed & MASK; // Create a new array if necessary if (arrayOffset == 0) { _dictIdToValue[arrayIndex] = new Object[INITIAL_DICTIONARY_SIZE]; } // First update dictId to value map then value to dictId map // Ensure we can always fetch value by dictId returned by index() or indexOf() _dictIdToValue[arrayIndex][arrayOffset] = value; _valueToDictId.put(value, _entriesIndexed); _entriesIndexed++; } } /** * Get the dictId of a single value. * <p>This method will only be called by a single writer thread. * * @param value single value already converted to correct type. * @return dictId of the value. */ protected int getDictId(@Nonnull Object value) { Integer dictId = _valueToDictId.get(value); if (dictId == null) { return NULL_VALUE_INDEX; } else { return dictId; } } }