Repository: ignite Updated Branches: refs/heads/ignite-2.0 af98efd54 -> 68ef21157
IGNITE-4363: SQL: fixed inner property updates for DML operations. Project: http://git-wip-us.apache.org/repos/asf/ignite/repo Commit: http://git-wip-us.apache.org/repos/asf/ignite/commit/70cd8e45 Tree: http://git-wip-us.apache.org/repos/asf/ignite/tree/70cd8e45 Diff: http://git-wip-us.apache.org/repos/asf/ignite/diff/70cd8e45 Branch: refs/heads/ignite-2.0 Commit: 70cd8e452b94b806a2fe6fe00b4b67ce80eb4373 Parents: 0726d32 Author: Alexander Paschenko <alexander.a.pasche...@gmail.com> Authored: Tue Feb 7 11:41:08 2017 +0300 Committer: devozerov <voze...@gridgain.com> Committed: Tue Feb 7 11:41:08 2017 +0300 ---------------------------------------------------------------------- .../configuration/CacheConfiguration.java | 16 ++- .../processors/query/GridQueryProcessor.java | 81 ++++++++++---- .../processors/query/GridQueryProperty.java | 21 ++-- .../query/h2/DmlStatementsProcessor.java | 69 ++++++------ .../query/h2/dml/UpdatePlanBuilder.java | 105 +++++++++++++------ ...niteCacheAbstractInsertSqlQuerySelfTest.java | 4 + .../IgniteCacheAbstractSqlDmlQuerySelfTest.java | 2 +- .../IgniteCacheInsertSqlQuerySelfTest.java | 22 ++++ .../cache/IgniteCacheMergeSqlQuerySelfTest.java | 24 +++++ .../IgniteCacheUpdateSqlQuerySelfTest.java | 63 +++++++++-- .../h2/GridIndexingSpiAbstractSelfTest.java | 5 + 11 files changed, 301 insertions(+), 111 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/ignite/blob/70cd8e45/modules/core/src/main/java/org/apache/ignite/configuration/CacheConfiguration.java ---------------------------------------------------------------------- diff --git a/modules/core/src/main/java/org/apache/ignite/configuration/CacheConfiguration.java b/modules/core/src/main/java/org/apache/ignite/configuration/CacheConfiguration.java index 0656dda..f0179ca 100644 --- a/modules/core/src/main/java/org/apache/ignite/configuration/CacheConfiguration.java +++ b/modules/core/src/main/java/org/apache/ignite/configuration/CacheConfiguration.java @@ -2358,9 +2358,13 @@ public class CacheConfiguration<K, V> extends MutableConfiguration<K, V> { prop.parent(parent); - processAnnotation(key, sqlAnn, txtAnn, field.getType(), prop, type); - + // Add parent property before its possible nested properties so that + // resulting parent column comes before columns corresponding to those + // nested properties in the resulting table - that way nested + // properties override will happen properly (first parent, then children). type.addProperty(prop, key, true); + + processAnnotation(key, sqlAnn, txtAnn, field.getType(), prop, type); } } @@ -2380,9 +2384,13 @@ public class CacheConfiguration<K, V> extends MutableConfiguration<K, V> { prop.parent(parent); - processAnnotation(key, sqlAnn, txtAnn, mtd.getReturnType(), prop, type); - + // Add parent property before its possible nested properties so that + // resulting parent column comes before columns corresponding to those + // nested properties in the resulting table - that way nested + // properties override will happen properly (first parent, then children). type.addProperty(prop, key, true); + + processAnnotation(key, sqlAnn, txtAnn, mtd.getReturnType(), prop, type); } } } http://git-wip-us.apache.org/repos/asf/ignite/blob/70cd8e45/modules/core/src/main/java/org/apache/ignite/internal/processors/query/GridQueryProcessor.java ---------------------------------------------------------------------- diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/query/GridQueryProcessor.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/query/GridQueryProcessor.java index a239ee2..5bfdde8 100644 --- a/modules/core/src/main/java/org/apache/ignite/internal/processors/query/GridQueryProcessor.java +++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/query/GridQueryProcessor.java @@ -64,6 +64,7 @@ import org.apache.ignite.internal.GridKernalContext; import org.apache.ignite.internal.IgniteInternalFuture; import org.apache.ignite.internal.binary.BinaryMarshaller; import org.apache.ignite.internal.binary.BinaryObjectEx; +import org.apache.ignite.internal.binary.BinaryObjectExImpl; import org.apache.ignite.internal.processors.GridProcessorAdapter; import org.apache.ignite.internal.processors.affinity.AffinityTopologyVersion; import org.apache.ignite.internal.processors.cache.CacheEntryImpl; @@ -1948,7 +1949,7 @@ public class GridQueryProcessor extends GridProcessorAdapter { /** * Description of type property. */ - private static class ClassProperty extends GridQueryProperty { + private static class ClassProperty implements GridQueryProperty { /** */ private final PropertyAccessor accessor; @@ -2041,12 +2042,17 @@ public class GridQueryProcessor extends GridProcessorAdapter { @Override public String toString() { return S.toString(ClassProperty.class, this); } + + /** {@inheritDoc} */ + @Override public GridQueryProperty parent() { + return parent; + } } /** * */ - private class BinaryProperty extends GridQueryProperty { + private class BinaryProperty implements GridQueryProperty { /** Property name. */ private String propName; @@ -2130,11 +2136,17 @@ public class GridQueryProcessor extends GridProcessorAdapter { obj = isKeyProp0 == 1 ? key : val; } - assert obj instanceof BinaryObject; - - BinaryObject obj0 = (BinaryObject)obj; + if (obj instanceof BinaryObject) { + BinaryObject obj0 = (BinaryObject) obj; + return fieldValue(obj0); + } + else if (obj instanceof BinaryObjectBuilder) { + BinaryObjectBuilder obj0 = (BinaryObjectBuilder)obj; - return fieldValue(obj0); + return obj0.getField(name()); + } + else + throw new IgniteCheckedException("Unexpected binary object class [type=" + obj.getClass() + ']'); } /** {@inheritDoc} */ @@ -2144,10 +2156,38 @@ public class GridQueryProcessor extends GridProcessorAdapter { if (obj == null) return; + Object srcObj = obj; + + if (!(srcObj instanceof BinaryObjectBuilder)) + throw new UnsupportedOperationException("Individual properties can be set for binary builders only"); + + if (parent != null) + obj = parent.value(key, val); + + boolean needsBuild = false; + + if (obj instanceof BinaryObjectExImpl) { + if (parent == null) + throw new UnsupportedOperationException("Individual properties can be set for binary builders only"); + + needsBuild = true; + + obj = ((BinaryObjectExImpl)obj).toBuilder(); + } + if (!(obj instanceof BinaryObjectBuilder)) throw new UnsupportedOperationException("Individual properties can be set for binary builders only"); setValue0((BinaryObjectBuilder) obj, propName, propVal, type()); + + if (needsBuild) { + obj = ((BinaryObjectBuilder) obj).build(); + + assert parent != null; + + // And now let's set this newly constructed object to parent + setValue0((BinaryObjectBuilder) srcObj, parent.propName, obj, obj.getClass()); + } } /** @@ -2224,6 +2264,11 @@ public class GridQueryProcessor extends GridProcessorAdapter { return isKeyProp0 == 1; } + + /** {@inheritDoc} */ + @Override public GridQueryProperty parent() { + return parent; + } } /** @@ -2307,7 +2352,12 @@ public class GridQueryProcessor extends GridProcessorAdapter { /** {@inheritDoc} */ @Override public GridQueryProperty property(String name) { - return getProperty(name); + GridQueryProperty res = props.get(name); + + if (res == null) + res = uppercaseProps.get(name.toUpperCase()); + + return res; } /** {@inheritDoc} */ @@ -2315,7 +2365,7 @@ public class GridQueryProcessor extends GridProcessorAdapter { @Override public <T> T value(String field, Object key, Object val) throws IgniteCheckedException { assert field != null; - GridQueryProperty prop = getProperty(field); + GridQueryProperty prop = property(field); if (prop == null) throw new IgniteCheckedException("Failed to find field '" + field + "' in type '" + name + "'."); @@ -2329,7 +2379,7 @@ public class GridQueryProcessor extends GridProcessorAdapter { throws IgniteCheckedException { assert field != null; - GridQueryProperty prop = getProperty(field); + GridQueryProperty prop = property(field); if (prop == null) throw new IgniteCheckedException("Failed to find field '" + field + "' in type '" + name + "'."); @@ -2469,19 +2519,6 @@ public class GridQueryProcessor extends GridProcessorAdapter { fields.put(name, prop.type()); } - /** - * @param field Property name. - * @return Property with given field name. - */ - private GridQueryProperty getProperty(String field) { - GridQueryProperty res = props.get(field); - - if (res == null) - res = uppercaseProps.get(field.toUpperCase()); - - return res; - } - /** {@inheritDoc} */ @Override public boolean valueTextIndex() { return valTextIdx; http://git-wip-us.apache.org/repos/asf/ignite/blob/70cd8e45/modules/core/src/main/java/org/apache/ignite/internal/processors/query/GridQueryProperty.java ---------------------------------------------------------------------- diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/query/GridQueryProperty.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/query/GridQueryProperty.java index 5d74a2e..fb4c037 100644 --- a/modules/core/src/main/java/org/apache/ignite/internal/processors/query/GridQueryProperty.java +++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/query/GridQueryProperty.java @@ -22,11 +22,7 @@ import org.apache.ignite.IgniteCheckedException; /** * Description and access method for query entity field. */ -public abstract class GridQueryProperty { - /** */ - public GridQueryProperty() { - } - +public interface GridQueryProperty { /** * Gets this property value from the given object. * @@ -35,7 +31,7 @@ public abstract class GridQueryProperty { * @return Property value. * @throws IgniteCheckedException If failed. */ - public abstract Object value(Object key, Object val) throws IgniteCheckedException; + public Object value(Object key, Object val) throws IgniteCheckedException; /** * Sets this property value for the given object. @@ -45,21 +41,26 @@ public abstract class GridQueryProperty { * @param propVal Property value. * @throws IgniteCheckedException If failed. */ - public abstract void setValue(Object key, Object val, Object propVal) throws IgniteCheckedException; + public void setValue(Object key, Object val, Object propVal) throws IgniteCheckedException; /** * @return Property name. */ - public abstract String name(); + public String name(); /** * @return Class member type. */ - public abstract Class<?> type(); + public Class<?> type(); /** * Property ownership flag. * @return {@code true} if this property belongs to key, {@code false} if it belongs to value. */ - public abstract boolean key(); + public boolean key(); + + /** + * @return Parent property or {@code null} if this property is not nested. + */ + public GridQueryProperty parent(); } http://git-wip-us.apache.org/repos/asf/ignite/blob/70cd8e45/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/DmlStatementsProcessor.java ---------------------------------------------------------------------- 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 4030758..7995083 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 @@ -59,7 +59,6 @@ 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.h2.dml.FastUpdateArguments; -import org.apache.ignite.internal.processors.query.h2.dml.KeyValueSupplier; import org.apache.ignite.internal.processors.query.h2.dml.UpdatePlan; import org.apache.ignite.internal.processors.query.h2.dml.UpdatePlanBuilder; import org.apache.ignite.internal.processors.query.h2.opt.GridH2RowDescriptor; @@ -481,7 +480,6 @@ public class DmlStatementsProcessor { while (it.hasNext()) { List<?> e = it.next(); Object key = e.get(0); - Object val = (hasNewVal ? e.get(valColIdx) : e.get(1)); Object newVal; @@ -500,9 +498,6 @@ public class DmlStatementsProcessor { if (newVal == null) throw new IgniteSQLException("New value for UPDATE must not be null", IgniteQueryErrorCode.NULL_VALUE); - if (bin && !(val instanceof BinaryObject)) - val = cctx.grid().binary().toBinary(val); - // Skip key and value - that's why we start off with 2nd column for (int i = 0; i < plan.tbl.getColumns().length - 2; i++) { Column c = plan.tbl.getColumn(i + 2); @@ -514,13 +509,10 @@ public class DmlStatementsProcessor { boolean hasNewColVal = newColVals.containsKey(c.getName()); - // Binary objects get old field values from the Builder, so we can skip what we're not updating - if (bin && !hasNewColVal) + if (!hasNewColVal) continue; - // Column values that have been explicitly specified have priority over field values in old or new _val - // If no value given for the column, then we expect to find it in value, and not in key - hence null arg. - Object colVal = hasNewColVal ? newColVals.get(c.getName()) : prop.value(null, val); + Object colVal = newColVals.get(c.getName()); // UPDATE currently does not allow to modify key or its fields, so we must be safe to pass null as key. desc.setColumnValue(null, newVal, colVal, i); @@ -696,8 +688,8 @@ public class DmlStatementsProcessor { // 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.colTypes, plan.keySupplier, - plan.valSupplier, plan.keyColIdx, plan.valColIdx, desc); + IgniteBiTuple t = rowToKeyValue(cctx, cursor.iterator().next(), + plan); cctx.cache().put(t.getKey(), t.getValue()); return 1; @@ -709,8 +701,7 @@ public class DmlStatementsProcessor { for (Iterator<List<?>> it = cursor.iterator(); it.hasNext();) { List<?> row = it.next(); - IgniteBiTuple t = rowToKeyValue(cctx, row.toArray(), plan.colNames, plan.colTypes, plan.keySupplier, plan.valSupplier, - plan.keyColIdx, plan.valColIdx, desc); + IgniteBiTuple t = rowToKeyValue(cctx, row, plan); rows.put(t.getKey(), t.getValue()); @@ -742,8 +733,7 @@ public class DmlStatementsProcessor { // 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.colTypes, - plan.keySupplier, plan.valSupplier, plan.keyColIdx, plan.valColIdx, desc); + IgniteBiTuple t = rowToKeyValue(cctx, cursor.iterator().next(), plan); if (cctx.cache().putIfAbsent(t.getKey(), t.getValue())) return 1; @@ -768,8 +758,7 @@ public class DmlStatementsProcessor { while (it.hasNext()) { List<?> row = it.next(); - final IgniteBiTuple t = rowToKeyValue(cctx, row.toArray(), plan.colNames, plan.colTypes, plan.keySupplier, - plan.valSupplier, plan.keyColIdx, plan.valColIdx, desc); + final IgniteBiTuple t = rowToKeyValue(cctx, row, plan); rows.put(t.getKey(), new InsertEntryProcessor(t.getValue())); @@ -837,21 +826,14 @@ public class DmlStatementsProcessor { * 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 rowDesc Row descriptor. + * @param plan Update plan. * @throws IgniteCheckedException if failed. */ @SuppressWarnings({"unchecked", "ConstantConditions", "ResultOfMethodCallIgnored"}) - private IgniteBiTuple<?, ?> rowToKeyValue(GridCacheContext cctx, Object[] row, String[] cols, - 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)); + private IgniteBiTuple<?, ?> rowToKeyValue(GridCacheContext cctx, List<?> row, UpdatePlan plan) + throws IgniteCheckedException { + Object key = plan.keySupplier.apply(row); + Object val = plan.valSupplier.apply(row); if (key == null) throw new IgniteSQLException("Key for INSERT or MERGE must not be null", IgniteQueryErrorCode.NULL_KEY); @@ -859,13 +841,32 @@ public class DmlStatementsProcessor { if (val == null) throw new IgniteSQLException("Value for INSERT or MERGE must not be null", IgniteQueryErrorCode.NULL_VALUE); - GridQueryTypeDescriptor desc = rowDesc.type(); + GridQueryTypeDescriptor desc = plan.tbl.rowDescriptor().type(); + + Map<String, Object> newColVals = new HashMap<>(); + + for (int i = 0; i < plan.colNames.length; i++) { + if (i == plan.keyColIdx || i == plan.valColIdx) + continue; + + newColVals.put(plan.colNames[i], convert(row.get(i), plan.colNames[i], + plan.tbl.rowDescriptor(), plan.colTypes[i])); + } + + // We update columns in the order specified by the table for a reason - table's + // column order preserves their precedence for correct update of nested properties. + Column[] cols = plan.tbl.getColumns(); - for (int i = 0; i < cols.length; i++) { - if (i == keyColIdx || i == valColIdx) + // First 2 columns are _key and _val, skip 'em. + for (int i = 2; i < cols.length; i++) { + String colName = cols[i].getName(); + + if (!newColVals.containsKey(colName)) continue; - desc.setValue(cols[i], key, val, convert(row[i], cols[i], rowDesc, colTypes[i])); + Object colVal = newColVals.get(colName); + + desc.setValue(colName, key, val, colVal); } if (cctx.binaryMarshaller()) { http://git-wip-us.apache.org/repos/asf/ignite/blob/70cd8e45/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/dml/UpdatePlanBuilder.java ---------------------------------------------------------------------- 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 fdcd164..ce2971a 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 @@ -48,6 +48,7 @@ import org.apache.ignite.internal.processors.query.h2.sql.GridSqlTable; import org.apache.ignite.internal.processors.query.h2.sql.GridSqlUnion; import org.apache.ignite.internal.processors.query.h2.sql.GridSqlUpdate; import org.apache.ignite.internal.util.GridUnsafe; +import org.apache.ignite.internal.util.typedef.internal.S; import org.apache.ignite.internal.util.typedef.internal.U; import org.h2.command.Prepared; import org.h2.table.Column; @@ -192,8 +193,8 @@ public final class UpdatePlanBuilder { hasValProps = true; } - KeyValueSupplier keySupplier = createSupplier(cctx, desc.type(), keyColIdx, hasKeyProps, true); - KeyValueSupplier valSupplier = createSupplier(cctx, desc.type(), valColIdx, hasValProps, false); + KeyValueSupplier keySupplier = createSupplier(cctx, desc.type(), keyColIdx, hasKeyProps, true, false); + KeyValueSupplier valSupplier = createSupplier(cctx, desc.type(), valColIdx, hasValProps, false, false); if (stmt instanceof GridSqlMerge) return UpdatePlan.forMerge(tbl.dataTable(), colNames, colTypes, keySupplier, valSupplier, keyColIdx, @@ -253,8 +254,6 @@ public final class UpdatePlanBuilder { GridSqlSelect sel; if (stmt instanceof GridSqlUpdate) { - boolean bin = desc.context().binaryMarshaller(); - List<GridSqlColumn> updatedCols = ((GridSqlUpdate) stmt).cols(); int valColIdx = -1; @@ -282,20 +281,10 @@ public final class UpdatePlanBuilder { if (hasNewVal) valColIdx += 2; - int newValColIdx; - - if (!hasProps) // No distinct properties, only whole new value - let's take it - newValColIdx = valColIdx; - else if (bin) // We update distinct columns in binary mode - let's choose correct index for the builder - newValColIdx = (hasNewVal ? valColIdx : 1); - else // Distinct properties, non binary mode - let's instantiate. - newValColIdx = -1; + int newValColIdx = (hasNewVal ? valColIdx : 1); - // We want supplier to take present value only in case of binary mode as it will create - // whole new object as a result anyway, so we don't need to copy previous property values explicitly. - // Otherwise we always want it to instantiate new object whose properties we will later - // set to current values. - KeyValueSupplier newValSupplier = createSupplier(desc.context(), desc.type(), newValColIdx, hasProps, false); + KeyValueSupplier newValSupplier = createSupplier(desc.context(), desc.type(), newValColIdx, hasProps, + false, true); sel = DmlAstUtils.selectForUpdate((GridSqlUpdate) stmt, errKeysPos); @@ -319,11 +308,11 @@ public final class UpdatePlanBuilder { * @param hasProps Whether column list affects individual properties of key or value. * @param key Whether supplier should be created for key or for value. * @return Closure returning key or value. - * @throws IgniteCheckedException + * @throws IgniteCheckedException If failed. */ @SuppressWarnings({"ConstantConditions", "unchecked"}) private static KeyValueSupplier createSupplier(final GridCacheContext<?, ?> cctx, GridQueryTypeDescriptor desc, - final int colIdx, boolean hasProps, final boolean key) throws IgniteCheckedException { + final int colIdx, boolean hasProps, final boolean key, boolean forUpdate) throws IgniteCheckedException { final String typeName = key ? desc.keyTypeName() : desc.valueTypeName(); //Try to find class for the key locally. @@ -332,15 +321,10 @@ public final class UpdatePlanBuilder { boolean isSqlType = GridQueryProcessor.isSqlType(cls); - // If we don't need to construct anything from scratch, just return value from array. - if (isSqlType || !hasProps || !cctx.binaryMarshaller()) { + // If we don't need to construct anything from scratch, just return value from given list. + if (isSqlType || !hasProps) { if (colIdx != -1) - return new KeyValueSupplier() { - /** {@inheritDoc} */ - @Override public Object apply(List<?> arg) throws IgniteCheckedException { - return arg.get(colIdx); - } - }; + return new PlainValueSupplier(colIdx); else if (isSqlType) // Non constructable keys and values (SQL types) must be present in the query explicitly. throw new IgniteCheckedException((key ? "Key" : "Value") + " is missing from query"); @@ -352,7 +336,12 @@ public final class UpdatePlanBuilder { return new KeyValueSupplier() { /** {@inheritDoc} */ @Override public Object apply(List<?> arg) throws IgniteCheckedException { - BinaryObject bin = cctx.grid().binary().toBinary(arg.get(colIdx)); + Object obj = arg.get(colIdx); + + if (obj == null) + return null; + + BinaryObject bin = cctx.grid().binary().toBinary(obj); return cctx.grid().binary().builder(bin); } @@ -369,6 +358,26 @@ public final class UpdatePlanBuilder { } } else { + if (colIdx != -1) { + if (forUpdate && colIdx == 1) { + // It's the case when the old value has to be taken as the basis for the new one on UPDATE, + // so we have to clone it. And on UPDATE we don't expect any key supplier. + assert !key; + + return new KeyValueSupplier() { + /** {@inheritDoc} */ + @Override public Object apply(List<?> arg) throws IgniteCheckedException { + byte[] oldPropBytes = cctx.marshaller().marshal(arg.get(1)); + + // colVal is another object now, we can mutate it + return cctx.marshaller().unmarshal(oldPropBytes, U.resolveClassLoader(cctx.gridConfig())); + } + }; + } + else // We either are not updating, or the new value is given explicitly, no cloning needed. + return new PlainValueSupplier(colIdx); + } + Constructor<?> ctor; try { @@ -390,8 +399,12 @@ public final class UpdatePlanBuilder { return ctor0.newInstance(); } catch (Exception e) { - throw new IgniteCheckedException("Failed to invoke default ctor for " + - (key ? "key" : "value"), e); + if (S.INCLUDE_SENSITIVE) + throw new IgniteCheckedException("Failed to instantiate " + + (key ? "key" : "value") + " [type=" + typeName + ']', e); + else + throw new IgniteCheckedException("Failed to instantiate " + + (key ? "key" : "value") + '.', e); } } }; @@ -405,8 +418,12 @@ public final class UpdatePlanBuilder { return GridUnsafe.allocateInstance(cls); } catch (InstantiationException e) { - throw new IgniteCheckedException("Failed to invoke default ctor for " + - (key ? "key" : "value"), e); + if (S.INCLUDE_SENSITIVE) + throw new IgniteCheckedException("Failed to instantiate " + + (key ? "key" : "value") + " [type=" + typeName + ']', e); + else + throw new IgniteCheckedException("Failed to instantiate " + + (key ? "key" : "value") + '.', e); } } }; @@ -414,8 +431,6 @@ public final class UpdatePlanBuilder { } } - - /** * @param target Expression to extract the table from. * @return Back end table for this element. @@ -483,4 +498,26 @@ public final class UpdatePlanBuilder { return false; } + + /** + * Simple supplier that just takes specified element of a given row. + */ + private final static class PlainValueSupplier implements KeyValueSupplier { + /** Index of column to use. */ + private final int colIdx; + + /** + * Constructor. + * + * @param colIdx Column index. + */ + private PlainValueSupplier(int colIdx) { + this.colIdx = colIdx; + } + + /** {@inheritDoc} */ + @Override public Object apply(List<?> arg) throws IgniteCheckedException { + return arg.get(colIdx); + } + } } http://git-wip-us.apache.org/repos/asf/ignite/blob/70cd8e45/modules/indexing/src/test/java/org/apache/ignite/internal/processors/cache/IgniteCacheAbstractInsertSqlQuerySelfTest.java ---------------------------------------------------------------------- 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 86d01c7..626846b 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 @@ -46,6 +46,8 @@ import org.apache.ignite.spi.discovery.tcp.ipfinder.vm.TcpDiscoveryVmIpFinder; import org.apache.ignite.testframework.junits.IgniteTestResources; import org.apache.ignite.testframework.junits.common.GridCommonAbstractTest; +import static org.apache.ignite.internal.processors.cache.IgniteCacheUpdateSqlQuerySelfTest.AllTypes; + /** * */ @@ -126,6 +128,8 @@ public abstract class IgniteCacheAbstractInsertSqlQuerySelfTest extends GridComm createCaches(); else createBinaryCaches(); + + ignite(0).createCache(cacheConfig("I2AT", true, false, Integer.class, AllTypes.class)); } /** http://git-wip-us.apache.org/repos/asf/ignite/blob/70cd8e45/modules/indexing/src/test/java/org/apache/ignite/internal/processors/cache/IgniteCacheAbstractSqlDmlQuerySelfTest.java ---------------------------------------------------------------------- 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 649012f..3c92fdf 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 @@ -65,7 +65,7 @@ public abstract class IgniteCacheAbstractSqlDmlQuerySelfTest extends GridCommonA /** * @return whether {@link #marsh} is an instance of {@link BinaryMarshaller} or not. */ - private boolean isBinaryMarshaller() { + protected boolean isBinaryMarshaller() { return marsh instanceof BinaryMarshaller; } http://git-wip-us.apache.org/repos/asf/ignite/blob/70cd8e45/modules/indexing/src/test/java/org/apache/ignite/internal/processors/cache/IgniteCacheInsertSqlQuerySelfTest.java ---------------------------------------------------------------------- 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 e9c21dc..f91f405 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 @@ -17,6 +17,8 @@ package org.apache.ignite.internal.processors.cache; +import java.util.ArrayList; +import java.util.Arrays; import java.util.concurrent.Callable; import javax.cache.CacheException; import org.apache.ignite.IgniteCache; @@ -202,4 +204,24 @@ public class IgniteCacheInsertSqlQuerySelfTest extends IgniteCacheAbstractInsert assertEquals(createPerson(2, "Alex"), p.get(new Key4(2))); } + + /** + * + */ + public void testNestedFieldsHandling() { + IgniteCache<Integer, IgniteCacheUpdateSqlQuerySelfTest.AllTypes> p = ignite(0).cache("I2AT"); + + p.query(new SqlFieldsQuery("insert into AllTypes(_key, innerTypeCol, arrListCol, _val, innerStrCol) " + + "values (1, ?, ?, ?, 'sss')") .setArgs(new IgniteCacheUpdateSqlQuerySelfTest.AllTypes.InnerType(50L), + new ArrayList<>(Arrays.asList(3L, 2L, 1L)), new IgniteCacheUpdateSqlQuerySelfTest.AllTypes(1L))); + + IgniteCacheUpdateSqlQuerySelfTest.AllTypes res = p.get(1); + + IgniteCacheUpdateSqlQuerySelfTest.AllTypes.InnerType resInner = new IgniteCacheUpdateSqlQuerySelfTest.AllTypes.InnerType(50L); + + resInner.innerStrCol = "sss"; + resInner.arrListCol = new ArrayList<>(Arrays.asList(3L, 2L, 1L)); + + assertEquals(resInner, res.innerTypeCol); + } } http://git-wip-us.apache.org/repos/asf/ignite/blob/70cd8e45/modules/indexing/src/test/java/org/apache/ignite/internal/processors/cache/IgniteCacheMergeSqlQuerySelfTest.java ---------------------------------------------------------------------- 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 32b7a12..d9a2d9e 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 @@ -17,9 +17,13 @@ package org.apache.ignite.internal.processors.cache; +import java.util.ArrayList; +import java.util.Arrays; import org.apache.ignite.IgniteCache; import org.apache.ignite.cache.query.SqlFieldsQuery; +import static org.apache.ignite.internal.processors.cache.IgniteCacheUpdateSqlQuerySelfTest.AllTypes; + /** * */ @@ -149,4 +153,24 @@ public class IgniteCacheMergeSqlQuerySelfTest extends IgniteCacheAbstractInsertS assertEquals(createPerson(2, "Alex"), p.get(new Key4(2))); } + + /** + * + */ + public void testNestedFieldsHandling() { + IgniteCache<Integer, AllTypes> p = ignite(0).cache("I2AT"); + + p.query(new SqlFieldsQuery("merge into AllTypes(_key, innerTypeCol, arrListCol, _val, innerStrCol) " + + "values (1, ?, ?, ?, 'sss')") .setArgs(new AllTypes.InnerType(50L), + new ArrayList<>(Arrays.asList(3L, 2L, 1L)), new AllTypes(1L))); + + AllTypes res = p.get(1); + + AllTypes.InnerType resInner = new AllTypes.InnerType(50L); + + resInner.innerStrCol = "sss"; + resInner.arrListCol = new ArrayList<>(Arrays.asList(3L, 2L, 1L)); + + assertEquals(resInner, res.innerTypeCol); + } } http://git-wip-us.apache.org/repos/asf/ignite/blob/70cd8e45/modules/indexing/src/test/java/org/apache/ignite/internal/processors/cache/IgniteCacheUpdateSqlQuerySelfTest.java ---------------------------------------------------------------------- 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 575f617..c3d9d8e 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 @@ -44,6 +44,12 @@ public class IgniteCacheUpdateSqlQuerySelfTest extends IgniteCacheAbstractSqlDml ignite(0).createCache(createAllTypesCacheConfig()); } + /** {@inheritDoc} */ + @Override protected void beforeTest() throws Exception { + super.beforeTest(); + ignite(0).cache("L2AT").clear(); + } + /** * */ @@ -182,17 +188,19 @@ public class IgniteCacheUpdateSqlQuerySelfTest extends IgniteCacheAbstractSqlDml 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))); - cache.query(new SqlFieldsQuery("select \"primitiveIntsCol\" from \"AllTypes\"")).getAll(); - - cache.query(new SqlFieldsQuery("update \"AllTypes\" set \"doubleCol\" = CAST('50' as INT)," + + // Look ma, no hands: first we set value of inner object column (innerTypeCol), then update only one of its + // fields (innerLongCol), while leaving another inner property (innerStrCol) as specified by innerTypeCol. + cache.query(new SqlFieldsQuery("update \"AllTypes\" set \"innerLongCol\" = ?, \"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})); + .setArgs(5, new AllTypes.InnerType(80L), new int[] {2, 3}, new Byte[] {4, 5, 6})); AllTypes res = (AllTypes) cache.get(2L); + assertNotNull(res); + assertEquals(new BigDecimal(301.0).doubleValue(), res.bigDecimalCol.doubleValue()); assertEquals(50.0, res.doubleCol); assertEquals(2L, (long) res.longCol); @@ -202,7 +210,11 @@ public class IgniteCacheUpdateSqlQuerySelfTest extends IgniteCacheAbstractSqlDml 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); + + AllTypes.InnerType expInnerType = new AllTypes.InnerType(80L); + expInnerType.innerLongCol = 5L; + + assertEquals(expInnerType, 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); @@ -213,6 +225,45 @@ public class IgniteCacheUpdateSqlQuerySelfTest extends IgniteCacheAbstractSqlDml assertEquals(49, res.shortCol); } + /** */ + public void testSingleInnerFieldUpdate() throws ParseException { + IgniteCache cache = ignite(0).cache("L2AT"); + + cache.query(new SqlFieldsQuery("insert into \"AllTypes\"(_key, _val, \"dateCol\", \"booleanCol\") values(2, ?," + + "'2016-11-30 12:00:00', false)").setArgs(new AllTypes(2L))); + + assertFalse(cache.query(new SqlFieldsQuery("select * from \"AllTypes\"")).getAll().isEmpty()); + + cache.query(new SqlFieldsQuery("update \"AllTypes\" set \"innerLongCol\" = 5")); + + AllTypes res = (AllTypes) cache.get(2L); + + assertNotNull(res); + + assertEquals(new BigDecimal(301.0).doubleValue(), res.bigDecimalCol.doubleValue()); + assertEquals(3.01, res.doubleCol); + assertEquals(2L, (long) res.longCol); + assertFalse(res.booleanCol); + + assertEquals("2", res.strCol); + assertTrue(Arrays.equals(new byte[] {0, 1}, res.primitiveBytesCol)); + assertTrue(Arrays.deepEquals(new Byte[] {0, 1}, res.bytesCol)); + assertTrue(Arrays.deepEquals(new Integer[] {0, 1}, res.intsCol)); + assertTrue(Arrays.equals(new int[] {0, 1}, res.primitiveIntsCol)); + + AllTypes.InnerType expInnerType = new AllTypes.InnerType(2L); + expInnerType.innerLongCol = 5L; + + assertEquals(expInnerType, res.innerTypeCol); + assertEquals(new SimpleDateFormat("yyyy-MM-dd HH:mm:SS").parse("2016-11-30 12:00:00"), res.dateCol); + assertNull(res.tsCol); + assertEquals(2, res.intCol); + assertEquals(AllTypes.EnumType.ENUMTRUE, res.enumCol); + assertNull(res.sqlDateCol); + + assertEquals(-23000, res.shortCol); + } + /** * */ @@ -308,7 +359,7 @@ public class IgniteCacheUpdateSqlQuerySelfTest extends IgniteCacheAbstractSqlDml InnerType innerTypeCol; /** */ - static final class InnerType implements Serializable { + static class InnerType implements Serializable { /** */ @QuerySqlField Long innerLongCol; http://git-wip-us.apache.org/repos/asf/ignite/blob/70cd8e45/modules/indexing/src/test/java/org/apache/ignite/internal/processors/query/h2/GridIndexingSpiAbstractSelfTest.java ---------------------------------------------------------------------- diff --git a/modules/indexing/src/test/java/org/apache/ignite/internal/processors/query/h2/GridIndexingSpiAbstractSelfTest.java b/modules/indexing/src/test/java/org/apache/ignite/internal/processors/query/h2/GridIndexingSpiAbstractSelfTest.java index e412828..69285f1 100644 --- a/modules/indexing/src/test/java/org/apache/ignite/internal/processors/query/h2/GridIndexingSpiAbstractSelfTest.java +++ b/modules/indexing/src/test/java/org/apache/ignite/internal/processors/query/h2/GridIndexingSpiAbstractSelfTest.java @@ -578,6 +578,11 @@ public abstract class GridIndexingSpiAbstractSelfTest extends GridCommonAbstract @Override public boolean key() { return false; } + + /** */ + @Override public GridQueryProperty parent() { + return null; + } }; }