blob: 356d46c960f22ffcff569f622384f4f77d8dd76e [file] [log] [blame]
/*
* 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;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.sql.Connection;
import java.sql.Date;
import java.sql.ParameterMetaData;
import java.sql.PreparedStatement;
import java.sql.ResultSetMetaData;
import java.sql.SQLException;
import java.sql.Time;
import java.sql.Timestamp;
import java.sql.Types;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.UUID;
import javax.cache.CacheException;
import org.apache.ignite.IgniteCheckedException;
import org.apache.ignite.IgniteException;
import org.apache.ignite.internal.GridKernalContext;
import org.apache.ignite.internal.processors.cache.CacheObject;
import org.apache.ignite.internal.processors.cache.CacheObjectValueContext;
import org.apache.ignite.internal.processors.cache.GridCacheContext;
import org.apache.ignite.internal.processors.cache.GridCacheSharedContext;
import org.apache.ignite.internal.processors.cache.QueryCursorImpl;
import org.apache.ignite.internal.processors.cache.mvcc.MvccUtils;
import org.apache.ignite.internal.processors.cache.query.IgniteQueryErrorCode;
import org.apache.ignite.internal.processors.cache.query.QueryTable;
import org.apache.ignite.internal.processors.odbc.jdbc.JdbcParameterMeta;
import org.apache.ignite.internal.processors.query.GridQueryFieldMetadata;
import org.apache.ignite.internal.processors.query.GridQueryProperty;
import org.apache.ignite.internal.processors.query.GridQueryTypeDescriptor;
import org.apache.ignite.internal.processors.query.IgniteSQLException;
import org.apache.ignite.internal.processors.query.QueryUtils;
import org.apache.ignite.internal.processors.query.h2.opt.GridH2IndexBase;
import org.apache.ignite.internal.processors.query.h2.opt.GridH2RetryException;
import org.apache.ignite.internal.processors.query.h2.opt.GridH2RowDescriptor;
import org.apache.ignite.internal.processors.query.h2.opt.GridH2Table;
import org.apache.ignite.internal.processors.query.h2.opt.GridH2ValueCacheObject;
import org.apache.ignite.internal.processors.query.h2.opt.QueryContext;
import org.apache.ignite.internal.processors.query.h2.twostep.msg.GridH2RowMessage;
import org.apache.ignite.internal.processors.query.h2.twostep.msg.GridH2ValueMessage;
import org.apache.ignite.internal.processors.query.h2.twostep.msg.GridH2ValueMessageFactory;
import org.apache.ignite.internal.util.GridStringBuilder;
import org.apache.ignite.internal.util.typedef.F;
import org.apache.ignite.internal.util.typedef.internal.SB;
import org.apache.ignite.internal.util.typedef.internal.U;
import org.h2.engine.Session;
import org.h2.jdbc.JdbcConnection;
import org.h2.result.Row;
import org.h2.result.SortOrder;
import org.h2.table.Column;
import org.h2.table.IndexColumn;
import org.h2.util.LocalDateTimeUtils;
import org.h2.value.CompareMode;
import org.h2.value.DataType;
import org.h2.value.Value;
import org.h2.value.ValueArray;
import org.h2.value.ValueBoolean;
import org.h2.value.ValueByte;
import org.h2.value.ValueBytes;
import org.h2.value.ValueDate;
import org.h2.value.ValueDecimal;
import org.h2.value.ValueDouble;
import org.h2.value.ValueFloat;
import org.h2.value.ValueGeometry;
import org.h2.value.ValueInt;
import org.h2.value.ValueJavaObject;
import org.h2.value.ValueLong;
import org.h2.value.ValueNull;
import org.h2.value.ValueShort;
import org.h2.value.ValueString;
import org.h2.value.ValueTime;
import org.h2.value.ValueTimestamp;
import org.h2.value.ValueUuid;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import static java.sql.ResultSetMetaData.columnNullableUnknown;
import static org.apache.ignite.internal.processors.query.QueryUtils.KEY_COL;
import static org.apache.ignite.internal.processors.query.QueryUtils.KEY_FIELD_NAME;
import static org.apache.ignite.internal.processors.query.QueryUtils.VAL_FIELD_NAME;
/**
* H2 utility methods.
*/
public class H2Utils {
/** Query context H2 variable name. */
public static final String QCTX_VARIABLE_NAME = "_IGNITE_QUERY_CONTEXT";
/**
* The default precision for a char/varchar value.
*/
static final int STRING_DEFAULT_PRECISION = Integer.MAX_VALUE;
/**
* The default precision for a decimal value.
*/
static final int DECIMAL_DEFAULT_PRECISION = 65535;
/** */
public static final IndexColumn[] EMPTY_COLUMNS = new IndexColumn[0];
/**
* The default scale for a decimal value.
*/
static final int DECIMAL_DEFAULT_SCALE = 32767;
/** Dummy metadata for update result. */
public static final List<GridQueryFieldMetadata> UPDATE_RESULT_META =
Collections.singletonList(new H2SqlFieldMetadata(null, null, "UPDATED", Long.class.getName(), -1, -1,
columnNullableUnknown));
/** Spatial index class name. */
private static final String SPATIAL_IDX_CLS =
"org.apache.ignite.internal.processors.query.h2.opt.GridH2SpatialIndex";
/** Spatial index factory class name. */
private static final String SPATIAL_IDX_FACTORY_CLS =
"org.apache.ignite.internal.processors.query.h2.opt.GeoSpatialUtils";
/** Quotation character. */
private static final char ESC_CH = '\"';
/**
* @param c1 First column.
* @param c2 Second column.
* @return {@code true} If they are the same.
*/
public static boolean equals(IndexColumn c1, IndexColumn c2) {
return c1.column.getColumnId() == c2.column.getColumnId();
}
/**
* @param cols Columns list.
* @param col Column to find.
* @return {@code true} If found.
*/
@SuppressWarnings("BooleanMethodIsAlwaysInverted")
public static boolean containsColumn(List<IndexColumn> cols, IndexColumn col) {
for (int i = cols.size() - 1; i >= 0; i--) {
if (equals(cols.get(i), col))
return true;
}
return false;
}
/**
* Check whether columns list contains key or key alias column.
*
* @param desc Row descriptor.
* @param cols Columns list.
* @return Result.
*/
public static boolean containsKeyColumn(GridH2RowDescriptor desc, List<IndexColumn> cols) {
for (int i = cols.size() - 1; i >= 0; i--) {
if (desc.isKeyColumn(cols.get(i).column.getColumnId()))
return true;
}
return false;
}
/**
* Prepare SQL statement for CREATE TABLE command.
*
* @param tbl Table descriptor.
* @return SQL.
*/
public static String tableCreateSql(H2TableDescriptor tbl) {
GridQueryProperty keyProp = tbl.type().property(KEY_FIELD_NAME);
GridQueryProperty valProp = tbl.type().property(VAL_FIELD_NAME);
String keyType = dbTypeFromClass(tbl.type().keyClass(),
keyProp == null ? -1 : keyProp.precision(),
keyProp == null ? -1 : keyProp.scale());
String valTypeStr = dbTypeFromClass(tbl.type().valueClass(),
valProp == null ? -1 : valProp.precision(),
valProp == null ? -1 : valProp.scale());
SB sql = new SB();
String keyValVisibility = tbl.type().fields().isEmpty() ? " VISIBLE" : " INVISIBLE";
sql.a("CREATE TABLE ").a(tbl.fullTableName()).a(" (")
.a(KEY_FIELD_NAME).a(' ').a(keyType).a(keyValVisibility).a(" NOT NULL");
sql.a(',').a(VAL_FIELD_NAME).a(' ').a(valTypeStr).a(keyValVisibility);
for (Map.Entry<String, Class<?>> e : tbl.type().fields().entrySet()) {
GridQueryProperty prop = tbl.type().property(e.getKey());
sql.a(',')
.a(withQuotes(e.getKey()))
.a(' ')
.a(dbTypeFromClass(e.getValue(), prop.precision(), prop.scale()))
.a(prop.notNull() ? " NOT NULL" : "");
}
sql.a(')');
return sql.toString();
}
/**
* Generate {@code CREATE INDEX} SQL statement for given params.
* @param fullTblName Fully qualified table name.
* @param h2Idx H2 index.
* @param ifNotExists Quietly skip index creation if it exists.
* @return Statement string.
*/
public static String indexCreateSql(String fullTblName, GridH2IndexBase h2Idx, boolean ifNotExists) {
boolean spatial = F.eq(SPATIAL_IDX_CLS, h2Idx.getClass().getName());
GridStringBuilder sb = new SB("CREATE ")
.a(spatial ? "SPATIAL " : "")
.a("INDEX ")
.a(ifNotExists ? "IF NOT EXISTS " : "")
.a(withQuotes(h2Idx.getName()))
.a(" ON ")
.a(fullTblName)
.a(" (");
sb.a(indexColumnsSql(h2Idx.getIndexColumns()));
sb.a(')');
return sb.toString();
}
/**
* Generate String represenation of given indexed columns.
*
* @param idxCols Indexed columns.
* @return String represenation of given indexed columns.
*/
public static String indexColumnsSql(IndexColumn[] idxCols) {
GridStringBuilder sb = new SB();
boolean first = true;
for (IndexColumn col : idxCols) {
if (first)
first = false;
else
sb.a(", ");
sb.a(withQuotes(col.columnName)).a(" ").a(col.sortType == SortOrder.ASCENDING ? "ASC" : "DESC");
}
return sb.toString();
}
/**
* Generate {@code CREATE INDEX} SQL statement for given params.
* @param schemaName <b>Quoted</b> schema name.
* @param idxName Index name.
* @param ifExists Quietly skip index drop if it exists.
* @return Statement string.
*/
public static String indexDropSql(String schemaName, String idxName, boolean ifExists) {
return "DROP INDEX " + (ifExists ? "IF EXISTS " : "") + withQuotes(schemaName) + '.' + withQuotes(idxName);
}
/**
* @param desc Row descriptor.
* @param cols Columns list.
* @param keyCol Primary key column.
* @param affCol Affinity key column.
* @return The same list back.
*/
public static List<IndexColumn> treeIndexColumns(GridH2RowDescriptor desc, List<IndexColumn> cols,
IndexColumn keyCol, IndexColumn affCol) {
assert keyCol != null;
if (!containsKeyColumn(desc, cols))
cols.add(keyCol);
if (affCol != null && !containsColumn(cols, affCol))
cols.add(affCol);
return cols;
}
/**
* Create spatial index.
*
* @param tbl Table.
* @param idxName Index name.
* @param cols Columns.
*/
@SuppressWarnings("ConstantConditions")
public static GridH2IndexBase createSpatialIndex(GridH2Table tbl, String idxName, List<IndexColumn> cols) {
try {
Class<?> fctCls = Class.forName(SPATIAL_IDX_FACTORY_CLS);
Method fctMethod = fctCls.getMethod("createIndex", GridH2Table.class, String.class, List.class);
return (GridH2IndexBase) fctMethod.invoke(null, tbl, idxName, cols);
}
catch (Exception e) {
throw new IgniteException("Failed to instantiate: " + SPATIAL_IDX_CLS, e);
}
}
/**
* Add quotes around the name.
*
* @param str String.
* @return String with quotes.
*/
public static String withQuotes(String str) {
return ESC_CH + str + ESC_CH;
}
/**
* @param rsMeta Metadata.
* @return List of fields metadata.
* @throws SQLException If failed.
*/
public static List<GridQueryFieldMetadata> meta(ResultSetMetaData rsMeta) throws SQLException {
List<GridQueryFieldMetadata> meta = new ArrayList<>(rsMeta.getColumnCount());
for (int i = 1; i <= rsMeta.getColumnCount(); i++) {
String schemaName = rsMeta.getSchemaName(i);
String typeName = rsMeta.getTableName(i);
String name = rsMeta.getColumnLabel(i);
String type = rsMeta.getColumnClassName(i);
int precision = rsMeta.getPrecision(i);
int scale = rsMeta.getScale(i);
int nullability = rsMeta.isNullable(i);
if (type == null) // Expression always returns NULL.
type = Void.class.getName();
meta.add(new H2SqlFieldMetadata(schemaName, typeName, name, type, precision, scale, nullability));
}
return meta;
}
/**
* Converts h2 parameters metadata to Ignite one.
*
* @param h2ParamsMeta parameters metadata returned by h2.
* @return Descriptions of the parameters.
*/
public static List<JdbcParameterMeta> parametersMeta(ParameterMetaData h2ParamsMeta) throws IgniteCheckedException {
try {
int paramsSize = h2ParamsMeta.getParameterCount();
if (paramsSize == 0)
return Collections.emptyList();
ArrayList<JdbcParameterMeta> params = new ArrayList<>(paramsSize);
for (int i = 1; i <= paramsSize; i++)
params.add(new JdbcParameterMeta(h2ParamsMeta, i));
return params;
}
catch (SQLException e) {
throw new IgniteCheckedException("Failed to get parameters metadata", e);
}
}
/**
* @param c Connection.
* @return Session.
*/
public static Session session(H2PooledConnection c) {
return session(c.connection());
}
/**
* @param c Connection.
* @return Session.
*/
public static Session session(Connection c) {
return (Session)((JdbcConnection)c).getSession();
}
/**
* @param conn Connection to use.
* @param qctx Query context.
* @param distributedJoins If distributed joins are enabled.
* @param enforceJoinOrder Enforce join order of tables.
*/
public static void setupConnection(H2PooledConnection conn, QueryContext qctx,
boolean distributedJoins, boolean enforceJoinOrder) {
assert qctx != null;
setupConnection(conn, qctx, distributedJoins, enforceJoinOrder, false);
}
/**
* @param conn Connection to use.
* @param qctx Query context.
* @param distributedJoins If distributed joins are enabled.
* @param enforceJoinOrder Enforce join order of tables.
* @param lazy Lazy query execution mode.
*/
public static void setupConnection(
H2PooledConnection conn,
QueryContext qctx,
boolean distributedJoins,
boolean enforceJoinOrder,
boolean lazy
) {
Session s = session(conn);
s.setForceJoinOrder(enforceJoinOrder);
s.setJoinBatchEnabled(distributedJoins);
s.setLazyQueryExecution(lazy);
QueryContext oldCtx = (QueryContext)s.getVariable(QCTX_VARIABLE_NAME).getObject();
assert oldCtx == null || oldCtx == qctx : oldCtx;
s.setVariable(QCTX_VARIABLE_NAME, new ValueRuntimeSimpleObject<>(qctx));
// Hack with thread local context is used only for H2 methods that is called without Session object.
// e.g. GridH2Table.getRowCountApproximation (used only on optimization phase, after parse).
QueryContext.threadLocal(qctx);
}
/**
* Clean up session for further reuse.
*
* @param conn Connection to use.
*/
public static void resetSession(H2PooledConnection conn) {
Session s = session(conn);
s.setVariable(QCTX_VARIABLE_NAME, ValueNull.INSTANCE);
}
/**
* @param conn Connection to use.
* @return Query context.
*/
public static QueryContext context(H2PooledConnection conn) {
Session s = session(conn);
return context(s);
}
/**
* @param ses Session.
* @return Query context.
*/
public static QueryContext context(Session ses) {
return (QueryContext)ses.getVariable(QCTX_VARIABLE_NAME).getObject();
}
/**
* Convert value to column's expected type by means of H2.
*
* @param val Source value.
* @param idx Row descriptor.
* @param type Expected column type to convert to.
* @return Converted object.
* @throws IgniteCheckedException if failed.
*/
public static Object convert(Object val, IgniteH2Indexing idx, int type) throws IgniteCheckedException {
if (val == null)
return null;
int objType = DataType.getTypeFromClass(val.getClass());
if (objType == type)
return val;
Value h2Val = wrap(idx.objectContext(), val, objType);
return h2Val.convertTo(type).getObject();
}
/**
* Private constructor.
*/
private H2Utils() {
// No-op.
}
/**
* @return Single-column, single-row cursor with 0 as number of updated records.
*/
@SuppressWarnings("unchecked")
public static QueryCursorImpl<List<?>> zeroCursor() {
QueryCursorImpl<List<?>> resCur = (QueryCursorImpl<List<?>>)new QueryCursorImpl(Collections.singletonList(
Collections.singletonList(0L)), null, false, false);
resCur.fieldsMeta(UPDATE_RESULT_META);
return resCur;
}
/**
* Add only new columns to destination list.
*
* @param dest List of index columns to add new elements from src list.
* @param src List of IndexColumns to add to dest list.
*/
public static void addUniqueColumns(List<IndexColumn> dest, List<IndexColumn> src) {
for (IndexColumn col : src) {
if (!containsColumn(dest, col))
dest.add(col);
}
}
/**
* Check that given table has not started cache and start it for such case.
*
* @param tbl Table to check on not started cache.
* @return {@code true} in case not started and has been started.
*/
@SuppressWarnings({"ConstantConditions", "UnusedReturnValue"})
public static boolean checkAndStartNotStartedCache(GridKernalContext ctx, GridH2Table tbl) {
if (tbl != null && tbl.isCacheLazy()) {
String cacheName = tbl.cacheInfo().config().getName();
try {
Boolean res = ctx.cache().dynamicStartCache(null, cacheName, null, false, true, true).get();
return U.firstNotNull(res, Boolean.FALSE);
}
catch (IgniteCheckedException ex) {
throw U.convertException(ex);
}
}
return false;
}
/**
* Wraps object to respective {@link Value}.
*
* @param obj Object.
* @param type Value type.
* @return Value.
* @throws IgniteCheckedException If failed.
*/
@SuppressWarnings("ConstantConditions")
public static Value wrap(CacheObjectValueContext coCtx, Object obj, int type) throws IgniteCheckedException {
assert obj != null;
if (obj instanceof CacheObject) { // Handle cache object.
CacheObject co = (CacheObject)obj;
if (type == Value.JAVA_OBJECT)
return new GridH2ValueCacheObject(co, coCtx);
obj = co.value(coCtx, false);
}
switch (type) {
case Value.BOOLEAN:
return ValueBoolean.get((Boolean)obj);
case Value.BYTE:
return ValueByte.get((Byte)obj);
case Value.SHORT:
return ValueShort.get((Short)obj);
case Value.INT:
return ValueInt.get((Integer)obj);
case Value.FLOAT:
return ValueFloat.get((Float)obj);
case Value.LONG:
return ValueLong.get((Long)obj);
case Value.DOUBLE:
return ValueDouble.get((Double)obj);
case Value.UUID:
UUID uuid = (UUID)obj;
return ValueUuid.get(uuid.getMostSignificantBits(), uuid.getLeastSignificantBits());
case Value.DATE:
if (LocalDateTimeUtils.LOCAL_DATE == obj.getClass())
return LocalDateTimeUtils.localDateToDateValue(obj);
return ValueDate.get((Date)obj);
case Value.TIME:
if (LocalDateTimeUtils.LOCAL_TIME == obj.getClass())
return LocalDateTimeUtils.localTimeToTimeValue(obj);
return ValueTime.get((Time)obj);
case Value.TIMESTAMP:
if (obj instanceof java.util.Date && !(obj instanceof Timestamp))
obj = new Timestamp(((java.util.Date)obj).getTime());
if (LocalDateTimeUtils.LOCAL_DATE_TIME == obj.getClass())
return LocalDateTimeUtils.localDateTimeToValue(obj);
return ValueTimestamp.get((Timestamp)obj);
case Value.DECIMAL:
return ValueDecimal.get((BigDecimal)obj);
case Value.STRING:
return ValueString.get(obj.toString());
case Value.BYTES:
return ValueBytes.get((byte[])obj);
case Value.JAVA_OBJECT:
return ValueJavaObject.getNoCopy(obj, null, null);
case Value.ARRAY:
Object[] arr = (Object[])obj;
Value[] valArr = new Value[arr.length];
for (int i = 0; i < arr.length; i++) {
Object o = arr[i];
valArr[i] = o == null ? ValueNull.INSTANCE :
wrap(coCtx, o, DataType.getTypeFromClass(o.getClass()));
}
return ValueArray.get(valArr);
case Value.GEOMETRY:
return ValueGeometry.getFromGeometry(obj);
}
throw new IgniteCheckedException("Failed to wrap value[type=" + type + ", value=" + obj + "]");
}
/**
* Validates properties described by query types.
*
* @param type Type descriptor.
* @throws IgniteCheckedException If validation failed.
*/
@SuppressWarnings("CollectionAddAllCanBeReplacedWithConstructor")
public static void validateTypeDescriptor(GridQueryTypeDescriptor type)
throws IgniteCheckedException {
assert type != null;
Collection<String> names = new HashSet<>();
names.addAll(type.fields().keySet());
if (names.size() < type.fields().size())
throw new IgniteCheckedException("Found duplicated properties with the same name [keyType=" +
type.keyClass().getName() + ", valueType=" + type.valueClass().getName() + "]");
String ptrn = "Name ''{0}'' is reserved and cannot be used as a field name [type=" + type.name() + "]";
for (String name : names) {
if (name.equalsIgnoreCase(KEY_FIELD_NAME) || name.equalsIgnoreCase(VAL_FIELD_NAME))
throw new IgniteCheckedException(MessageFormat.format(ptrn, name));
}
}
/**
* Gets corresponding DB type from java class.
*
* @param cls Java class.
* @param precision Field precision.
* @param scale Field scale.
* @return DB type name.
*/
private static String dbTypeFromClass(Class<?> cls, int precision, int scale) {
String dbType = H2DatabaseType.fromClass(cls).dBTypeAsString();
if (precision != -1 && scale != -1 && dbType.equalsIgnoreCase(H2DatabaseType.DECIMAL.dBTypeAsString()))
return dbType + "(" + precision + ", " + scale + ')';
if (precision != -1 && (
dbType.equalsIgnoreCase(H2DatabaseType.VARCHAR.dBTypeAsString())
|| dbType.equalsIgnoreCase(H2DatabaseType.DECIMAL.dBTypeAsString())
|| dbType.equalsIgnoreCase(H2DatabaseType.BINARY.dBTypeAsString())))
return dbType + '(' + precision + ')';
return dbType;
}
/**
* Generate SqlFieldsQuery string from SqlQuery.
*
* @param qry Query string.
* @param tableAlias table alias.
* @param tbl Table to use.
* @return Prepared statement.
* @throws IgniteCheckedException In case of error.
*/
public static String generateFieldsQueryString(String qry, String tableAlias, H2TableDescriptor tbl)
throws IgniteCheckedException {
assert tbl != null;
final String qry0 = qry;
String t = tbl.fullTableName();
String from = " ";
qry = qry.trim();
String upper = qry.toUpperCase();
if (upper.startsWith("SELECT")) {
qry = qry.substring(6).trim();
final int star = qry.indexOf('*');
if (star == 0)
qry = qry.substring(1).trim();
else if (star > 0) {
if (F.eq('.', qry.charAt(star - 1))) {
t = qry.substring(0, star - 1);
qry = qry.substring(star + 1).trim();
}
else
throw new IgniteCheckedException("Invalid query (missing alias before asterisk): " + qry0);
}
else
throw new IgniteCheckedException("Only queries starting with 'SELECT *' and 'SELECT alias.*' " +
"are supported (rewrite your query or use SqlFieldsQuery instead): " + qry0);
upper = qry.toUpperCase();
}
if (!upper.startsWith("FROM"))
from = " FROM " + t + (tableAlias != null ? " as " + tableAlias : "") +
(upper.startsWith("WHERE") || upper.startsWith("ORDER") || upper.startsWith("LIMIT") ?
" " : " WHERE ");
if (tableAlias != null)
t = tableAlias;
qry = "SELECT " + t + "." + KEY_FIELD_NAME + ", " + t + "." + VAL_FIELD_NAME + from + qry;
return qry;
}
/**
* @param row Row.
* @return Row message.
*/
public static GridH2RowMessage toRowMessage(Row row) {
if (row == null)
return null;
int cols = row.getColumnCount();
assert cols > 0 : cols;
List<GridH2ValueMessage> vals = new ArrayList<>(cols);
for (int i = 0; i < cols; i++) {
try {
vals.add(GridH2ValueMessageFactory.toMessage(row.getValue(i)));
}
catch (IgniteCheckedException e) {
throw new CacheException(e);
}
}
GridH2RowMessage res = new GridH2RowMessage();
res.values(vals);
return res;
}
/**
* Create retry exception for distributed join.
*
* @param msg Message.
* @return Exception.
*/
public static GridH2RetryException retryException(String msg) {
return new GridH2RetryException(msg);
}
/**
* Binds parameters to prepared statement.
*
* @param stmt Prepared statement.
* @param params Parameters collection.
* @throws IgniteCheckedException If failed.
*/
public static void bindParameters(PreparedStatement stmt, @Nullable Collection<Object> params)
throws IgniteCheckedException {
if (!F.isEmpty(params)) {
int idx = 1;
for (Object arg : params)
bindObject(stmt, idx++, arg);
}
}
/**
* Binds object to prepared statement.
*
* @param stmt SQL statement.
* @param idx Index.
* @param obj Value to store.
* @throws IgniteCheckedException If failed.
*/
private static void bindObject(PreparedStatement stmt, int idx, @Nullable Object obj) throws IgniteCheckedException {
try {
if (obj == null)
stmt.setNull(idx, Types.VARCHAR);
else if (obj instanceof BigInteger)
stmt.setObject(idx, obj, Types.JAVA_OBJECT);
else if (obj instanceof BigDecimal)
stmt.setObject(idx, obj, Types.DECIMAL);
else
stmt.setObject(idx, obj);
}
catch (SQLException e) {
throw new IgniteCheckedException("Failed to bind parameter [idx=" + idx + ", obj=" + obj + ", stmt=" +
stmt + ']', e);
}
}
/**
* @param arr Array.
* @param off Offset.
* @param cmp Comparator.
*/
public static <Z> void bubbleUp(Z[] arr, int off, Comparator<Z> cmp) {
for (int i = off, last = arr.length - 1; i < last; i++) {
if (cmp.compare(arr[i], arr[i + 1]) <= 0)
break;
U.swap(arr, i, i + 1);
}
}
/**
* Collect cache identifiers from two-step query.
*
* @param mainCacheId Id of main cache.
* @return Result.
*/
public static List<Integer> collectCacheIds(
IgniteH2Indexing idx,
@Nullable Integer mainCacheId,
Collection<QueryTable> tbls
) {
LinkedHashSet<Integer> caches0 = new LinkedHashSet<>();
if (mainCacheId != null)
caches0.add(mainCacheId);
if (!F.isEmpty(tbls)) {
for (QueryTable tblKey : tbls) {
GridH2Table tbl = idx.schemaManager().dataTable(tblKey.schema(), tblKey.table());
if (tbl != null) {
checkAndStartNotStartedCache(idx.kernalContext(), tbl);
caches0.add(tbl.cacheId());
}
}
}
return caches0.isEmpty() ? Collections.emptyList() : new ArrayList<>(caches0);
}
/**
* Collect MVCC enabled flag.
*
* @param idx Indexing.
* @param cacheIds Cache IDs.
* @return {@code True} if indexing is enabled.
*/
public static boolean collectMvccEnabled(IgniteH2Indexing idx, List<Integer> cacheIds) {
if (cacheIds.isEmpty())
return false;
GridCacheSharedContext sharedCtx = idx.kernalContext().cache().context();
GridCacheContext cctx0 = null;
boolean mvccEnabled = false;
for (int i = 0; i < cacheIds.size(); i++) {
Integer cacheId = cacheIds.get(i);
GridCacheContext cctx = sharedCtx.cacheContext(cacheId);
assert cctx != null;
if (i == 0) {
mvccEnabled = cctx.mvccEnabled();
cctx0 = cctx;
}
else if (cctx.mvccEnabled() != mvccEnabled)
MvccUtils.throwAtomicityModesMismatchException(cctx0.config(), cctx.config());
}
return mvccEnabled;
}
/**
* Check if query is valid.
*
* @param idx Indexing.
* @param cacheIds Cache IDs.
* @param tbls Tables.
*/
@SuppressWarnings("ForLoopReplaceableByForEach")
public static void checkQuery(
IgniteH2Indexing idx,
List<Integer> cacheIds,
Collection<QueryTable> tbls
) {
GridCacheSharedContext sharedCtx = idx.kernalContext().cache().context();
// Check query parallelism.
int expectedParallelism = 0;
for (int i = 0; i < cacheIds.size(); i++) {
Integer cacheId = cacheIds.get(i);
GridCacheContext cctx = sharedCtx.cacheContext(cacheId);
assert cctx != null;
if (!cctx.isPartitioned())
continue;
if (expectedParallelism == 0)
expectedParallelism = cctx.config().getQueryParallelism();
else if (cctx.config().getQueryParallelism() != expectedParallelism) {
throw new IllegalStateException("Using indexes with different parallelism levels in same query is " +
"forbidden.");
}
}
// Check for joins between system views and normal tables.
if (!F.isEmpty(tbls)) {
for (QueryTable tbl : tbls) {
if (QueryUtils.SCHEMA_SYS.equals(tbl.schema())) {
if (!F.isEmpty(cacheIds)) {
throw new IgniteSQLException("Normal tables and system views cannot be used in the same query.",
IgniteQueryErrorCode.UNSUPPORTED_OPERATION);
}
else
return;
}
}
}
}
/**
* Create list of index columns. Where possible _KEY columns will be unwrapped.
*
* @param tbl GridH2Table instance
* @param idxCols List of index columns.
*
* @return Array of key and affinity columns. Key's, if it possible, splitted into simple components.
*/
@NotNull public static IndexColumn[] unwrapKeyColumns(GridH2Table tbl, IndexColumn[] idxCols) {
ArrayList<IndexColumn> keyCols = new ArrayList<>();
boolean isSql = tbl.rowDescriptor().tableDescriptor().sql();
if (!isSql)
return idxCols;
GridQueryTypeDescriptor type = tbl.rowDescriptor().type();
for (IndexColumn idxCol : idxCols) {
if (idxCol.column.getColumnId() == KEY_COL) {
if (QueryUtils.isSqlType(type.keyClass())) {
int altKeyColId = tbl.rowDescriptor().getAlternativeColumnId(QueryUtils.KEY_COL);
//Remap simple key to alternative column.
IndexColumn idxKeyCol = new IndexColumn();
idxKeyCol.column = tbl.getColumn(altKeyColId);
idxKeyCol.columnName = idxKeyCol.column.getName();
idxKeyCol.sortType = idxCol.sortType;
keyCols.add(idxKeyCol);
}
else {
boolean added = false;
for (String propName : type.fields().keySet()) {
GridQueryProperty prop = type.property(propName);
if (prop.key()) {
added = true;
Column col = tbl.getColumn(propName);
keyCols.add(tbl.indexColumn(col.getColumnId(), SortOrder.ASCENDING));
}
}
// If key is object but the user has not specified any particular columns,
// we have to fall back to whole-key index.
if (!added)
keyCols.add(idxCol);
}
} else
keyCols.add(idxCol);
}
return keyCols.toArray(new IndexColumn[0]);
}
/**
* @param <T>
*/
public static class ValueRuntimeSimpleObject<T> extends Value {
/** */
private final T val;
/**
* @param val Object.
*/
public ValueRuntimeSimpleObject(T val) {
this.val = val;
}
/** {@inheritDoc} */
@Override public String getSQL() {
throw new UnsupportedOperationException();
}
/** {@inheritDoc} */
@Override public int getType() {
return Value.JAVA_OBJECT;
}
/** {@inheritDoc} */
@Override public long getPrecision() {
return 0;
}
/** {@inheritDoc} */
@Override public int getDisplaySize() {
return 0;
}
/** {@inheritDoc} */
@Override public String getString() {
throw new UnsupportedOperationException();
}
/** {@inheritDoc} */
@Override public Object getObject() {
return val;
}
/** {@inheritDoc} */
@Override public void set(PreparedStatement prep, int parameterIndex) throws SQLException {
throw new UnsupportedOperationException();
}
/** {@inheritDoc} */
@Override protected int compareSecure(Value v, CompareMode mode) {
throw new UnsupportedOperationException();
}
/** {@inheritDoc} */
@Override public boolean equals(Object o) {
if (this == o)
return true;
if (o == null || getClass() != o.getClass())
return false;
ValueRuntimeSimpleObject<?> object = (ValueRuntimeSimpleObject<?>)o;
return Objects.equals(val, object.val);
}
/** {@inheritDoc} */
@Override public int hashCode() {
return Objects.hash(val);
}
}
/**
* @param cls Class.
* @param fldName Fld name.
*/
public static <T, R> Getter<T, R> getter(Class<? extends T> cls, String fldName) {
Field field;
try {
field = cls.getDeclaredField(fldName);
}
catch (NoSuchFieldException e) {
throw new RuntimeException(e);
}
field.setAccessible(true);
return new Getter<>(field);
}
/**
* Field getter.
*/
public static class Getter<T, R> {
/** */
private final Field fld;
/**
* @param fld Fld.
*/
private Getter(Field fld) {
this.fld = fld;
}
/**
* @param obj Object.
* @return Result.
*/
public R get(T obj) {
try {
return (R)fld.get(obj);
}
catch (IllegalAccessException e) {
throw new IgniteException(e);
}
}
}
/**
* @param cls Class.
* @param fldName Fld name.
*/
public static <T, R> Setter<T, R> setter(Class<? extends T> cls, String fldName) {
Field field;
try {
field = cls.getDeclaredField(fldName);
}
catch (NoSuchFieldException e) {
throw new RuntimeException(e);
}
field.setAccessible(true);
return new Setter<>(field);
}
/**
* Field getter.
*/
public static class Setter<T, R> {
/** */
private final Field fld;
/**
* @param fld Fld.
*/
private Setter(Field fld) {
this.fld = fld;
}
/**
* @param obj Object.
* @param val Value.
*/
public void set(T obj, R val) {
try {
fld.set(obj, val);
}
catch (IllegalAccessException e) {
throw new IgniteException(e);
}
}
}
}