/*
* 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.opt;
import java.lang.ref.WeakReference;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.util.concurrent.TimeUnit;
import org.apache.ignite.IgniteCheckedException;
import org.apache.ignite.IgniteException;
import org.apache.ignite.IgniteInterruptedException;
import org.apache.ignite.internal.processors.cache.version.GridCacheVersion;
import org.apache.ignite.internal.processors.query.GridQueryTypeDescriptor;
import org.apache.ignite.internal.util.typedef.internal.SB;
import org.apache.ignite.internal.util.typedef.internal.U;
import org.h2.message.DbException;
import org.h2.result.Row;
import org.h2.result.SearchRow;
import org.h2.value.CompareMode;
import org.h2.value.Value;
import org.h2.value.ValueNull;
import org.jetbrains.annotations.Nullable;
/**
* Table row implementation based on {@link GridQueryTypeDescriptor}.
*/
public abstract class GridH2AbstractKeyValueRow extends GridH2Row {
/** */
public static final int DEFAULT_COLUMNS_COUNT = 3;
/** Key column. */
public static final int KEY_COL = 0;
/** Value column. */
public static final int VAL_COL = 1;
/** Version column. */
public static final int VER_COL = 2;
/** */
protected final GridH2RowDescriptor desc;
/** */
@SuppressWarnings("FieldAccessedSynchronizedAndUnsynchronized")
protected long expirationTime;
/** */
private Value key;
/** */
private volatile Value val;
/** */
private Value[] valCache;
/** */
private Value version;
/**
* Constructor.
*
* @param desc Row descriptor.
* @param key Key.
* @param keyType Key type.
* @param val Value.
* @param valType Value type.
* @param expirationTime Expiration time.
* @throws IgniteCheckedException If failed.
*/
protected GridH2AbstractKeyValueRow(GridH2RowDescriptor desc, Object key, int keyType, @Nullable Object val,
int valType, GridCacheVersion ver, long expirationTime) throws IgniteCheckedException {
this.desc = desc;
this.expirationTime = expirationTime;
setValue(KEY_COL, desc.wrap(key, keyType));
if (val != null) // We remove by key only, so value can be null here.
setValue(VAL_COL, desc.wrap(val, valType));
if (ver != null)
setValue(VER_COL, desc.wrap(ver, Value.JAVA_OBJECT));
}
/** {@inheritDoc} */
@Override public Value[] getValueList() {
throw new UnsupportedOperationException();
}
/**
* Protected constructor for {@link GridH2KeyValueRowOffheap}
*
* @param desc Row descriptor.
*/
protected GridH2AbstractKeyValueRow(GridH2RowDescriptor desc) {
this.desc = desc;
}
/** {@inheritDoc} */
@Override public long expireTime() {
return expirationTime;
}
/** {@inheritDoc} */
@Override public int getColumnCount() {
return DEFAULT_COLUMNS_COUNT + desc.fieldsCount();
}
/**
* Atomically updates weak value.
*
* @param valObj New value.
* @return New value if old value is empty, old value otherwise.
* @throws IgniteCheckedException If failed.
*/
protected synchronized Value updateWeakValue(Object valObj) throws IgniteCheckedException {
Value res = peekValue(VAL_COL);
if (res != null && !(res instanceof WeakValue))
return res;
Value upd = desc.wrap(valObj, desc.valueType());
setValue(VAL_COL, new WeakValue(upd));
notifyAll();
return upd;
}
/**
* @param waitTime Time to await for value unswap.
* @return Synchronized value.
*/
protected synchronized Value syncValue(long waitTime) {
Value v = peekValue(VAL_COL);
while (v == null && waitTime > 0) {
long start = System.nanoTime(); // This call must be quite rare, so performance is not a concern.
try {
wait(waitTime); // Wait for value arrival to allow other threads to make a progress.
}
catch (InterruptedException e) {
throw new IgniteInterruptedException(e);
}
long t = System.nanoTime() - start;
if (t > 0)
waitTime -= TimeUnit.NANOSECONDS.toMillis(t);
v = peekValue(VAL_COL);
}
return v;
}
/**
* @param col Column index.
* @return Value if exists.
*/
protected final Value peekValue(int col) {
if (col == KEY_COL)
return key;
if (col == VAL_COL)
return val;
assert col == VER_COL;
return version;
}
/** {@inheritDoc} */
@Override public Value getValue(int col) {
Value[] vCache = valCache;
if (vCache != null) {
Value v = vCache[col];
if (v != null)
return v;
}
Value v;
if (desc.isValueColumn(col)) {
v = peekValue(VAL_COL);
assert !(v instanceof WeakValue) : v;
return v;
}
else if (desc.isKeyColumn(col)) {
v = peekValue(KEY_COL);
if (v == null) {
v = getOffheapValue(KEY_COL);
assert v != null;
setValue(KEY_COL, v);
if (peekValue(VAL_COL) == null)
cache();
}
assert !(v instanceof WeakValue) : v;
return v;
}
else if (col == VER_COL)
return version;
col -= DEFAULT_COLUMNS_COUNT;
assert col >= 0;
Value key = getValue(KEY_COL);
Value val = getValue(VAL_COL);
assert key != null;
assert val != null;
Object res = desc.columnValue(key.getObject(), val.getObject(), col);
if (res == null)
v = ValueNull.INSTANCE;
else {
try {
v = desc.wrap(res, desc.fieldType(col));
}
catch (IgniteCheckedException e) {
throw DbException.convert(e);
}
}
if (vCache != null)
vCache[col + DEFAULT_COLUMNS_COUNT] = v;
return v;
}
/**
* @param valCache Value cache.
*/
public void valuesCache(Value[] valCache) {
if (valCache != null) {
desc.initValueCache(valCache, key, val, version);
}
this.valCache = valCache;
}
/**
* Caches this row for reuse.
*/
protected abstract void cache();
/**
* @param col Column.
* @return Value read from offheap memory or null if it is impossible.
*/
protected abstract Value getOffheapValue(int col);
/**
* Adds offheap row ID.
*/
protected void addOffheapRowId(SB sb) {
// No-op.
}
/** {@inheritDoc} */
@Override public String toString() {
SB sb = new SB("Row@");
sb.a(Integer.toHexString(System.identityHashCode(this)));
addOffheapRowId(sb);
Value v = peekValue(KEY_COL);
sb.a("[ key: ").a(v == null ? "nil" : v.getString());
v = WeakValue.unwrap(peekValue(VAL_COL));
sb.a(", val: ").a(v == null ? "nil" : v.getString());
v = peekValue(VER_COL);
sb.a(", ver: ").a(v == null ? "nil" : v.getString());
sb.a(" ][ ");
if (v != null) {
for (int i = DEFAULT_COLUMNS_COUNT, cnt = getColumnCount(); i < cnt; i++) {
v = getValue(i);
if (i != DEFAULT_COLUMNS_COUNT)
sb.a(", ");
if (!desc.isKeyValueOrVersionColumn(i))
sb.a(v == null ? "nil" : v.getString());
}
}
sb.a(" ]");
return sb.toString();
}
/** {@inheritDoc} */
@Override public void setKeyAndVersion(SearchRow old) {
throw new IllegalStateException();
}
/** {@inheritDoc} */
@Override public void setKey(long key) {
throw new IllegalStateException();
}
/** {@inheritDoc} */
@Override public Row getCopy() {
throw new IllegalStateException();
}
/** {@inheritDoc} */
@Override public void setDeleted(boolean deleted) {
throw new IllegalStateException();
}
/** {@inheritDoc} */
@Override public long getKey() {
throw new IllegalStateException();
}
/** {@inheritDoc} */
@Override public void setSessionId(int sesId) {
throw new IllegalStateException();
}
/** {@inheritDoc} */
@Override public void setVersion(int ver) {
throw new IllegalStateException();
}
/**
* Weak reference to value that was swapped but accessed in indexing SPI.
*/
private static class WeakValue extends Value {
/**
* Unwraps value.
*
* @param v Value.
* @return Unwrapped value.
*/
static Value unwrap(Value v) {
return (v instanceof WeakValue) ? ((WeakValue)v).get() : v;
}
/** */
private final WeakReference<Value> ref;
/**
* @param v Value.
*/
private WeakValue(Value v) {
ref = new WeakReference<>(v);
}
/**
* @return Referenced value.
*/
public Value get() {
return ref.get();
}
/** {@inheritDoc} */
@Override public String getSQL() {
throw new IllegalStateException();
}
/** {@inheritDoc} */
@Override public int getType() {
throw new IllegalStateException();
}
/** {@inheritDoc} */
@Override public long getPrecision() {
throw new IllegalStateException();
}
/** {@inheritDoc} */
@Override public int getDisplaySize() {
throw new IllegalStateException();
}
/** {@inheritDoc} */
@Override public String getString() {
throw new IllegalStateException();
}
/** {@inheritDoc} */
@Override public Object getObject() {
throw new IllegalStateException();
}
/** {@inheritDoc} */
@Override public void set(PreparedStatement preparedStatement, int i) throws SQLException {
throw new IllegalStateException();
}
/** {@inheritDoc} */
@Override protected int compareSecure(Value val, CompareMode compareMode) {
throw new IllegalStateException();
}
/** {@inheritDoc} */
@Override public int hashCode() {
throw new IllegalStateException();
}
/** {@inheritDoc} */
@Override public boolean equals(Object o) {
throw new IllegalStateException();
}
}
/** {@inheritDoc} */
@Override public void setValue(int idx, Value v) {
if (desc.isValueColumn(idx))
val = v;
else if (idx == VER_COL)
version = v;
else {
assert desc.isKeyColumn(idx) : idx + " " + v;
key = v;
}
}
/** {@inheritDoc} */
@Override public final int hashCode() {
throw new IllegalStateException();
}
}