IGNITE-4340: Fixed implicit type conversion in DML. This closes #1303.
diff --git a/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/DmlStatementsProcessor.java b/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/DmlStatementsProcessor.java
index c2c8726..4030758 100644
--- a/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/DmlStatementsProcessor.java
+++ b/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/DmlStatementsProcessor.java
@@ -17,11 +17,13 @@
package org.apache.ignite.internal.processors.query.h2;
+import java.lang.reflect.Array;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
+import java.util.Date;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedHashMap;
@@ -73,6 +75,8 @@
import org.h2.command.Prepared;
import org.h2.jdbc.JdbcPreparedStatement;
import org.h2.table.Column;
+import org.h2.value.DataType;
+import org.h2.value.Value;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jsr166.ConcurrentHashMap8;
@@ -487,7 +491,8 @@
if (hasNewVal && i == valColIdx - 2)
continue;
- newColVals.put(plan.colNames[i], e.get(i + 2));
+ newColVals.put(plan.colNames[i], convert(e.get(i + 2), plan.colNames[i],
+ plan.tbl.rowDescriptor(), plan.colTypes[i]));
}
newVal = plan.valSupplier.apply(e);
@@ -575,6 +580,64 @@
}
/**
+ * Convert value to column's expected type by means of H2.
+ *
+ * @param val Source value.
+ * @param colName Column name to search for property.
+ * @param desc Row descriptor.
+ * @param type Expected column type to convert to.
+ * @return Converted object.
+ * @throws IgniteCheckedException if failed.
+ */
+ @SuppressWarnings({"ConstantConditions", "SuspiciousSystemArraycopy"})
+ private static Object convert(Object val, String colName, GridH2RowDescriptor desc, int type)
+ throws IgniteCheckedException {
+ if (val == null)
+ return null;
+
+ GridQueryProperty prop = desc.type().property(colName);
+
+ assert prop != null;
+
+ Class<?> expCls = prop.type();
+
+ Class<?> currCls = val.getClass();
+
+ if (val instanceof Date && currCls != Date.class && expCls == Date.class) {
+ // H2 thinks that java.util.Date is always a Timestamp, while binary marshaller expects
+ // precise Date instance. Let's satisfy it.
+ return new Date(((Date) val).getTime());
+ }
+
+ // We have to convert arrays of reference types manually - see https://issues.apache.org/jira/browse/IGNITE-4327
+ // Still, we only can convert from Object[] to something more precise.
+ if (type == Value.ARRAY && currCls != expCls) {
+ if (currCls != Object[].class)
+ throw new IgniteCheckedException("Unexpected array type - only conversion from Object[] is assumed");
+
+ // Why would otherwise type be Value.ARRAY?
+ assert expCls.isArray();
+
+ Object[] curr = (Object[]) val;
+
+ Object newArr = Array.newInstance(expCls.getComponentType(), curr.length);
+
+ System.arraycopy(curr, 0, newArr, 0, curr.length);
+
+ return newArr;
+ }
+
+ int objType = DataType.getTypeFromClass(val.getClass());
+
+ if (objType == type)
+ return val;
+
+ Value h2Val = desc.wrap(val, objType);
+
+ return h2Val.convertTo(type).getObject();
+ }
+
+ /**
* Process errors of entry processor - split the keys into duplicated/concurrently modified and those whose
* processing yielded an exception.
*
@@ -633,8 +696,8 @@
// If we have just one item to put, just do so
if (plan.rowsNum == 1) {
- IgniteBiTuple t = rowToKeyValue(cctx, cursor.iterator().next().toArray(), plan.colNames, plan.keySupplier,
- plan.valSupplier, plan.keyColIdx, plan.valColIdx, desc.type());
+ IgniteBiTuple t = rowToKeyValue(cctx, cursor.iterator().next().toArray(), plan.colNames, plan.colTypes, plan.keySupplier,
+ plan.valSupplier, plan.keyColIdx, plan.valColIdx, desc);
cctx.cache().put(t.getKey(), t.getValue());
return 1;
@@ -646,8 +709,8 @@
for (Iterator<List<?>> it = cursor.iterator(); it.hasNext();) {
List<?> row = it.next();
- IgniteBiTuple t = rowToKeyValue(cctx, row.toArray(), plan.colNames, plan.keySupplier, plan.valSupplier,
- plan.keyColIdx, plan.valColIdx, desc.type());
+ IgniteBiTuple t = rowToKeyValue(cctx, row.toArray(), plan.colNames, plan.colTypes, plan.keySupplier, plan.valSupplier,
+ plan.keyColIdx, plan.valColIdx, desc);
rows.put(t.getKey(), t.getValue());
@@ -679,8 +742,8 @@
// If we have just one item to put, just do so
if (plan.rowsNum == 1) {
- IgniteBiTuple t = rowToKeyValue(cctx, cursor.iterator().next().toArray(), plan.colNames, plan.keySupplier,
- plan.valSupplier, plan.keyColIdx, plan.valColIdx, desc.type());
+ IgniteBiTuple t = rowToKeyValue(cctx, cursor.iterator().next().toArray(), plan.colNames, plan.colTypes,
+ plan.keySupplier, plan.valSupplier, plan.keyColIdx, plan.valColIdx, desc);
if (cctx.cache().putIfAbsent(t.getKey(), t.getValue()))
return 1;
@@ -705,8 +768,8 @@
while (it.hasNext()) {
List<?> row = it.next();
- final IgniteBiTuple t = rowToKeyValue(cctx, row.toArray(), plan.colNames, plan.keySupplier,
- plan.valSupplier, plan.keyColIdx, plan.valColIdx, desc.type());
+ final IgniteBiTuple t = rowToKeyValue(cctx, row.toArray(), plan.colNames, plan.colTypes, plan.keySupplier,
+ plan.valSupplier, plan.keyColIdx, plan.valColIdx, desc);
rows.put(t.getKey(), new InsertEntryProcessor(t.getValue()));
@@ -772,22 +835,21 @@
/**
* Convert row presented as an array of Objects into key-value pair to be inserted to cache.
- *
* @param cctx Cache context.
* @param row Row to process.
* @param cols Query cols.
+ * @param colTypes Column types to convert data from {@code row} to.
* @param keySupplier Key instantiation method.
* @param valSupplier Key instantiation method.
* @param keyColIdx Key column index, or {@code -1} if no key column is mentioned in {@code cols}.
* @param valColIdx Value column index, or {@code -1} if no value column is mentioned in {@code cols}.
- * @param desc Table descriptor.
- * @return Key-value pair.
+ * @param rowDesc Row descriptor.
* @throws IgniteCheckedException if failed.
*/
@SuppressWarnings({"unchecked", "ConstantConditions", "ResultOfMethodCallIgnored"})
private IgniteBiTuple<?, ?> rowToKeyValue(GridCacheContext cctx, Object[] row, String[] cols,
- KeyValueSupplier keySupplier, KeyValueSupplier valSupplier, int keyColIdx, int valColIdx,
- GridQueryTypeDescriptor desc) throws IgniteCheckedException {
+ int[] colTypes, KeyValueSupplier keySupplier, KeyValueSupplier valSupplier, int keyColIdx, int valColIdx,
+ GridH2RowDescriptor rowDesc) throws IgniteCheckedException {
Object key = keySupplier.apply(F.asList(row));
Object val = valSupplier.apply(F.asList(row));
@@ -797,11 +859,13 @@
if (val == null)
throw new IgniteSQLException("Value for INSERT or MERGE must not be null", IgniteQueryErrorCode.NULL_VALUE);
+ GridQueryTypeDescriptor desc = rowDesc.type();
+
for (int i = 0; i < cols.length; i++) {
if (i == keyColIdx || i == valColIdx)
continue;
- desc.setValue(cols[i], key, val, row[i]);
+ desc.setValue(cols[i], key, val, convert(row[i], cols[i], rowDesc, colTypes[i]));
}
if (cctx.binaryMarshaller()) {
diff --git a/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/IgniteH2Indexing.java b/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/IgniteH2Indexing.java
index f4ffbc7..5df44db 100644
--- a/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/IgniteH2Indexing.java
+++ b/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/IgniteH2Indexing.java
@@ -212,10 +212,10 @@
private static final int TWO_STEP_QRY_CACHE_SIZE = 1024;
/** Field name for key. */
- public static final String KEY_FIELD_NAME = "_key";
+ public static final String KEY_FIELD_NAME = "_KEY";
/** Field name for value. */
- public static final String VAL_FIELD_NAME = "_val";
+ public static final String VAL_FIELD_NAME = "_VAL";
/** */
private static final Field COMMAND_FIELD;
@@ -1246,8 +1246,8 @@
cachesCreated = true;
}
else
- throw new IgniteSQLException("Failed to parse query: " + sqlQry, e.getSQLState(),
- IgniteQueryErrorCode.PARSING);
+ throw new IgniteSQLException("Failed to parse query: " + sqlQry,
+ IgniteQueryErrorCode.PARSING, e);
}
}
}
@@ -1459,7 +1459,7 @@
String ptrn = "Name ''{0}'' is reserved and cannot be used as a field name [type=" + type.name() + "]";
for (String name : names) {
- if (name.equals(KEY_FIELD_NAME) || name.equals(VAL_FIELD_NAME))
+ if (name.equalsIgnoreCase(KEY_FIELD_NAME) || name.equalsIgnoreCase(VAL_FIELD_NAME))
throw new IgniteCheckedException(MessageFormat.format(ptrn, name));
}
diff --git a/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/dml/UpdatePlan.java b/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/dml/UpdatePlan.java
index 5976f4c..b81ac60 100644
--- a/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/dml/UpdatePlan.java
+++ b/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/dml/UpdatePlan.java
@@ -33,6 +33,12 @@
/** Column names to set or update. */
public final String[] colNames;
+ /**
+ * Expected column types to set or insert/merge.
+ * @see org.h2.value.Value
+ */
+ public final int[] colTypes;
+
/** Method to create key for INSERT or MERGE, ignored for UPDATE and DELETE. */
public final KeyValueSupplier keySupplier;
@@ -59,10 +65,11 @@
public final FastUpdateArguments fastUpdateArgs;
/** */
- private UpdatePlan(UpdateMode mode, GridH2Table tbl, String[] colNames, KeyValueSupplier keySupplier,
+ private UpdatePlan(UpdateMode mode, GridH2Table tbl, String[] colNames, int[] colTypes, KeyValueSupplier keySupplier,
KeyValueSupplier valSupplier, int keyColIdx, int valColIdx, String selectQry, boolean isLocSubqry,
int rowsNum, FastUpdateArguments fastUpdateArgs) {
this.colNames = colNames;
+ this.colTypes = colTypes;
this.rowsNum = rowsNum;
assert mode != null;
assert tbl != null;
@@ -79,43 +86,43 @@
}
/** */
- public static UpdatePlan forMerge(GridH2Table tbl, String[] colNames, KeyValueSupplier keySupplier,
+ public static UpdatePlan forMerge(GridH2Table tbl, String[] colNames, int[] colTypes, KeyValueSupplier keySupplier,
KeyValueSupplier valSupplier, int keyColIdx, int valColIdx, String selectQry, boolean isLocSubqry,
int rowsNum) {
assert !F.isEmpty(colNames);
- return new UpdatePlan(UpdateMode.MERGE, tbl, colNames, keySupplier, valSupplier, keyColIdx, valColIdx,
+ return new UpdatePlan(UpdateMode.MERGE, tbl, colNames, colTypes, keySupplier, valSupplier, keyColIdx, valColIdx,
selectQry, isLocSubqry, rowsNum, null);
}
/** */
- public static UpdatePlan forInsert(GridH2Table tbl, String[] colNames, KeyValueSupplier keySupplier,
+ public static UpdatePlan forInsert(GridH2Table tbl, String[] colNames, int[] colTypes, KeyValueSupplier keySupplier,
KeyValueSupplier valSupplier, int keyColIdx, int valColIdx, String selectQry, boolean isLocSubqry, int rowsNum) {
assert !F.isEmpty(colNames);
- return new UpdatePlan(UpdateMode.INSERT, tbl, colNames, keySupplier, valSupplier, keyColIdx, valColIdx, selectQry,
- isLocSubqry, rowsNum, null);
+ return new UpdatePlan(UpdateMode.INSERT, tbl, colNames, colTypes, keySupplier, valSupplier, keyColIdx, valColIdx,
+ selectQry, isLocSubqry, rowsNum, null);
}
/** */
- public static UpdatePlan forUpdate(GridH2Table tbl, String[] colNames, KeyValueSupplier valSupplier, int valColIdx,
- String selectQry) {
+ public static UpdatePlan forUpdate(GridH2Table tbl, String[] colNames, int[] colTypes, KeyValueSupplier valSupplier,
+ int valColIdx, String selectQry) {
assert !F.isEmpty(colNames);
- return new UpdatePlan(UpdateMode.UPDATE, tbl, colNames, null, valSupplier, -1, valColIdx, selectQry,
+ return new UpdatePlan(UpdateMode.UPDATE, tbl, colNames, colTypes, null, valSupplier, -1, valColIdx, selectQry,
false, 0, null);
}
/** */
public static UpdatePlan forDelete(GridH2Table tbl, String selectQry) {
- return new UpdatePlan(UpdateMode.DELETE, tbl, null, null, null, -1, -1, selectQry, false, 0, null);
+ return new UpdatePlan(UpdateMode.DELETE, tbl, null, null, null, null, -1, -1, selectQry, false, 0, null);
}
/** */
public static UpdatePlan forFastUpdate(UpdateMode mode, GridH2Table tbl, FastUpdateArguments fastUpdateArgs) {
assert mode == UpdateMode.UPDATE || mode == UpdateMode.DELETE;
- return new UpdatePlan(mode, tbl, null, null, null, -1, -1, null, false, 0, fastUpdateArgs);
+ return new UpdatePlan(mode, tbl, null, null, null, null, -1, -1, null, false, 0, fastUpdateArgs);
}
}
diff --git a/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/dml/UpdatePlanBuilder.java b/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/dml/UpdatePlanBuilder.java
index 549b901..fdcd164 100644
--- a/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/dml/UpdatePlanBuilder.java
+++ b/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/dml/UpdatePlanBuilder.java
@@ -132,7 +132,7 @@
// not for updates, and hence will allow putting new pairs only.
// We don't quote _key and _val column names on CREATE TABLE, so they are always uppercase here.
GridSqlColumn[] keys = merge.keys();
- if (keys.length != 1 || IgniteH2Indexing.KEY_FIELD_NAME.equals(keys[0].columnName()))
+ if (keys.length != 1 || !IgniteH2Indexing.KEY_FIELD_NAME.equals(keys[0].columnName()))
throw new CacheException("SQL MERGE does not support arbitrary keys");
cols = merge.columns();
@@ -161,22 +161,30 @@
String[] colNames = new String[cols.length];
- for (int i = 0; i < cols.length; i++) {
- colNames[i] = cols[i].columnName();
+ int[] colTypes = new int[cols.length];
- if (isKeyColumn(cols[i].columnName(), desc)) {
+ for (int i = 0; i < cols.length; i++) {
+ GridSqlColumn col = cols[i];
+
+ String colName = col.columnName();
+
+ colNames[i] = colName;
+
+ colTypes[i] = col.resultType().type();
+
+ if (KEY_FIELD_NAME.equals(colName)) {
keyColIdx = i;
continue;
}
- if (isValColumn(cols[i].columnName(), desc)) {
+ if (VAL_FIELD_NAME.equals(colName)) {
valColIdx = i;
continue;
}
- GridQueryProperty prop = desc.type().property(cols[i].columnName());
+ GridQueryProperty prop = desc.type().property(colName);
- assert prop != null : "Property '" + cols[i].columnName() + "' not found.";
+ assert prop != null : "Property '" + colName + "' not found.";
if (prop.key())
hasKeyProps = true;
@@ -188,11 +196,11 @@
KeyValueSupplier valSupplier = createSupplier(cctx, desc.type(), valColIdx, hasValProps, false);
if (stmt instanceof GridSqlMerge)
- return UpdatePlan.forMerge(tbl.dataTable(), colNames, keySupplier, valSupplier, keyColIdx, valColIdx,
- sel.getSQL(), !isTwoStepSubqry, rowsNum);
+ return UpdatePlan.forMerge(tbl.dataTable(), colNames, colTypes, keySupplier, valSupplier, keyColIdx,
+ valColIdx, sel.getSQL(), !isTwoStepSubqry, rowsNum);
else
- return UpdatePlan.forInsert(tbl.dataTable(), colNames, keySupplier, valSupplier, keyColIdx, valColIdx,
- sel.getSQL(), !isTwoStepSubqry, rowsNum);
+ return UpdatePlan.forInsert(tbl.dataTable(), colNames, colTypes, keySupplier, valSupplier, keyColIdx,
+ valColIdx, sel.getSQL(), !isTwoStepSubqry, rowsNum);
}
/**
@@ -253,10 +261,14 @@
String[] colNames = new String[updatedCols.size()];
+ int[] colTypes = new int[updatedCols.size()];
+
for (int i = 0; i < updatedCols.size(); i++) {
colNames[i] = updatedCols.get(i).columnName();
- if (isValColumn(colNames[i], desc))
+ colTypes[i] = updatedCols.get(i).resultType().type();
+
+ if (VAL_FIELD_NAME.equals(colNames[i]))
valColIdx = i;
}
@@ -287,7 +299,7 @@
sel = DmlAstUtils.selectForUpdate((GridSqlUpdate) stmt, errKeysPos);
- return UpdatePlan.forUpdate(gridTbl, colNames, newValSupplier, valColIdx, sel.getSQL());
+ return UpdatePlan.forUpdate(gridTbl, colNames, colTypes, newValSupplier, valColIdx, sel.getSQL());
}
else {
sel = DmlAstUtils.selectForDelete((GridSqlDelete) stmt, errKeysPos);
@@ -471,32 +483,4 @@
return false;
}
-
- /**
- * Check that given column corresponds to the key with respect to case sensitivity, if needed (should be considered
- * when the schema escapes all identifiers on table creation).
- * @param colName Column name.
- * @param desc Row descriptor.
- * @return {@code true} if column name corresponds to _key with respect to case sensitivity depending on schema.
- */
- private static boolean isKeyColumn(String colName, GridH2RowDescriptor desc) {
- if (desc.quoteAllIdentifiers())
- return KEY_FIELD_NAME.equals(colName);
- else
- return KEY_FIELD_NAME.equalsIgnoreCase(colName);
- }
-
- /**
- * Check that given column corresponds to the key with respect to case sensitivity, if needed (should be considered
- * when the schema escapes all identifiers on table creation).
- * @param colName Column name.
- * @param desc Row descriptor.
- * @return {@code true} if column name corresponds to _key with respect to case sensitivity depending on schema.
- */
- private static boolean isValColumn(String colName, GridH2RowDescriptor desc) {
- if (desc.quoteAllIdentifiers())
- return VAL_FIELD_NAME.equals(colName);
- else
- return VAL_FIELD_NAME.equalsIgnoreCase(colName);
- }
}
diff --git a/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/sql/DmlAstUtils.java b/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/sql/DmlAstUtils.java
index 5ff715e..6deb146 100644
--- a/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/sql/DmlAstUtils.java
+++ b/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/sql/DmlAstUtils.java
@@ -280,7 +280,7 @@
GridSqlElement right = op.child(1);
return left instanceof GridSqlColumn &&
- colName.equalsIgnoreCase(((GridSqlColumn) left).columnName()) &&
+ colName.equals(((GridSqlColumn) left).columnName()) &&
(right instanceof GridSqlConst || right instanceof GridSqlParameter);
}
diff --git a/modules/indexing/src/test/java/org/apache/ignite/internal/processors/cache/IgniteCacheAbstractInsertSqlQuerySelfTest.java b/modules/indexing/src/test/java/org/apache/ignite/internal/processors/cache/IgniteCacheAbstractInsertSqlQuerySelfTest.java
index 47369ee..df4259e 100644
--- a/modules/indexing/src/test/java/org/apache/ignite/internal/processors/cache/IgniteCacheAbstractInsertSqlQuerySelfTest.java
+++ b/modules/indexing/src/test/java/org/apache/ignite/internal/processors/cache/IgniteCacheAbstractInsertSqlQuerySelfTest.java
@@ -217,7 +217,7 @@
flds.put("Id", Integer.class.getName());
flds.put("id", Integer.class.getName());
flds.put("name", String.class.getName());
- flds.put("_Val", Integer.class.getName());
+ flds.put("IntVal", Integer.class.getName());
k22p.setFields(flds);
@@ -336,7 +336,7 @@
if (!isBinaryMarshaller()) {
Person2 p = new Person2(id);
p.name = name;
- p._Val = valFld;
+ p.IntVal = valFld;
return p;
}
@@ -344,7 +344,7 @@
BinaryObjectBuilder o = grid(0).binary().builder("Person2");
o.setField("id", id);
o.setField("name", name);
- o.setField("_Val", valFld);
+ o.setField("IntVal", valFld);
return o.build();
}
@@ -554,6 +554,6 @@
/** */
@QuerySqlField
- public int _Val;
+ public int IntVal;
}
}
diff --git a/modules/indexing/src/test/java/org/apache/ignite/internal/processors/cache/IgniteCacheAbstractSqlDmlQuerySelfTest.java b/modules/indexing/src/test/java/org/apache/ignite/internal/processors/cache/IgniteCacheAbstractSqlDmlQuerySelfTest.java
index 1424163..7f79ec4 100644
--- a/modules/indexing/src/test/java/org/apache/ignite/internal/processors/cache/IgniteCacheAbstractSqlDmlQuerySelfTest.java
+++ b/modules/indexing/src/test/java/org/apache/ignite/internal/processors/cache/IgniteCacheAbstractSqlDmlQuerySelfTest.java
@@ -166,7 +166,7 @@
* @param escapeSql whether identifiers should be quoted - see {@link CacheConfiguration#setSqlEscapeAll}
* @return Cache configuration.
*/
- private static CacheConfiguration cacheConfig(String name, boolean partitioned, boolean escapeSql) {
+ protected static CacheConfiguration cacheConfig(String name, boolean partitioned, boolean escapeSql) {
return new CacheConfiguration()
.setName(name)
.setCacheMode(partitioned ? CacheMode.PARTITIONED : CacheMode.REPLICATED)
diff --git a/modules/indexing/src/test/java/org/apache/ignite/internal/processors/cache/IgniteCacheInsertSqlQuerySelfTest.java b/modules/indexing/src/test/java/org/apache/ignite/internal/processors/cache/IgniteCacheInsertSqlQuerySelfTest.java
index 4f87740..04a352f 100644
--- a/modules/indexing/src/test/java/org/apache/ignite/internal/processors/cache/IgniteCacheInsertSqlQuerySelfTest.java
+++ b/modules/indexing/src/test/java/org/apache/ignite/internal/processors/cache/IgniteCacheInsertSqlQuerySelfTest.java
@@ -119,7 +119,7 @@
public void testFieldsCaseSensitivity() {
IgniteCache<Key2, Person> p = ignite(0).cache("K22P").withKeepBinary();
- p.query(new SqlFieldsQuery("insert into \"Person2\" (\"Id\", \"id\", \"name\", \"_Val\") values (1, ?, ?, 5), " +
+ p.query(new SqlFieldsQuery("insert into \"Person2\" (\"Id\", \"id\", \"name\", \"IntVal\") values (1, ?, ?, 5), " +
"(2, 3, 'Alex', 6)").setArgs(4, "Sergi"));
assertEquals(createPerson2(4, "Sergi", 5), p.get(new Key2(1)));
diff --git a/modules/indexing/src/test/java/org/apache/ignite/internal/processors/cache/IgniteCacheMergeSqlQuerySelfTest.java b/modules/indexing/src/test/java/org/apache/ignite/internal/processors/cache/IgniteCacheMergeSqlQuerySelfTest.java
index e487564..0ff3fda 100644
--- a/modules/indexing/src/test/java/org/apache/ignite/internal/processors/cache/IgniteCacheMergeSqlQuerySelfTest.java
+++ b/modules/indexing/src/test/java/org/apache/ignite/internal/processors/cache/IgniteCacheMergeSqlQuerySelfTest.java
@@ -95,7 +95,7 @@
public void testFieldsCaseSensitivity() {
IgniteCache<Key2, Person> p = ignite(0).cache("K22P").withKeepBinary();
- p.query(new SqlFieldsQuery("merge into \"Person2\" (\"Id\", \"id\", \"name\", \"_Val\") values (1, ?, ?, 5), " +
+ p.query(new SqlFieldsQuery("merge into \"Person2\" (\"Id\", \"id\", \"name\", \"IntVal\") values (1, ?, ?, 5), " +
"(2, 3, 'Alex', 6)").setArgs(4, "Sergi"));
assertEquals(createPerson2(4, "Sergi", 5), p.get(new Key2(1)));
diff --git a/modules/indexing/src/test/java/org/apache/ignite/internal/processors/cache/IgniteCacheUpdateSqlQuerySelfTest.java b/modules/indexing/src/test/java/org/apache/ignite/internal/processors/cache/IgniteCacheUpdateSqlQuerySelfTest.java
index 538141f..332a082 100644
--- a/modules/indexing/src/test/java/org/apache/ignite/internal/processors/cache/IgniteCacheUpdateSqlQuerySelfTest.java
+++ b/modules/indexing/src/test/java/org/apache/ignite/internal/processors/cache/IgniteCacheUpdateSqlQuerySelfTest.java
@@ -17,17 +17,44 @@
package org.apache.ignite.internal.processors.cache;
+import java.io.Serializable;
+import java.math.BigDecimal;
+import java.sql.Timestamp;
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+import java.util.ArrayList;
import java.util.Arrays;
+import java.util.Date;
import java.util.List;
import org.apache.ignite.IgniteCache;
import org.apache.ignite.cache.query.QueryCursor;
import org.apache.ignite.cache.query.SqlFieldsQuery;
+import org.apache.ignite.cache.query.annotations.QuerySqlField;
+import org.apache.ignite.configuration.CacheConfiguration;
/**
*
*/
@SuppressWarnings("unchecked")
public class IgniteCacheUpdateSqlQuerySelfTest extends IgniteCacheAbstractSqlDmlQuerySelfTest {
+ /** {@inheritDoc} */
+ @Override protected void beforeTestsStarted() throws Exception {
+ super.beforeTestsStarted();
+
+ ignite(0).createCache(createAllTypesCacheConfig());
+ }
+
+ /**
+ *
+ */
+ private static CacheConfiguration createAllTypesCacheConfig() {
+ CacheConfiguration ccfg = cacheConfig("L2AT", true, true);
+
+ ccfg.setIndexedTypes(Long.class, AllTypes.class);
+
+ return ccfg;
+ }
+
/**
*
*/
@@ -147,4 +174,299 @@
assertEqualsCollections(Arrays.asList("k3", createPerson(3, "Sylvia", "Green"), 3, "Sylvia", "Green"),
leftovers.get(3));
}
+
+ /** */
+ public void testTypeConversions() throws ParseException {
+ IgniteCache cache = ignite(0).cache("L2AT");
+
+ cache.query(new SqlFieldsQuery("insert into \"AllTypes\"(_key, _val, \"dateCol\", \"booleanCol\"," +
+ "\"tsCol\") values(2, ?, '2016-11-30 12:00:00', false, DATE '2016-12-01')").setArgs(new AllTypes(2L)));
+
+ List<?> ll = cache.query(new SqlFieldsQuery("select \"primitiveIntsCol\" from \"AllTypes\"")).getAll();
+
+ cache.query(new SqlFieldsQuery("update \"AllTypes\" set \"doubleCol\" = CAST('50' as INT)," +
+ " \"booleanCol\" = 80, \"innerTypeCol\" = ?, \"strCol\" = PI(), \"shortCol\" = " +
+ "CAST(WEEK(PARSEDATETIME('2016-11-30', 'yyyy-MM-dd')) as VARCHAR), " +
+ "\"sqlDateCol\"=TIMESTAMP '2016-12-02 13:47:00', \"tsCol\"=TIMESTAMPADD('MI', 2, " +
+ "DATEADD('DAY', 2, \"tsCol\")), \"primitiveIntsCol\" = ?, \"bytesCol\" = ?")
+ .setArgs(new AllTypes.InnerType(80L), new int[] {2, 3}, new Byte[] {4, 5, 6}));
+
+ AllTypes res = (AllTypes) cache.get(2L);
+
+ assertEquals(new BigDecimal(301.0).doubleValue(), res.bigDecimalCol.doubleValue());
+ assertEquals(50.0, res.doubleCol);
+ assertEquals(2L, (long) res.longCol);
+ assertTrue(res.booleanCol);
+ assertEquals("3.141592653589793", res.strCol);
+ assertTrue(Arrays.equals(new byte[] {0, 1}, res.primitiveBytesCol));
+ assertTrue(Arrays.equals(new Byte[] {4, 5, 6}, res.bytesCol));
+ assertTrue(Arrays.deepEquals(new Integer[] {0, 1}, res.intsCol));
+ assertTrue(Arrays.equals(new int[] {2, 3}, res.primitiveIntsCol));
+ assertEquals(new AllTypes.InnerType(80L), res.innerTypeCol);
+ assertEquals(new SimpleDateFormat("yyyy-MM-dd HH:mm:SS").parse("2016-11-30 12:00:00"), res.dateCol);
+ assertEquals(new SimpleDateFormat("yyyy-MM-dd HH:mm:SS").parse("2016-12-03 00:02:00"), res.tsCol);
+ assertEquals(2, res.intCol);
+ assertEquals(AllTypes.EnumType.ENUMTRUE, res.enumCol);
+ assertEquals(new java.sql.Date(new SimpleDateFormat("yyyy-MM-dd").parse("2016-12-02").getTime()), res.sqlDateCol);
+
+ // 49th week, right?
+ assertEquals(49, res.shortCol);
+ }
+
+ /**
+ *
+ */
+ static final class AllTypes implements Serializable {
+ /**
+ * Data Long.
+ */
+ @QuerySqlField
+ Long longCol;
+
+ /**
+ * Data double.
+ */
+ @QuerySqlField
+ double doubleCol;
+
+ /**
+ * Data String.
+ */
+ @QuerySqlField
+ String strCol;
+
+ /**
+ * Data boolean.
+ */
+ @QuerySqlField
+ boolean booleanCol;
+
+ /**
+ * Date.
+ */
+ @QuerySqlField
+ Date dateCol;
+
+ /**
+ * SQL date (non timestamp).
+ */
+ @QuerySqlField
+ java.sql.Date sqlDateCol;
+
+ /**
+ * Timestamp.
+ */
+ @QuerySqlField
+ Timestamp tsCol;
+
+ /**
+ * Data int.
+ */
+ @QuerySqlField
+ int intCol;
+
+ /**
+ * BigDecimal
+ */
+ @QuerySqlField
+ BigDecimal bigDecimalCol;
+
+ /**
+ * Data bytes array.
+ */
+ @QuerySqlField
+ Byte[] bytesCol;
+
+ /**
+ * Data bytes primitive array.
+ */
+ @QuerySqlField
+ byte[] primitiveBytesCol;
+
+ /**
+ * Data bytes array.
+ */
+ @QuerySqlField
+ Integer[] intsCol;
+
+ /**
+ * Data bytes primitive array.
+ */
+ @QuerySqlField
+ int[] primitiveIntsCol;
+
+ /**
+ * Data bytes array.
+ */
+ @QuerySqlField
+ short shortCol;
+
+ /**
+ * Inner type object.
+ */
+ @QuerySqlField
+ InnerType innerTypeCol;
+
+ /** */
+ static final class InnerType implements Serializable {
+ /** */
+ @QuerySqlField
+ Long innerLongCol;
+
+ /** */
+ @QuerySqlField
+ String innerStrCol;
+
+ /** */
+ @QuerySqlField
+ ArrayList<Long> arrListCol = new ArrayList<>();
+
+ /** */
+ InnerType(Long key) {
+ innerLongCol = key;
+ innerStrCol = Long.toString(key);
+
+ Long m = key % 8;
+
+ for (Integer i = 0; i < m; i++)
+ arrListCol.add(key + i);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override public String toString() {
+ return "[Long=" + Long.toString(innerLongCol) +
+ ", String='" + innerStrCol + "'" +
+ ", ArrayList=" + arrListCol.toString() +
+ "]";
+ }
+
+ /** {@inheritDoc} */
+ @Override public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+
+ InnerType innerType = (InnerType) o;
+
+ if (innerLongCol != null ? !innerLongCol.equals(innerType.innerLongCol) : innerType.innerLongCol != null)
+ return false;
+ if (innerStrCol != null ? !innerStrCol.equals(innerType.innerStrCol) : innerType.innerStrCol != null)
+ return false;
+ return arrListCol != null ? arrListCol.equals(innerType.arrListCol) : innerType.arrListCol == null;
+
+ }
+
+ /** {@inheritDoc} */
+ @Override public int hashCode() {
+ int res = innerLongCol != null ? innerLongCol.hashCode() : 0;
+ res = 31 * res + (innerStrCol != null ? innerStrCol.hashCode() : 0);
+ res = 31 * res + (arrListCol != null ? arrListCol.hashCode() : 0);
+ return res;
+ }
+ }
+
+ /** */
+ @QuerySqlField
+ EnumType enumCol;
+
+ /** */
+ enum EnumType {
+ /** */
+ ENUMTRUE,
+
+ /** */
+ ENUMFALSE
+ }
+
+ /** */
+ private void init(Long key, String str) {
+ this.longCol = key;
+ this.doubleCol = Math.round(1000 * Math.log10(longCol.doubleValue()));
+ this.bigDecimalCol = BigDecimal.valueOf(doubleCol);
+ this.doubleCol = doubleCol / 100;
+ this.strCol = str;
+ if (key % 2 == 0) {
+ this.booleanCol = true;
+ this.enumCol = EnumType.ENUMTRUE;
+ this.innerTypeCol = new InnerType(key);
+ }
+ else {
+ this.booleanCol = false;
+ this.enumCol = EnumType.ENUMFALSE;
+ this.innerTypeCol = null;
+ }
+ this.intCol = key.intValue();
+ this.bytesCol = new Byte[(int) (key % 10)];
+ this.intsCol = new Integer[(int) (key % 10)];
+ this.primitiveBytesCol = new byte[(int) (key % 10)];
+ this.primitiveIntsCol = new int[(int) (key % 10)];
+ //this.bytesCol = new Byte[10];
+ int b = 0;
+ for (int j = 0; j < bytesCol.length; j++) {
+ if (b == 256)
+ b = 0;
+ bytesCol[j] = (byte) b;
+ primitiveBytesCol[j] = (byte) b;
+ intsCol[j] = b;
+ primitiveIntsCol[j] = b;
+ b++;
+ }
+ this.shortCol = (short) (((1000 * key) % 50000) - 25000);
+
+ dateCol = new Date();
+ }
+
+ /** */
+ AllTypes(Long key) {
+ this.init(key, Long.toString(key));
+ }
+
+ /** {@inheritDoc} */
+ @Override public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+
+ AllTypes allTypes = (AllTypes) o;
+
+ if (Double.compare(allTypes.doubleCol, doubleCol) != 0) return false;
+ if (booleanCol != allTypes.booleanCol) return false;
+ if (intCol != allTypes.intCol) return false;
+ if (shortCol != allTypes.shortCol) return false;
+ if (longCol != null ? !longCol.equals(allTypes.longCol) : allTypes.longCol != null) return false;
+ if (strCol != null ? !strCol.equals(allTypes.strCol) : allTypes.strCol != null) return false;
+ if (dateCol != null ? !dateCol.equals(allTypes.dateCol) : allTypes.dateCol != null) return false;
+ if (sqlDateCol != null ? !sqlDateCol.equals(allTypes.sqlDateCol) : allTypes.sqlDateCol != null) return false;
+ if (tsCol != null ? !tsCol.equals(allTypes.tsCol) : allTypes.tsCol != null) return false;
+ if (bigDecimalCol != null ? !bigDecimalCol.equals(allTypes.bigDecimalCol) : allTypes.bigDecimalCol != null)
+ return false;
+ // Probably incorrect - comparing Object[] arrays with Arrays.equals
+ if (!Arrays.equals(bytesCol, allTypes.bytesCol)) return false;
+ if (innerTypeCol != null ? !innerTypeCol.equals(allTypes.innerTypeCol) : allTypes.innerTypeCol != null)
+ return false;
+ return enumCol == allTypes.enumCol;
+
+ }
+
+ /** {@inheritDoc} */
+ @Override public int hashCode() {
+ int res;
+ long temp;
+ res = longCol != null ? longCol.hashCode() : 0;
+ temp = Double.doubleToLongBits(doubleCol);
+ res = 31 * res + (int) (temp ^ (temp >>> 32));
+ res = 31 * res + (strCol != null ? strCol.hashCode() : 0);
+ res = 31 * res + (booleanCol ? 1 : 0);
+ res = 31 * res + (dateCol != null ? dateCol.hashCode() : 0);
+ res = 31 * res + (sqlDateCol != null ? sqlDateCol.hashCode() : 0);
+ res = 31 * res + (tsCol != null ? tsCol.hashCode() : 0);
+ res = 31 * res + intCol;
+ res = 31 * res + (bigDecimalCol != null ? bigDecimalCol.hashCode() : 0);
+ res = 31 * res + Arrays.hashCode(bytesCol);
+ res = 31 * res + (int) shortCol;
+ res = 31 * res + (innerTypeCol != null ? innerTypeCol.hashCode() : 0);
+ res = 31 * res + (enumCol != null ? enumCol.hashCode() : 0);
+ return res;
+ }
+ }
}