From 76d2e43c063660179bf6223b493ae0e9b1aa552e Mon Sep 17 00:00:00 2001 From: Alexander Paschenko Date: Mon, 12 Dec 2016 13:35:47 +0300 Subject: [PATCH 01/12] IGNITE-4363 Non binary UPDATE/inner properties general UPDATE fix --- .../configuration/CacheConfiguration.java | 16 +++- .../processors/query/GridQueryProcessor.java | 83 +++++++++++++----- .../processors/query/GridQueryProperty.java | 21 ++--- .../query/h2/DmlStatementsProcessor.java | 45 ++++------ .../query/h2/dml/UpdatePlanBuilder.java | 85 ++++++++++++------- ...gniteCacheAbstractSqlDmlQuerySelfTest.java | 2 +- .../IgniteCacheUpdateSqlQuerySelfTest.java | 63 ++++++++++++-- .../h2/GridIndexingSpiAbstractSelfTest.java | 5 ++ 8 files changed, 219 insertions(+), 101 deletions(-) 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 56fc5b4c9129f..b8897052aeedd 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 @@ -2356,9 +2356,13 @@ private static void processAnnotationsInClass(boolean key, Class cls, TypeDes 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); } } @@ -2378,9 +2382,13 @@ private static void processAnnotationsInClass(boolean key, Class cls, TypeDes 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); } } } 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 1594ceee81c33..cb5247dd1e49f 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 @@ -63,6 +63,7 @@ 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; @@ -1929,7 +1930,7 @@ public static AffinityTopologyVersion getRequestAffinityTopologyVersion() { /** * Description of type property. */ - private static class ClassProperty extends GridQueryProperty { + private static class ClassProperty implements GridQueryProperty { /** */ private final PropertyAccessor accessor; @@ -2022,12 +2023,17 @@ public void parent(ClassProperty parent) { @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; @@ -2111,11 +2117,17 @@ else if (val instanceof BinaryObject && ((BinaryObject)val).hasField(propName)) obj = isKeyProp0 == 1 ? key : val; } - assert obj instanceof BinaryObject; - - BinaryObject obj0 = (BinaryObject)obj; - - return fieldValue(obj0); + if (obj instanceof BinaryObject) { + BinaryObject obj0 = (BinaryObject) obj; + return fieldValue(obj0); + } + else if (obj instanceof BinaryObjectBuilder) { + BinaryObjectBuilder obj0 = (BinaryObjectBuilder) obj; + return obj0.getField(name()); + } + else + throw new IgniteCheckedException("Unexpected type of binary object to get field value from [obj=" + obj + + ", type=" + obj.getClass() + ']'); } /** {@inheritDoc} */ @@ -2125,10 +2137,38 @@ else if (val instanceof BinaryObject && ((BinaryObject)val).hasField(propName)) 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, name(), 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.name(), obj, obj.getClass()); + } } /** @@ -2205,6 +2245,11 @@ private Object fieldValue(BinaryObject obj) { return isKeyProp0 == 1; } + + /** {@inheritDoc} */ + @Override public GridQueryProperty parent() { + return parent; + } } /** @@ -2288,7 +2333,12 @@ void name(String name) { /** {@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} */ @@ -2296,7 +2346,7 @@ void name(String name) { @Override public 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 + "'."); @@ -2310,7 +2360,7 @@ void name(String name) { 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 + "'."); @@ -2450,19 +2500,6 @@ public void addProperty(GridQueryProperty prop, boolean failOnDuplicate) throws 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; 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 5d74a2e2756a5..fb4c037f32c35 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 @@ /** * 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 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 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(); } 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 40307581b93e1..a18efb15aef9e 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 @@ -55,11 +55,11 @@ import org.apache.ignite.internal.processors.query.GridQueryFieldMetadata; import org.apache.ignite.internal.processors.query.GridQueryFieldsResult; import org.apache.ignite.internal.processors.query.GridQueryFieldsResultAdapter; +import org.apache.ignite.internal.processors.query.GridQueryProcessor; 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; @@ -514,8 +514,7 @@ private UpdateResult doUpdate(UpdatePlan plan, QueryCursorImpl> cursor, 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 @@ -696,8 +695,8 @@ private long doMerge(UpdatePlan plan, QueryCursorImpl> cursor, int pageS // 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 +708,8 @@ private long doMerge(UpdatePlan plan, QueryCursorImpl> cursor, int pageS for (Iterator> 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 +741,7 @@ private long doInsert(UpdatePlan plan, QueryCursorImpl> cursor, int page // 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 +766,8 @@ private long doInsert(UpdatePlan plan, QueryCursorImpl> cursor, int page 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 +835,13 @@ private static PageProcessingResult processPage(GridCacheContext cctx, * 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 +849,14 @@ private static PageProcessingResult processPage(GridCacheContext cctx, 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(); - for (int i = 0; i < cols.length; i++) { - if (i == keyColIdx || i == valColIdx) + for (int i = 0; i < plan.colNames.length; i++) { + if (i == plan.keyColIdx || i == plan.valColIdx) continue; - desc.setValue(cols[i], key, val, convert(row[i], cols[i], rowDesc, colTypes[i])); + desc.setValue(plan.colNames[i], key, val, convert(row.get(i), plan.colNames[i], plan.tbl.rowDescriptor(), + plan.colTypes[i])); } if (cctx.binaryMarshaller()) { 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 fdcd1647dca01..47e89a3868f4b 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 @@ -24,6 +24,7 @@ import javax.cache.CacheException; import org.apache.ignite.IgniteCheckedException; import org.apache.ignite.binary.BinaryObject; +import org.apache.ignite.cache.affinity.AffinityKey; import org.apache.ignite.internal.processors.cache.GridCacheContext; import org.apache.ignite.internal.processors.cache.query.IgniteQueryErrorCode; import org.apache.ignite.internal.processors.query.GridQueryProcessor; @@ -192,8 +193,8 @@ else throw new IgniteSQLException("Unexpected DML operation [cls=" + stmt.getCla 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 @@ else if (stmt instanceof GridSqlDelete) { GridSqlSelect sel; if (stmt instanceof GridSqlUpdate) { - boolean bin = desc.context().binaryMarshaller(); - List updatedCols = ((GridSqlUpdate) stmt).cols(); int valColIdx = -1; @@ -282,20 +281,10 @@ else if (stmt instanceof GridSqlDelete) { 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); @@ -323,7 +312,7 @@ else if (bin) // We update distinct columns in binary mode - let's choose correc */ @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 @@ private static KeyValueSupplier createSupplier(final GridCacheContext cctx 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 @@ else if (isSqlType) 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 @@ else if (isSqlType) } } 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 { @@ -391,7 +400,7 @@ else if (isSqlType) } catch (Exception e) { throw new IgniteCheckedException("Failed to invoke default ctor for " + - (key ? "key" : "value"), e); + (key ? "key" : "value") + " [type=" + typeName + ']', e); } } }; @@ -405,8 +414,8 @@ else if (isSqlType) return GridUnsafe.allocateInstance(cls); } catch (InstantiationException e) { - throw new IgniteCheckedException("Failed to invoke default ctor for " + - (key ? "key" : "value"), e); + throw new IgniteCheckedException("Failed to instantiate " + + (key ? "key" : "value") + " via Unsafe [type=" + typeName + ']', e); } } }; @@ -483,4 +492,20 @@ private static boolean updateAffectsKeyColumns(GridH2Table gridTbl, Set 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; + + /** */ + private PlainValueSupplier(int colIdx) { + this.colIdx = colIdx; + } + + /** {@inheritDoc} */ + @Override public Object apply(List arg) throws IgniteCheckedException { + return arg.get(colIdx); + } + } } 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 7f79ec4623206..f08b4521f3a9f 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; } 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 332a082201512..2f83b207db695 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 void testTypeConversions() throws ParseException { 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)," + + // 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 void testTypeConversions() throws ParseException { 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 void testTypeConversions() throws ParseException { 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 @@ static final class AllTypes implements Serializable { InnerType innerTypeCol; /** */ - static final class InnerType implements Serializable { + static class InnerType implements Serializable { /** */ @QuerySqlField Long innerLongCol; 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 512001f2f1a6f..7ae39f81ee86a 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 @@ String space() { @Override public boolean key() { return false; } + + /** */ + @Override public GridQueryProperty parent() { + return null; + } }; } From ed577b65532c83d1e2863c99d06945af9263af49 Mon Sep 17 00:00:00 2001 From: Alexander Paschenko Date: Mon, 12 Dec 2016 15:48:58 +0300 Subject: [PATCH 02/12] IGNITE-4363 Nested fields handling fix for INSERT/MERGE --- .../query/h2/DmlStatementsProcessor.java | 23 ++++++++++++++--- ...teCacheAbstractInsertSqlQuerySelfTest.java | 4 +++ .../IgniteCacheInsertSqlQuerySelfTest.java | 22 ++++++++++++++++ .../IgniteCacheMergeSqlQuerySelfTest.java | 25 ++++++++++++++++++- 4 files changed, 70 insertions(+), 4 deletions(-) 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 a18efb15aef9e..831de4b2fb871 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 @@ -55,7 +55,6 @@ import org.apache.ignite.internal.processors.query.GridQueryFieldMetadata; import org.apache.ignite.internal.processors.query.GridQueryFieldsResult; import org.apache.ignite.internal.processors.query.GridQueryFieldsResultAdapter; -import org.apache.ignite.internal.processors.query.GridQueryProcessor; import org.apache.ignite.internal.processors.query.GridQueryProperty; import org.apache.ignite.internal.processors.query.GridQueryTypeDescriptor; import org.apache.ignite.internal.processors.query.IgniteSQLException; @@ -851,12 +850,30 @@ private static PageProcessingResult processPage(GridCacheContext cctx, GridQueryTypeDescriptor desc = plan.tbl.rowDescriptor().type(); + Map newColVals = new HashMap<>(); + for (int i = 0; i < plan.colNames.length; i++) { if (i == plan.keyColIdx || i == plan.valColIdx) continue; - desc.setValue(plan.colNames[i], key, val, convert(row.get(i), plan.colNames[i], plan.tbl.rowDescriptor(), - plan.colTypes[i])); + 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(); + + // 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; + + Object colVal = newColVals.get(colName); + + desc.setValue(colName, key, val, colVal); } if (cctx.binaryMarshaller()) { 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 df4259e94c24b..1924527d5d4d5 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.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 @@ boolean isBinaryMarshaller() { createCaches(); else createBinaryCaches(); + + ignite(0).createCache(cacheConfig("I2AT", true, false, Integer.class, AllTypes.class)); } /** 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 04a352fd24940..1a63e9f74229e 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; @@ -200,4 +202,24 @@ public void testCustomIdentity() { assertEquals(createPerson(2, "Alex"), p.get(new Key4(2))); } + + /** + * + */ + public void testNestedFieldsHandling() { + IgniteCache 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); + } } 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 0ff3fda0da87f..8afeef92a8917 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,12 @@ 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 org.apache.ignite.internal.binary.BinaryMarshaller; + +import static org.apache.ignite.internal.processors.cache.IgniteCacheUpdateSqlQuerySelfTest.AllTypes; /** * @@ -150,4 +153,24 @@ public void testCustomIdentity() { assertEquals(createPerson(2, "Alex"), p.get(new Key4(2))); } + + /** + * + */ + public void testNestedFieldsHandling() { + IgniteCache 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); + } } From e5f15ddcd39e1b4d5578f12ea78eb3225d1ae184 Mon Sep 17 00:00:00 2001 From: Alexander Paschenko Date: Tue, 13 Dec 2016 19:52:49 +0300 Subject: [PATCH 03/12] IGNITE-4363 Removed redundant code --- .../processors/query/h2/DmlStatementsProcessor.java | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) 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 831de4b2fb871..6e800143213c5 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 @@ -480,7 +480,6 @@ private UpdateResult doUpdate(UpdatePlan plan, QueryCursorImpl> cursor, while (it.hasNext()) { List e = it.next(); Object key = e.get(0); - Object val = (hasNewVal ? e.get(valColIdx) : e.get(1)); Object newVal; @@ -499,9 +498,6 @@ private UpdateResult doUpdate(UpdatePlan plan, QueryCursorImpl> cursor, 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); @@ -516,9 +512,7 @@ private UpdateResult doUpdate(UpdatePlan plan, QueryCursorImpl> cursor, 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); From d148d576b10e3e5b20983d8f93760068088d7e40 Mon Sep 17 00:00:00 2001 From: devozerov Date: Mon, 26 Dec 2016 18:18:47 +0300 Subject: [PATCH 04/12] Cosmetics. --- .../internal/processors/query/GridQueryProcessor.java | 3 ++- .../processors/query/h2/DmlStatementsProcessor.java | 9 ++++----- 2 files changed, 6 insertions(+), 6 deletions(-) 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 cb5247dd1e49f..a6f0935e0f2d5 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 @@ -2122,7 +2122,8 @@ else if (val instanceof BinaryObject && ((BinaryObject)val).hasField(propName)) return fieldValue(obj0); } else if (obj instanceof BinaryObjectBuilder) { - BinaryObjectBuilder obj0 = (BinaryObjectBuilder) obj; + BinaryObjectBuilder obj0 = (BinaryObjectBuilder)obj; + return obj0.getField(name()); } else 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 6e800143213c5..7995083519744 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 @@ -701,8 +701,7 @@ private long doMerge(UpdatePlan plan, QueryCursorImpl> cursor, int pageS for (Iterator> it = cursor.iterator(); it.hasNext();) { List row = it.next(); - IgniteBiTuple t = rowToKeyValue(cctx, row, - plan); + IgniteBiTuple t = rowToKeyValue(cctx, row, plan); rows.put(t.getKey(), t.getValue()); @@ -759,8 +758,7 @@ private long doInsert(UpdatePlan plan, QueryCursorImpl> cursor, int page while (it.hasNext()) { List row = it.next(); - final IgniteBiTuple t = rowToKeyValue(cctx, row, - plan); + final IgniteBiTuple t = rowToKeyValue(cctx, row, plan); rows.put(t.getKey(), new InsertEntryProcessor(t.getValue())); @@ -832,7 +830,8 @@ private static PageProcessingResult processPage(GridCacheContext cctx, * @throws IgniteCheckedException if failed. */ @SuppressWarnings({"unchecked", "ConstantConditions", "ResultOfMethodCallIgnored"}) - private IgniteBiTuple rowToKeyValue(GridCacheContext cctx, List row, UpdatePlan plan) throws IgniteCheckedException { + private IgniteBiTuple rowToKeyValue(GridCacheContext cctx, List row, UpdatePlan plan) + throws IgniteCheckedException { Object key = plan.keySupplier.apply(row); Object val = plan.valSupplier.apply(row); From d362da5fdb0671d17672b0096d064ce30254638f Mon Sep 17 00:00:00 2001 From: Alexander Paschenko Date: Mon, 26 Dec 2016 18:39:10 +0300 Subject: [PATCH 05/12] IGNITE-4490 Query-less INSERT and MERGE --- .../query/h2/DmlStatementsProcessor.java | 91 ++++++++++++++----- .../query/h2/dml/FastUpdateArguments.java | 36 ++++++++ .../processors/query/h2/dml/UpdatePlan.java | 43 +++++---- .../query/h2/dml/UpdatePlanBuilder.java | 59 +++++++++--- .../processors/query/h2/sql/DmlAstUtils.java | 55 +++-------- .../IgniteCacheUpdateSqlQuerySelfTest.java | 2 - 6 files changed, 184 insertions(+), 102 deletions(-) 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 40307581b93e1..f660148dce238 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 @@ -58,6 +58,7 @@ 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.FastUpdateArgument; 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; @@ -241,13 +242,15 @@ private UpdateResult executeUpdateStatement(final GridCacheContext cctx, Prepare return new UpdateResult(doSingleUpdate(plan, params), X.EMPTY_OBJECT_ARRAY); } - assert !F.isEmpty(plan.selectQry); + assert !F.isEmpty(plan.rows) ^ !F.isEmpty(plan.selectQry); - QueryCursorImpl> cur; + Iterable> cur; // Do a two-step query only if locality flag is not set AND if plan's SELECT corresponds to an actual // subquery and not some dummy stuff like "select 1, 2, 3;" if (!loc && !plan.isLocSubqry) { + assert !F.isEmpty(plan.selectQry); + SqlFieldsQuery newFieldsQry = new SqlFieldsQuery(plan.selectQry, fieldsQry.isCollocated()) .setArgs(params) .setDistributedJoins(fieldsQry.isDistributedJoins()) @@ -256,13 +259,13 @@ private UpdateResult executeUpdateStatement(final GridCacheContext cctx, Prepare .setPageSize(fieldsQry.getPageSize()) .setTimeout(fieldsQry.getTimeout(), TimeUnit.MILLISECONDS); - cur = (QueryCursorImpl>) indexing.queryTwoStep(cctx, newFieldsQry, cancel); + cur = indexing.queryTwoStep(cctx, newFieldsQry, cancel); } - else { + else if (F.isEmpty(plan.rows)) { final GridQueryFieldsResult res = indexing.queryLocalSqlFields(cctx.name(), plan.selectQry, F.asList(params), filters, fieldsQry.isEnforceJoinOrder(), fieldsQry.getTimeout(), cancel); - cur = new QueryCursorImpl<>(new Iterable>() { + QueryCursorImpl> resCur = new QueryCursorImpl<>(new Iterable>() { @Override public Iterator> iterator() { try { return new GridQueryCacheObjectsIterator(res.iterator(), cctx, cctx.keepBinary()); @@ -273,7 +276,34 @@ private UpdateResult executeUpdateStatement(final GridCacheContext cctx, Prepare } }, cancel); - cur.fieldsMeta(res.metaData()); + resCur.fieldsMeta(res.metaData()); + + cur = resCur; + } + else { + assert plan.rowsNum > 0 && !F.isEmpty(plan.colNames); + + List> args = new ArrayList<>(plan.rowsNum); + + GridH2RowDescriptor desc = plan.tbl.rowDescriptor(); + + for (List argRow : plan.rows) { + List row = new ArrayList<>(); + + for (int j = 0; j < plan.colNames.length; j++) { + Object colVal = argRow.get(j).apply(fieldsQry.getArgs()); + + if (j == plan.keyColIdx || j == plan.valColIdx) + colVal = convert(colVal, j == plan.keyColIdx ? desc.type().keyClass() : desc.type().valueClass(), + desc); + + row.add(colVal); + } + + args.add(row); + } + + cur = args; } int pageSize = loc ? 0 : fieldsQry.getPageSize(); @@ -379,7 +409,7 @@ private static long doSingleUpdate(UpdatePlan plan, Object[] params) throws Igni * @return Results of DELETE (number of items affected AND keys that failed to be updated). */ @SuppressWarnings({"unchecked", "ConstantConditions", "ThrowableResultOfMethodCallIgnored"}) - private UpdateResult doDelete(GridCacheContext cctx, QueryCursorImpl> cursor, int pageSize) + private UpdateResult doDelete(GridCacheContext cctx, Iterable> cursor, int pageSize) throws IgniteCheckedException { // With DELETE, we have only two columns - key and value. long res = 0; @@ -449,7 +479,7 @@ private UpdateResult doDelete(GridCacheContext cctx, QueryCursorImpl> cu * had been modified concurrently (arguments for a re-run)]. */ @SuppressWarnings({"unchecked", "ThrowableResultOfMethodCallIgnored"}) - private UpdateResult doUpdate(UpdatePlan plan, QueryCursorImpl> cursor, int pageSize) + private UpdateResult doUpdate(UpdatePlan plan, Iterable> cursor, int pageSize) throws IgniteCheckedException { GridH2RowDescriptor desc = plan.tbl.rowDescriptor(); @@ -492,7 +522,7 @@ private UpdateResult doUpdate(UpdatePlan plan, QueryCursorImpl> cursor, continue; newColVals.put(plan.colNames[i], convert(e.get(i + 2), plan.colNames[i], - plan.tbl.rowDescriptor(), plan.colTypes[i])); + plan.tbl.rowDescriptor())); } newVal = plan.valSupplier.apply(e); @@ -585,12 +615,11 @@ private UpdateResult doUpdate(UpdatePlan plan, QueryCursorImpl> cursor, * @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) + private static Object convert(Object val, String colName, GridH2RowDescriptor desc) throws IgniteCheckedException { if (val == null) return null; @@ -601,6 +630,21 @@ private static Object convert(Object val, String colName, GridH2RowDescriptor de Class expCls = prop.type(); + return convert(val, expCls, desc); + } + + /** + * Convert value to column's expected type by means of H2. + * + * @param val Source value. + * @param expCls Expected property class. + * @param desc Row descriptor. + * @return Converted object. + * @throws IgniteCheckedException if failed. + */ + @SuppressWarnings({"ConstantConditions", "SuspiciousSystemArraycopy"}) + private static Object convert(Object val, Class expCls, GridH2RowDescriptor desc) + throws IgniteCheckedException { Class currCls = val.getClass(); if (val instanceof Date && currCls != Date.class && expCls == Date.class) { @@ -609,6 +653,8 @@ private static Object convert(Object val, String colName, GridH2RowDescriptor de return new Date(((Date) val).getTime()); } + int type = DataType.getTypeFromClass(expCls); + // 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) { @@ -689,14 +735,14 @@ private static PageProcessingErrorResult splitErrors(Map> cursor, int pageSize) throws IgniteCheckedException { + private long doMerge(UpdatePlan plan, Iterable> cursor, int pageSize) throws IgniteCheckedException { GridH2RowDescriptor desc = plan.tbl.rowDescriptor(); GridCacheContext cctx = desc.context(); // 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, + IgniteBiTuple t = rowToKeyValue(cctx, cursor.iterator().next().toArray(), plan.colNames, plan.keySupplier, plan.valSupplier, plan.keyColIdx, plan.valColIdx, desc); cctx.cache().put(t.getKey(), t.getValue()); @@ -709,7 +755,7 @@ private long doMerge(UpdatePlan plan, QueryCursorImpl> cursor, int pageS for (Iterator> it = cursor.iterator(); it.hasNext();) { List row = it.next(); - IgniteBiTuple t = rowToKeyValue(cctx, row.toArray(), plan.colNames, plan.colTypes, plan.keySupplier, plan.valSupplier, + IgniteBiTuple t = rowToKeyValue(cctx, row.toArray(), plan.colNames, plan.keySupplier, plan.valSupplier, plan.keyColIdx, plan.valColIdx, desc); rows.put(t.getKey(), t.getValue()); @@ -735,14 +781,14 @@ private long doMerge(UpdatePlan plan, QueryCursorImpl> cursor, int pageS * @throws IgniteCheckedException if failed, particularly in case of duplicate keys. */ @SuppressWarnings({"unchecked", "ConstantConditions"}) - private long doInsert(UpdatePlan plan, QueryCursorImpl> cursor, int pageSize) throws IgniteCheckedException { + private long doInsert(UpdatePlan plan, Iterable> cursor, int pageSize) throws IgniteCheckedException { GridH2RowDescriptor desc = plan.tbl.rowDescriptor(); GridCacheContext cctx = desc.context(); // 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, + IgniteBiTuple t = rowToKeyValue(cctx, cursor.iterator().next().toArray(), plan.colNames, plan.keySupplier, plan.valSupplier, plan.keyColIdx, plan.valColIdx, desc); if (cctx.cache().putIfAbsent(t.getKey(), t.getValue())) @@ -768,7 +814,7 @@ private long doInsert(UpdatePlan plan, QueryCursorImpl> cursor, int page while (it.hasNext()) { List row = it.next(); - final IgniteBiTuple t = rowToKeyValue(cctx, row.toArray(), plan.colNames, plan.colTypes, plan.keySupplier, + final IgniteBiTuple t = rowToKeyValue(cctx, row.toArray(), plan.colNames, plan.keySupplier, plan.valSupplier, plan.keyColIdx, plan.valColIdx, desc); rows.put(t.getKey(), new InsertEntryProcessor(t.getValue())); @@ -838,7 +884,6 @@ private static PageProcessingResult processPage(GridCacheContext cctx, * @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}. @@ -848,10 +893,12 @@ private static PageProcessingResult processPage(GridCacheContext cctx, */ @SuppressWarnings({"unchecked", "ConstantConditions", "ResultOfMethodCallIgnored"}) private IgniteBiTuple rowToKeyValue(GridCacheContext cctx, Object[] row, String[] cols, - int[] colTypes, KeyValueSupplier keySupplier, KeyValueSupplier valSupplier, int keyColIdx, int valColIdx, + 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)); + List rowList = F.asList(row); + + Object key = keySupplier.apply(rowList); + Object val = valSupplier.apply(rowList); if (key == null) throw new IgniteSQLException("Key for INSERT or MERGE must not be null", IgniteQueryErrorCode.NULL_KEY); @@ -865,7 +912,7 @@ private static PageProcessingResult processPage(GridCacheContext cctx, if (i == keyColIdx || i == valColIdx) continue; - desc.setValue(cols[i], key, val, convert(row[i], cols[i], rowDesc, colTypes[i])); + desc.setValue(cols[i], key, val, convert(row[i], cols[i], rowDesc)); } if (cctx.binaryMarshaller()) { diff --git a/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/dml/FastUpdateArguments.java b/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/dml/FastUpdateArguments.java index cb47704c83749..056dfaa7c4b5b 100644 --- a/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/dml/FastUpdateArguments.java +++ b/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/dml/FastUpdateArguments.java @@ -50,4 +50,40 @@ public FastUpdateArguments(FastUpdateArgument key, FastUpdateArgument val, FastU return null; } }; + + /** Simple constant value based operand. */ + public final static class ValueArgument implements FastUpdateArgument { + /** Value to return. */ + private final Object val; + + /** */ + public ValueArgument(Object val) { + this.val = val; + } + + /** {@inheritDoc} */ + @Override public Object apply(Object[] arg) throws IgniteCheckedException { + return val; + } + } + + /** Simple constant value based operand. */ + public final static class ParamArgument implements FastUpdateArgument { + /** Value to return. */ + private final int paramIdx; + + /** */ + public ParamArgument(int paramIdx) { + assert paramIdx >= 0; + + this.paramIdx = paramIdx; + } + + /** {@inheritDoc} */ + @Override public Object apply(Object[] arg) throws IgniteCheckedException { + assert arg.length > paramIdx; + + return arg[paramIdx]; + } + } } 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 b81ac60025113..9bd1ecfc7bdf3 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 @@ -17,6 +17,7 @@ package org.apache.ignite.internal.processors.query.h2.dml; +import java.util.List; import org.apache.ignite.internal.processors.query.h2.opt.GridH2Table; import org.apache.ignite.internal.util.typedef.F; @@ -33,12 +34,6 @@ public final class UpdatePlan { /** 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; @@ -58,6 +53,9 @@ public final class UpdatePlan { /** Subquery flag - {@code true} if {@link #selectQry} is an actual subquery that retrieves data from some cache. */ public final boolean isLocSubqry; + /** */ + public final Iterable> rows; + /** Number of rows in rows based MERGE or INSERT. */ public final int rowsNum; @@ -65,11 +63,11 @@ public final class UpdatePlan { public final FastUpdateArguments fastUpdateArgs; /** */ - private UpdatePlan(UpdateMode mode, GridH2Table tbl, String[] colNames, int[] colTypes, KeyValueSupplier keySupplier, + private UpdatePlan(UpdateMode mode, GridH2Table tbl, String[] colNames, KeyValueSupplier keySupplier, KeyValueSupplier valSupplier, int keyColIdx, int valColIdx, String selectQry, boolean isLocSubqry, - int rowsNum, FastUpdateArguments fastUpdateArgs) { + Iterable> rows, int rowsNum, FastUpdateArguments fastUpdateArgs) { this.colNames = colNames; - this.colTypes = colTypes; + this.rows = rows; this.rowsNum = rowsNum; assert mode != null; assert tbl != null; @@ -86,43 +84,44 @@ private UpdatePlan(UpdateMode mode, GridH2Table tbl, String[] colNames, int[] co } /** */ - public static UpdatePlan forMerge(GridH2Table tbl, String[] colNames, int[] colTypes, KeyValueSupplier keySupplier, + public static UpdatePlan forMerge(GridH2Table tbl, String[] colNames, KeyValueSupplier keySupplier, KeyValueSupplier valSupplier, int keyColIdx, int valColIdx, String selectQry, boolean isLocSubqry, - int rowsNum) { + Iterable> rows, int rowsNum) { assert !F.isEmpty(colNames); - return new UpdatePlan(UpdateMode.MERGE, tbl, colNames, colTypes, keySupplier, valSupplier, keyColIdx, valColIdx, - selectQry, isLocSubqry, rowsNum, null); + return new UpdatePlan(UpdateMode.MERGE, tbl, colNames, keySupplier, valSupplier, keyColIdx, valColIdx, + selectQry, isLocSubqry, rows, rowsNum, null); } /** */ - public static UpdatePlan forInsert(GridH2Table tbl, String[] colNames, int[] colTypes, KeyValueSupplier keySupplier, - KeyValueSupplier valSupplier, int keyColIdx, int valColIdx, String selectQry, boolean isLocSubqry, int rowsNum) { + public static UpdatePlan forInsert(GridH2Table tbl, String[] colNames, KeyValueSupplier keySupplier, + KeyValueSupplier valSupplier, int keyColIdx, int valColIdx, String selectQry, boolean isLocSubqry, + Iterable> rows, int rowsNum) { assert !F.isEmpty(colNames); - return new UpdatePlan(UpdateMode.INSERT, tbl, colNames, colTypes, keySupplier, valSupplier, keyColIdx, valColIdx, - selectQry, isLocSubqry, rowsNum, null); + return new UpdatePlan(UpdateMode.INSERT, tbl, colNames, keySupplier, valSupplier, keyColIdx, valColIdx, + selectQry, isLocSubqry, rows, rowsNum, null); } /** */ - public static UpdatePlan forUpdate(GridH2Table tbl, String[] colNames, int[] colTypes, KeyValueSupplier valSupplier, + public static UpdatePlan forUpdate(GridH2Table tbl, String[] colNames, KeyValueSupplier valSupplier, int valColIdx, String selectQry) { assert !F.isEmpty(colNames); - return new UpdatePlan(UpdateMode.UPDATE, tbl, colNames, colTypes, null, valSupplier, -1, valColIdx, selectQry, - false, 0, null); + return new UpdatePlan(UpdateMode.UPDATE, tbl, colNames, null, valSupplier, -1, valColIdx, selectQry, + false, null, 0, null); } /** */ public static UpdatePlan forDelete(GridH2Table tbl, String selectQry) { - return new UpdatePlan(UpdateMode.DELETE, tbl, null, null, null, null, -1, -1, selectQry, false, 0, null); + return new UpdatePlan(UpdateMode.DELETE, tbl, null, null, null, -1, -1, selectQry, false, null, 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, null, -1, -1, null, false, 0, fastUpdateArgs); + return new UpdatePlan(mode, tbl, null, null, null, -1, -1, null, false, null, 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 fdcd1647dca01..0303fa433002f 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 @@ -18,6 +18,7 @@ package org.apache.ignite.internal.processors.query.h2.dml; import java.lang.reflect.Constructor; +import java.util.ArrayList; import java.util.HashSet; import java.util.List; import java.util.Set; @@ -36,10 +37,12 @@ import org.apache.ignite.internal.processors.query.h2.opt.GridH2Table; import org.apache.ignite.internal.processors.query.h2.sql.DmlAstUtils; import org.apache.ignite.internal.processors.query.h2.sql.GridSqlColumn; +import org.apache.ignite.internal.processors.query.h2.sql.GridSqlConst; import org.apache.ignite.internal.processors.query.h2.sql.GridSqlDelete; import org.apache.ignite.internal.processors.query.h2.sql.GridSqlElement; import org.apache.ignite.internal.processors.query.h2.sql.GridSqlInsert; import org.apache.ignite.internal.processors.query.h2.sql.GridSqlMerge; +import org.apache.ignite.internal.processors.query.h2.sql.GridSqlParameter; import org.apache.ignite.internal.processors.query.h2.sql.GridSqlQuery; import org.apache.ignite.internal.processors.query.h2.sql.GridSqlQueryParser; import org.apache.ignite.internal.processors.query.h2.sql.GridSqlSelect; @@ -107,6 +110,10 @@ private static UpdatePlan planForInsert(GridSqlStatement stmt) throws IgniteChec GridH2RowDescriptor desc; + List elRows = null; + + List> rows = null; + if (stmt instanceof GridSqlInsert) { GridSqlInsert ins = (GridSqlInsert) stmt; target = ins.into(); @@ -116,6 +123,10 @@ private static UpdatePlan planForInsert(GridSqlStatement stmt) throws IgniteChec cols = ins.columns(); sel = DmlAstUtils.selectForInsertOrMerge(cols, ins.rows(), ins.query(), desc); + + if (sel == null) + elRows = ins.rows(); + isTwoStepSubqry = (ins.query() != null); rowsNum = isTwoStepSubqry ? 0 : ins.rows().size(); } @@ -137,14 +148,40 @@ else if (stmt instanceof GridSqlMerge) { cols = merge.columns(); sel = DmlAstUtils.selectForInsertOrMerge(cols, merge.rows(), merge.query(), desc); + + if (sel == null) + elRows = merge.rows(); + isTwoStepSubqry = (merge.query() != null); rowsNum = isTwoStepSubqry ? 0 : merge.rows().size(); } else throw new IgniteSQLException("Unexpected DML operation [cls=" + stmt.getClass().getName() + ']', IgniteQueryErrorCode.UNEXPECTED_OPERATION); + if (elRows != null) { + assert sel == null; + + rows = new ArrayList<>(elRows.size()); + + for (GridSqlElement[] elRow : elRows) { + List row = new ArrayList<>(cols.length); + + for (GridSqlElement e : elRow) { + if (e instanceof GridSqlConst) + row.add(new FastUpdateArguments.ValueArgument(((GridSqlConst) e).value().getObject())); + else if (e instanceof GridSqlParameter) + row.add(new FastUpdateArguments.ParamArgument(((GridSqlParameter) e).index())); + else + throw new IgniteSQLException("Unexpected element type: " + e.getClass().getSimpleName(), + IgniteQueryErrorCode.UNEXPECTED_ELEMENT_TYPE); + } + + rows.add(row); + } + } + // Let's set the flag only for subqueries that have their FROM specified. - isTwoStepSubqry = (isTwoStepSubqry && (sel instanceof GridSqlUnion || + isTwoStepSubqry &= (sel != null && (sel instanceof GridSqlUnion || (sel instanceof GridSqlSelect && ((GridSqlSelect) sel).from() != null))); int keyColIdx = -1; @@ -161,8 +198,6 @@ else throw new IgniteSQLException("Unexpected DML operation [cls=" + stmt.getCla String[] colNames = new String[cols.length]; - int[] colTypes = new int[cols.length]; - for (int i = 0; i < cols.length; i++) { GridSqlColumn col = cols[i]; @@ -170,8 +205,6 @@ else throw new IgniteSQLException("Unexpected DML operation [cls=" + stmt.getCla colNames[i] = colName; - colTypes[i] = col.resultType().type(); - if (KEY_FIELD_NAME.equals(colName)) { keyColIdx = i; continue; @@ -196,11 +229,11 @@ else throw new IgniteSQLException("Unexpected DML operation [cls=" + stmt.getCla KeyValueSupplier valSupplier = createSupplier(cctx, desc.type(), valColIdx, hasValProps, false); if (stmt instanceof GridSqlMerge) - return UpdatePlan.forMerge(tbl.dataTable(), colNames, colTypes, keySupplier, valSupplier, keyColIdx, - valColIdx, sel.getSQL(), !isTwoStepSubqry, rowsNum); + return UpdatePlan.forMerge(tbl.dataTable(), colNames, keySupplier, valSupplier, keyColIdx, + valColIdx, sel != null ? sel.getSQL() : null, !isTwoStepSubqry, rows, rowsNum); else - return UpdatePlan.forInsert(tbl.dataTable(), colNames, colTypes, keySupplier, valSupplier, keyColIdx, - valColIdx, sel.getSQL(), !isTwoStepSubqry, rowsNum); + return UpdatePlan.forInsert(tbl.dataTable(), colNames, keySupplier, valSupplier, keyColIdx, + valColIdx, sel != null ? sel.getSQL() : null, !isTwoStepSubqry, rows, rowsNum); } /** @@ -261,13 +294,9 @@ else if (stmt instanceof GridSqlDelete) { 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(); - colTypes[i] = updatedCols.get(i).resultType().type(); - if (VAL_FIELD_NAME.equals(colNames[i])) valColIdx = i; } @@ -299,7 +328,7 @@ else if (bin) // We update distinct columns in binary mode - let's choose correc sel = DmlAstUtils.selectForUpdate((GridSqlUpdate) stmt, errKeysPos); - return UpdatePlan.forUpdate(gridTbl, colNames, colTypes, newValSupplier, valColIdx, sel.getSQL()); + return UpdatePlan.forUpdate(gridTbl, colNames, newValSupplier, valColIdx, sel.getSQL()); } else { sel = DmlAstUtils.selectForDelete((GridSqlDelete) stmt, errKeysPos); @@ -323,7 +352,7 @@ else if (bin) // We update distinct columns in binary mode - let's choose correc */ @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) throws IgniteCheckedException { final String typeName = key ? desc.keyTypeName() : desc.valueTypeName(); //Try to find class for the key locally. 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 6deb1465b4845..8df786c0a8c14 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 @@ -21,7 +21,6 @@ import java.util.HashSet; import java.util.List; import java.util.Set; -import org.apache.ignite.IgniteCheckedException; import org.apache.ignite.IgniteException; import org.apache.ignite.internal.processors.query.IgniteSQLException; import org.apache.ignite.internal.processors.query.h2.IgniteH2Indexing; @@ -67,7 +66,7 @@ private DmlAstUtils() { * @param rows Rows to create pseudo-SELECT upon. * @param subQry Subquery to use rather than rows. * @param desc Row descriptor. - * @return Subquery or pseudo-SELECT to evaluate inserted expressions. + * @return Subquery or pseudo-SELECT to evaluate inserted expressions, or {@code null} no query needs to be run. */ public static GridSqlQuery selectForInsertOrMerge(GridSqlColumn[] cols, List rows, GridSqlQuery subQry, GridH2RowDescriptor desc) { @@ -82,6 +81,8 @@ public static GridSqlQuery selectForInsertOrMerge(GridSqlColumn[] cols, List= 0; - - this.paramIdx = paramIdx; - } - - /** {@inheritDoc} */ - @Override public Object apply(Object[] arg) throws IgniteCheckedException { - assert arg.length > paramIdx; - - return arg[paramIdx]; - } - } } 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 332a082201512..5ee21b299f603 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 @@ -182,8 +182,6 @@ public void testTypeConversions() throws ParseException { 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), " + From 0146046f9e80bf11e215764a38f8427dc48996f1 Mon Sep 17 00:00:00 2001 From: Alexander Paschenko Date: Wed, 28 Dec 2016 11:07:58 +0300 Subject: [PATCH 06/12] IGNITE-4489 UPDATE reworked to better distribute entries processing --- .../query/h2/DmlStatementsProcessor.java | 200 +++++++++++------- .../processors/query/h2/dml/UpdatePlan.java | 39 ++-- .../query/h2/dml/UpdatePlanBuilder.java | 90 +++++--- 3 files changed, 205 insertions(+), 124 deletions(-) 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 e456c0f8d67ff..b48c22f3bc678 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 @@ -45,6 +45,7 @@ import org.apache.ignite.binary.BinaryObject; import org.apache.ignite.binary.BinaryObjectBuilder; import org.apache.ignite.cache.query.SqlFieldsQuery; +import org.apache.ignite.internal.processors.cache.CacheInvokeEntry; import org.apache.ignite.internal.processors.cache.CacheOperationContext; import org.apache.ignite.internal.processors.cache.GridCacheAdapter; import org.apache.ignite.internal.processors.cache.GridCacheContext; @@ -60,17 +61,18 @@ import org.apache.ignite.internal.processors.query.IgniteSQLException; import org.apache.ignite.internal.processors.query.h2.dml.FastUpdateArgument; 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; import org.apache.ignite.internal.processors.query.h2.sql.GridSqlQueryParser; import org.apache.ignite.internal.util.GridBoundedConcurrentLinkedHashMap; import org.apache.ignite.internal.util.lang.IgniteSingletonIterator; +import org.apache.ignite.internal.util.typedef.CIX2; import org.apache.ignite.internal.util.typedef.F; import org.apache.ignite.internal.util.typedef.X; import org.apache.ignite.internal.util.typedef.internal.U; import org.apache.ignite.lang.IgniteBiTuple; -import org.apache.ignite.lang.IgniteInClosure; import org.apache.ignite.spi.indexing.IndexingQueryFilter; import org.h2.command.Prepared; import org.h2.jdbc.JdbcPreparedStatement; @@ -292,7 +294,7 @@ else if (F.isEmpty(plan.rows)) { for (int j = 0; j < plan.colNames.length; j++) { Object colVal = argRow.get(j).apply(fieldsQry.getArgs()); - if (j == plan.keyColIdx || j == plan.valColIdx) + if (j == plan.keyColIdx || j == plan.valColIdx) // The rest columns will be converted later colVal = convert(colVal, j == plan.keyColIdx ? desc.type().keyClass() : desc.type().valueClass(), desc); @@ -486,15 +488,7 @@ private UpdateResult doUpdate(UpdatePlan plan, Iterable> cursor, int pag boolean bin = cctx.binaryMarshaller(); - String[] updatedColNames = plan.colNames; - - int valColIdx = plan.valColIdx; - - boolean hasNewVal = (valColIdx != -1); - - // Statement updates distinct properties if it does not have _val in updated columns list - // or if its list of updated columns includes only _val, i.e. is single element. - boolean hasProps = !hasNewVal || updatedColNames.length > 1; + String[] propNames = plan.colNames; long res = 0; @@ -507,62 +501,28 @@ private UpdateResult doUpdate(UpdatePlan plan, Iterable> cursor, int pag Iterator> it = cursor.iterator(); + ModifierArgs args = new ModifierArgs(desc.type().name(), plan.props, plan.valSupplier); + while (it.hasNext()) { List e = it.next(); - Object key = e.get(0); - - Object newVal; - - Map newColVals = new HashMap<>(); - - for (int i = 0; i < plan.colNames.length; i++) { - if (hasNewVal && i == valColIdx - 2) - continue; - - newColVals.put(plan.colNames[i], convert(e.get(i + 2), plan.colNames[i], - plan.tbl.rowDescriptor())); - } - - newVal = plan.valSupplier.apply(e); - - if (newVal == null) - throw new IgniteSQLException("New value for UPDATE must not be null", IgniteQueryErrorCode.NULL_VALUE); - - // 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); - - GridQueryProperty prop = desc.type().property(c.getName()); - - if (prop.key()) - continue; // Don't get values of key's columns - we won't use them anyway - - boolean hasNewColVal = newColVals.containsKey(c.getName()); - - if (!hasNewColVal) - continue; - 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); - } + Object key = e.get(0); - if (bin && hasProps) { - assert newVal instanceof BinaryObjectBuilder; + Object srcVal = e.get(1); - newVal = ((BinaryObjectBuilder) newVal).build(); - } + Object[] newColVals = e.subList(2, propNames.length + 2).toArray(); - Object srcVal = e.get(1); + for (int i = 0; i < propNames.length; i++) + if (i != plan.valColIdx) + newColVals[i] = convert(newColVals[i], propNames[i], desc); if (bin && !(srcVal instanceof BinaryObject)) srcVal = cctx.grid().binary().toBinary(srcVal); - rows.put(key, new ModifyingEntryProcessor(srcVal, new EntryValueUpdater(newVal))); + rows.put(key, new ModifyingEntryProcessor(srcVal, new EntryValueUpdater(newColVals))); if ((pageSize > 0 && rows.size() == pageSize) || (!it.hasNext())) { - PageProcessingResult pageRes = processPage(cctx, rows); + PageProcessingResult pageRes = processPage(cctx, rows, args); res += pageRes.cnt; @@ -855,8 +815,8 @@ private long doInsert(UpdatePlan plan, Iterable> cursor, int pageSize) t */ @SuppressWarnings({"unchecked", "ConstantConditions"}) private static PageProcessingResult processPage(GridCacheContext cctx, - Map> rows) throws IgniteCheckedException { - Map> res = cctx.cache().invokeAll(rows); + Map> rows, Object... args) throws IgniteCheckedException { + Map> res = cctx.cache().invokeAll(rows, args); if (F.isEmpty(res)) return new PageProcessingResult(rows.size(), null, null); @@ -931,6 +891,18 @@ private static PageProcessingResult processPage(GridCacheContext cctx, return new IgniteBiTuple<>(key, val); } + private static Object[] rowToPropValues(List row, LinkedHashMap props) { + Object[] res = new Object[props.size()]; + + for (Map.Entry e : props.entrySet()) { + assert res[e.getValue()] == null; + + res[e.getValue()] = row.get(e.getKey()); + } + + return res; + } + /** * Set hash code to binary object if it does not have one. * @@ -987,10 +959,10 @@ private final static class ModifyingEntryProcessor implements EntryProcessor> entryModifier; + private final EntryModifier entryModifier; /** */ - private ModifyingEntryProcessor(Object val, IgniteInClosure> entryModifier) { + private ModifyingEntryProcessor(Object val, EntryModifier entryModifier) { assert val != null; this.val = val; @@ -1011,16 +983,60 @@ private ModifyingEntryProcessor(Object val, IgniteInClosure) entry, (ModifierArgs) arguments[0]); + } + catch (Exception e) { + throw new EntryProcessorException(e); + } return null; // To leave out only erroneous keys - nulls are skipped on results' processing. } } + /** + * Arguments for {@link EntryModifier}. + */ + private final static class ModifierArgs { + /** + * Target type descriptor name. + */ + private final String typeName; + + /** + * + */ + private final LinkedHashMap props; + + /** + * Value supplier. + */ + private final KeyValueSupplier valSupplier; + + /** */ + private ModifierArgs(String typeName, LinkedHashMap props, KeyValueSupplier valSupplier) { + this.typeName = typeName; + this.props = props; + this.valSupplier = valSupplier; + } + } + + /** + * In-closure that mutates given entry with respect to given args. + */ + private abstract static class EntryModifier extends CIX2, ModifierArgs> { + } + /** */ - private static IgniteInClosure> RMV = new IgniteInClosure>() { + private static EntryModifier RMV = new EntryModifier() { /** {@inheritDoc} */ - @Override public void apply(MutableEntry e) { + @Override public void applyx(CacheInvokeEntry e, ModifierArgs args) + throws IgniteCheckedException { e.remove(); } }; @@ -1028,19 +1044,59 @@ private ModifyingEntryProcessor(Object val, IgniteInClosure> { - /** Value to set. */ - private final Object val; - - /** */ - private EntryValueUpdater(Object val) { - assert val != null; - - this.val = val; + private static final class EntryValueUpdater extends EntryModifier { + /** Values to set - in order specified by {@link ModifierArgs#props}. */ + private final Object[] newColVals; + + /** + * @param newColVals Column values to set. + */ + private EntryValueUpdater(Object[] newColVals) { + this.newColVals = newColVals; } /** {@inheritDoc} */ - @Override public void apply(MutableEntry e) { + @Override public void applyx(CacheInvokeEntry e, ModifierArgs args) throws IgniteCheckedException { + assert e.exists(); + + GridCacheContext cctx = e.entry().context(); + + GridQueryTypeDescriptor typeDesc = cctx.grid().context().query().type(cctx.name(), args.typeName); + + // If we're here then value check has passed, so we can take old val as basis. + Object val = args.valSupplier != null ? args.valSupplier.apply(F.asList(newColVals)) : e.oldVal(); + + if (val == null) + throw new IgniteSQLException("New value for UPDATE must not be null", IgniteQueryErrorCode.NULL_VALUE); + + boolean hasProps = (args.valSupplier == null || newColVals.length > 1); + + if (cctx.binaryMarshaller() && hasProps && !(val instanceof BinaryObjectBuilder)) + val = cctx.grid().binary().builder(cctx.grid().binary().toBinary(val)); + + int i = 0; + + for (String propName : typeDesc.fields().keySet()) { + Integer idx = args.props.get(i++); + + if (idx == null) + continue; + + GridQueryProperty prop = typeDesc.property(propName); + + assert !prop.key(); + + Object colVal = newColVals[idx]; + + prop.setValue(null, val, colVal); + } + + if (cctx.binaryMarshaller() && hasProps) { + assert val instanceof BinaryObjectBuilder; + + val = ((BinaryObjectBuilder) val).build(); + } + e.setValue(val); } } 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 9bd1ecfc7bdf3..db16314d9a3f1 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 @@ -17,6 +17,7 @@ package org.apache.ignite.internal.processors.query.h2.dml; +import java.util.LinkedHashMap; import java.util.List; import org.apache.ignite.internal.processors.query.h2.opt.GridH2Table; import org.apache.ignite.internal.util.typedef.F; @@ -34,6 +35,9 @@ public final class UpdatePlan { /** Column names to set or update. */ public final String[] colNames; + /** Properties to set or update. */ + public final LinkedHashMap props; + /** Method to create key for INSERT or MERGE, ignored for UPDATE and DELETE. */ public final KeyValueSupplier keySupplier; @@ -63,10 +67,11 @@ public final class UpdatePlan { public final FastUpdateArguments fastUpdateArgs; /** */ - private UpdatePlan(UpdateMode mode, GridH2Table tbl, String[] colNames, KeyValueSupplier keySupplier, - KeyValueSupplier valSupplier, int keyColIdx, int valColIdx, String selectQry, boolean isLocSubqry, - Iterable> rows, int rowsNum, FastUpdateArguments fastUpdateArgs) { + private UpdatePlan(UpdateMode mode, GridH2Table tbl, String[] colNames, LinkedHashMap props, + KeyValueSupplier keySupplier, KeyValueSupplier valSupplier, int keyColIdx, int valColIdx, String selectQry, + boolean isLocSubqry, Iterable> rows, int rowsNum, FastUpdateArguments fastUpdateArgs) { this.colNames = colNames; + this.props = props; this.rows = rows; this.rowsNum = rowsNum; assert mode != null; @@ -84,44 +89,44 @@ private UpdatePlan(UpdateMode mode, GridH2Table tbl, String[] colNames, KeyValue } /** */ - public static UpdatePlan forMerge(GridH2Table tbl, String[] colNames, KeyValueSupplier keySupplier, - KeyValueSupplier valSupplier, int keyColIdx, int valColIdx, String selectQry, boolean isLocSubqry, - Iterable> rows, int rowsNum) { + public static UpdatePlan forMerge(GridH2Table tbl, String[] colNames, LinkedHashMap props, + KeyValueSupplier keySupplier, KeyValueSupplier valSupplier, int keyColIdx, int valColIdx, String selectQry, + boolean isLocSubqry, Iterable> rows, int rowsNum) { assert !F.isEmpty(colNames); - return new UpdatePlan(UpdateMode.MERGE, tbl, colNames, keySupplier, valSupplier, keyColIdx, valColIdx, + return new UpdatePlan(UpdateMode.MERGE, tbl, colNames, props, keySupplier, valSupplier, keyColIdx, valColIdx, selectQry, isLocSubqry, rows, rowsNum, null); } /** */ - public static UpdatePlan forInsert(GridH2Table tbl, String[] colNames, KeyValueSupplier keySupplier, - KeyValueSupplier valSupplier, int keyColIdx, int valColIdx, String selectQry, boolean isLocSubqry, - Iterable> rows, int rowsNum) { + public static UpdatePlan forInsert(GridH2Table tbl, String[] colNames, LinkedHashMap props, + KeyValueSupplier keySupplier, KeyValueSupplier valSupplier, int keyColIdx, int valColIdx, String selectQry, + boolean isLocSubqry, Iterable> rows, int rowsNum) { assert !F.isEmpty(colNames); - return new UpdatePlan(UpdateMode.INSERT, tbl, colNames, keySupplier, valSupplier, keyColIdx, valColIdx, + return new UpdatePlan(UpdateMode.INSERT, tbl, colNames, props, keySupplier, valSupplier, keyColIdx, valColIdx, selectQry, isLocSubqry, rows, rowsNum, null); } /** */ - public static UpdatePlan forUpdate(GridH2Table tbl, String[] colNames, KeyValueSupplier valSupplier, - int valColIdx, String selectQry) { - assert !F.isEmpty(colNames); + public static UpdatePlan forUpdate(GridH2Table tbl, String[] colNames, LinkedHashMap props, + KeyValueSupplier valSupplier, int valColIdx, String selectQry) { + assert !F.isEmpty(props); - return new UpdatePlan(UpdateMode.UPDATE, tbl, colNames, null, valSupplier, -1, valColIdx, selectQry, + return new UpdatePlan(UpdateMode.UPDATE, tbl, colNames, props, null, valSupplier, -1, valColIdx, selectQry, false, null, 0, null); } /** */ public static UpdatePlan forDelete(GridH2Table tbl, String selectQry) { - return new UpdatePlan(UpdateMode.DELETE, tbl, null, null, null, -1, -1, selectQry, false, null, 0, null); + return new UpdatePlan(UpdateMode.DELETE, tbl, null, null, null, null, -1, -1, selectQry, false, null, 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, null, 0, fastUpdateArgs); + return new UpdatePlan(mode, tbl, null, null, null, null, -1, -1, null, false, null, 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 3ff4c7655507f..396d553a58a8c 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 @@ -19,8 +19,12 @@ import java.lang.reflect.Constructor; import java.util.ArrayList; +import java.util.HashMap; import java.util.HashSet; +import java.util.LinkedHashMap; +import java.util.LinkedHashSet; import java.util.List; +import java.util.Map; import java.util.Set; import javax.cache.CacheException; import org.apache.ignite.IgniteCheckedException; @@ -198,6 +202,8 @@ else if (e instanceof GridSqlParameter) String[] colNames = new String[cols.length]; + Map propIdxs = new HashMap<>(cols.length); + for (int i = 0; i < cols.length; i++) { GridSqlColumn col = cols[i]; @@ -217,6 +223,8 @@ else if (e instanceof GridSqlParameter) GridQueryProperty prop = desc.type().property(colName); + propIdxs.put(prop.name(), i); + assert prop != null : "Property '" + colName + "' not found."; if (prop.key()) @@ -225,14 +233,27 @@ else if (e instanceof GridSqlParameter) hasValProps = true; } - KeyValueSupplier keySupplier = createSupplier(cctx, desc.type(), keyColIdx, hasKeyProps, true, false); - KeyValueSupplier valSupplier = createSupplier(cctx, desc.type(), valColIdx, hasValProps, false, false); + LinkedHashMap props = new LinkedHashMap<>(propIdxs.size()); + + int i = 0; + + for (String propName : desc.type().fields().keySet()) { + Integer idx = propIdxs.get(propName); + + if (idx != null) + props.put(i, idx); + + i++; + } + + KeyValueSupplier keySupplier = createSupplier(cctx, desc.type(), keyColIdx, hasKeyProps, true); + KeyValueSupplier valSupplier = createSupplier(cctx, desc.type(), valColIdx, hasValProps, false); if (stmt instanceof GridSqlMerge) - return UpdatePlan.forMerge(tbl.dataTable(), colNames, keySupplier, valSupplier, keyColIdx, + return UpdatePlan.forMerge(tbl.dataTable(), colNames, props, keySupplier, valSupplier, keyColIdx, valColIdx, sel != null ? sel.getSQL() : null, !isTwoStepSubqry, rows, rowsNum); else - return UpdatePlan.forInsert(tbl.dataTable(), colNames, keySupplier, valSupplier, keyColIdx, + return UpdatePlan.forInsert(tbl.dataTable(), colNames, props, keySupplier, valSupplier, keyColIdx, valColIdx, sel != null ? sel.getSQL() : null, !isTwoStepSubqry, rows, rowsNum); } @@ -292,11 +313,33 @@ else if (stmt instanceof GridSqlDelete) { String[] colNames = new String[updatedCols.size()]; + Map propIdxs = new HashMap<>(updatedCols.size()); + for (int i = 0; i < updatedCols.size(); i++) { - colNames[i] = updatedCols.get(i).columnName(); + String colName = updatedCols.get(i).columnName(); - if (VAL_FIELD_NAME.equals(colNames[i])) + colNames[i] = colName; + + if (VAL_FIELD_NAME.equals(colName)) { valColIdx = i; + + continue; + } + + propIdxs.put(desc.type().property(colName).name(), i); + } + + LinkedHashMap props = new LinkedHashMap<>(propIdxs.size()); + + int i = 0; + + for (String propName : desc.type().fields().keySet()) { + Integer idx = propIdxs.get(propName); + + if (idx != null) + props.put(i, idx); + + i++; } boolean hasNewVal = (valColIdx != -1); @@ -305,18 +348,12 @@ else if (stmt instanceof GridSqlDelete) { // or if its list of updated columns includes only _val, i.e. is single element. boolean hasProps = !hasNewVal || updatedCols.size() > 1; - // Index of new _val in results of SELECT - if (hasNewVal) - valColIdx += 2; - - int newValColIdx = (hasNewVal ? valColIdx : 1); - - KeyValueSupplier newValSupplier = createSupplier(desc.context(), desc.type(), newValColIdx, hasProps, - false, true); + KeyValueSupplier newValSupplier = hasNewVal ? createSupplier(desc.context(), desc.type(), valColIdx, + hasProps, false) : null; sel = DmlAstUtils.selectForUpdate((GridSqlUpdate) stmt, errKeysPos); - return UpdatePlan.forUpdate(gridTbl, colNames, newValSupplier, valColIdx, sel.getSQL()); + return UpdatePlan.forUpdate(gridTbl, colNames, props, newValSupplier, valColIdx, sel.getSQL()); } else { sel = DmlAstUtils.selectForDelete((GridSqlDelete) stmt, errKeysPos); @@ -340,7 +377,7 @@ else if (stmt instanceof GridSqlDelete) { */ @SuppressWarnings({"ConstantConditions", "unchecked"}) private static KeyValueSupplier createSupplier(final GridCacheContext cctx, GridQueryTypeDescriptor desc, - final int colIdx, boolean hasProps, final boolean key, boolean forUpdate) throws IgniteCheckedException { + final int colIdx, boolean hasProps, final boolean key) throws IgniteCheckedException { final String typeName = key ? desc.keyTypeName() : desc.valueTypeName(); //Try to find class for the key locally. @@ -386,25 +423,8 @@ else if (isSqlType) } } 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); - } + if (colIdx != -1) + return new PlainValueSupplier(colIdx); Constructor ctor; From 8c785ccc6c3b4fde3c6580fab7f5f5c0f8d3a154 Mon Sep 17 00:00:00 2001 From: Alexander Paschenko Date: Wed, 28 Dec 2016 11:54:51 +0300 Subject: [PATCH 07/12] Handle Dates correctly when parsing Date string without query --- .../processors/query/h2/DmlStatementsProcessor.java | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) 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 f660148dce238..70bd2bd241683 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 @@ -680,7 +680,14 @@ private static Object convert(Object val, Class expCls, GridH2RowDescriptor d Value h2Val = desc.wrap(val, objType); - return h2Val.convertTo(type).getObject(); + Object res = h2Val.convertTo(type).getObject(); + + if (res instanceof Date && res.getClass() != Date.class && expCls == Date.class) { + // We can get a Timestamp instead of Date when converting a String to Date without query - let's handle this + return new Date(((Date) res).getTime()); + } + + return res; } /** From cebfe59ddca606942f1d9843d92f99c76c804fab Mon Sep 17 00:00:00 2001 From: Alexander Paschenko Date: Wed, 28 Dec 2016 12:01:47 +0300 Subject: [PATCH 08/12] Correct missing args handling --- .../processors/query/h2/DmlStatementsProcessor.java | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) 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 daaa445474a6d..5288df3e4caf1 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 @@ -993,10 +993,13 @@ private ModifyingEntryProcessor(Object val, EntryModifier entryModifier) { if (!(entry instanceof CacheInvokeEntry)) throw new EntryProcessorException("Unexpected mutable entry type - CacheInvokeEntry expected"); - assert F.isEmpty(arguments) || (arguments[0] != null && arguments[0] instanceof ModifierArgs); + boolean hasArgs = !F.isEmpty(arguments); + + assert !hasArgs || (arguments[0] != null && arguments[0] instanceof ModifierArgs); try { - entryModifier.apply((CacheInvokeEntry) entry, (ModifierArgs) arguments[0]); + entryModifier.apply((CacheInvokeEntry) entry, + hasArgs ? (ModifierArgs) arguments[0] : null); } catch (Exception e) { throw new EntryProcessorException(e); From 600be2be7a8c86ca1873ee75bd273447957a8a0d Mon Sep 17 00:00:00 2001 From: Alexander Paschenko Date: Thu, 29 Dec 2016 15:29:46 +0300 Subject: [PATCH 09/12] IGNITE-4489 Better distributed INSERT and MERGE --- .../query/h2/DmlStatementsProcessor.java | 403 +++++++++++++++--- .../processors/query/h2/dml/UpdatePlan.java | 10 +- .../query/h2/dml/UpdatePlanBuilder.java | 25 +- 3 files changed, 376 insertions(+), 62 deletions(-) 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 5288df3e4caf1..acdcb99a582ae 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,6 +17,7 @@ package org.apache.ignite.internal.processors.query.h2; +import java.io.Serializable; import java.lang.reflect.Array; import java.sql.PreparedStatement; import java.sql.SQLException; @@ -41,6 +42,7 @@ import org.apache.ignite.IgniteCache; import org.apache.ignite.IgniteCheckedException; import org.apache.ignite.IgniteException; +import org.apache.ignite.IgniteLogger; import org.apache.ignite.binary.BinaryArrayIdentityResolver; import org.apache.ignite.binary.BinaryObject; import org.apache.ignite.binary.BinaryObjectBuilder; @@ -56,12 +58,13 @@ import org.apache.ignite.internal.processors.query.GridQueryFieldMetadata; import org.apache.ignite.internal.processors.query.GridQueryFieldsResult; import org.apache.ignite.internal.processors.query.GridQueryFieldsResultAdapter; +import org.apache.ignite.internal.processors.query.GridQueryProcessor; 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.FastUpdateArgument; 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.UpdateMode; 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; @@ -95,6 +98,9 @@ public class DmlStatementsProcessor { /** Indexing. */ private final IgniteH2Indexing indexing; + /** Logger. */ + private static IgniteLogger log; + /** Set of binary type ids for which warning about missing identity in configuration has been printed. */ private final static Set WARNED_TYPES = Collections.newSetFromMap(new ConcurrentHashMap8()); @@ -114,6 +120,10 @@ public class DmlStatementsProcessor { */ DmlStatementsProcessor(IgniteH2Indexing indexing) { this.indexing = indexing; + + assert log == null; + + log = indexing.getLogger(); } /** @@ -311,7 +321,10 @@ else if (F.isEmpty(plan.rows)) { switch (plan.mode) { case MERGE: - return new UpdateResult(doMerge(plan, cur, pageSize), X.EMPTY_OBJECT_ARRAY); + if (plan.valColIdx != -1) // _val column is given explicitly - we can just call 'put' directly + return new UpdateResult(doSimpleMerge(plan, cur, pageSize), X.EMPTY_OBJECT_ARRAY); + else // We have to use entry processors + return new UpdateResult(doComplexMerge(plan, cur, pageSize), X.EMPTY_OBJECT_ARRAY); case INSERT: return new UpdateResult(doInsert(plan, cur, pageSize), X.EMPTY_OBJECT_ARRAY); @@ -501,7 +514,13 @@ private UpdateResult doUpdate(UpdatePlan plan, Iterable> cursor, int pag Iterator> it = cursor.iterator(); - ModifierArgs args = new ModifierArgs(desc.type().name(), plan.props, plan.valSupplier); + ModifierArgs args = new ModifierArgs(desc.type().name(), plan.props); + + int valColIdx = plan.valColIdx; + + // Actual position of new _val in args' array + if (valColIdx != -1) + valColIdx -= 2; while (it.hasNext()) { List e = it.next(); @@ -513,13 +532,18 @@ private UpdateResult doUpdate(UpdatePlan plan, Iterable> cursor, int pag Object[] newColVals = e.subList(2, propNames.length + 2).toArray(); for (int i = 0; i < propNames.length; i++) - if (i != plan.valColIdx) + if (i != valColIdx) newColVals[i] = convert(newColVals[i], propNames[i], desc); if (bin && !(srcVal instanceof BinaryObject)) srcVal = cctx.grid().binary().toBinary(srcVal); - rows.put(key, new ModifyingEntryProcessor(srcVal, new EntryValueUpdater(newColVals))); + Object newVal = null; + + if (plan.valSupplier != null) + newVal = plan.valSupplier.apply(e); + + rows.put(key, new ModifyingEntryProcessor(srcVal, new UpdateModifier(newColVals, newVal))); if ((pageSize > 0 && rows.size() == pageSize) || (!it.hasNext())) { PageProcessingResult pageRes = processPage(cctx, rows, args); @@ -557,7 +581,6 @@ private UpdateResult doUpdate(UpdatePlan plan, Iterable> cursor, int pag throw new IgniteSQLException(resEx); } - return new UpdateResult(res, failedKeys.toArray()); } @@ -694,15 +717,14 @@ private static PageProcessingErrorResult splitErrors(Map> cursor, int pageSize) throws IgniteCheckedException { + private long doSimpleMerge(UpdatePlan plan, Iterable> cursor, int pageSize) throws IgniteCheckedException { GridH2RowDescriptor desc = plan.tbl.rowDescriptor(); GridCacheContext cctx = desc.context(); // If we have just one item to put, just do so if (plan.rowsNum == 1) { - IgniteBiTuple t = rowToKeyValue(cctx, cursor.iterator().next(), - plan); + IgniteBiTuple t = rowToKeyValue(cctx, cursor.iterator().next(), plan); cctx.cache().put(t.getKey(), t.getValue()); return 1; @@ -731,6 +753,55 @@ private long doMerge(UpdatePlan plan, Iterable> cursor, int pageSize) th } } + /** + * Perform entry processors based MERGE. + * @param plan Update plan. + * @param cur Cursor with rows to process. + * @param pageSize Page size. + * @return Number of affected items. + * @throws IgniteCheckedException if failed. + */ + @SuppressWarnings("unchecked") + private long doComplexMerge(UpdatePlan plan, Iterable> cur, int pageSize) throws IgniteCheckedException { + assert plan.valColIdx == -1; + + GridH2RowDescriptor desc = plan.tbl.rowDescriptor(); + + GridCacheContext cctx = desc.context(); + + ModifierArgs args = new ModifierArgs(desc.type().name(), plan.props); + + // If we have just one item to process, just do so + if (plan.rowsNum == 1) { + IgniteBiTuple t = rowToInsertProc(cctx, cur.iterator().next(), plan); + + cctx.cache().invoke(t.get1(), t.get2(), args); + return 1; + } + else { + int resCnt = 0; + Map rows = new LinkedHashMap<>(); + + for (Iterator> it = cur.iterator(); it.hasNext();) { + List row = it.next(); + + IgniteBiTuple t = rowToInsertProc(cctx, row, plan); + + rows.put(t.getKey(), t.getValue()); + + if ((pageSize > 0 && rows.size() == pageSize) || !it.hasNext()) { + cctx.cache().invokeAll(rows, args); + resCnt += rows.size(); + + if (it.hasNext()) + rows.clear(); + } + } + + return resCnt; + } + } + /** * Execute INSERT statement plan. * @param cursor Cursor to take inserted data from. @@ -755,6 +826,8 @@ private long doInsert(UpdatePlan plan, Iterable> cursor, int pageSize) t IgniteQueryErrorCode.DUPLICATE_KEY); } else { + ModifierArgs args = new ModifierArgs(desc.type().name(), plan.props); + Map> rows = plan.isLocSubqry ? new LinkedHashMap>(plan.rowsNum) : new LinkedHashMap>(); @@ -771,12 +844,12 @@ private long doInsert(UpdatePlan plan, Iterable> cursor, int pageSize) t while (it.hasNext()) { List row = it.next(); - final IgniteBiTuple t = rowToKeyValue(cctx, row, plan); + final IgniteBiTuple t = rowToInsertProc(cctx, row, plan); - rows.put(t.getKey(), new InsertEntryProcessor(t.getValue())); + rows.put(t.getKey(), t.getValue()); if (!it.hasNext() || (pageSize > 0 && rows.size() == pageSize)) { - PageProcessingResult pageRes = processPage(cctx, rows); + PageProcessingResult pageRes = processPage(cctx, rows, args); resCnt += pageRes.cnt; @@ -837,6 +910,8 @@ private static PageProcessingResult processPage(GridCacheContext cctx, /** * Convert row presented as an array of Objects into key-value pair to be inserted to cache. + * To be used in simple MERGE cases - i.e. when _val column value is given, hence we can perform + * cache puts directly, without entry processors. * @param cctx Cache context. * @param row Row to process. * @param plan Update plan. @@ -898,16 +973,74 @@ private static PageProcessingResult processPage(GridCacheContext cctx, return new IgniteBiTuple<>(key, val); } - private static Object[] rowToPropValues(List row, LinkedHashMap props) { - Object[] res = new Object[props.size()]; + /** + * Create key and entry processor to perform atomic MERGE or INSERT (based on {@link UpdatePlan#mode}). + * @param cctx Cache context. + * @param row Column values to set. + * @param plan Update plan. + * @return Tuple [key; {@link MergeModifier} for given column values] + * @throws IgniteCheckedException if failed. + */ + private IgniteBiTuple rowToInsertProc(GridCacheContext cctx, List row, + UpdatePlan plan) throws IgniteCheckedException { + assert plan.mode == UpdateMode.INSERT || (plan.mode == UpdateMode.MERGE && plan.valColIdx == -1); + + Object key = plan.keySupplier.apply(row); + + if (key == null) + throw new IgniteSQLException("Key for INSERT or MERGE must not be null", IgniteQueryErrorCode.NULL_KEY); + + Object val = (plan.valSupplier != null ? plan.valSupplier.apply(row) : null); + + GridQueryTypeDescriptor desc = plan.tbl.rowDescriptor().type(); - for (Map.Entry e : props.entrySet()) { - assert res[e.getValue()] == null; + // This array will have nulls at positions corresponding to key fields. + Object[] newColVals = new Object[plan.colNames.length]; - res[e.getValue()] = row.get(e.getKey()); + int i = 0; + + for (String propName : desc.fields().keySet()) { + Integer idx = plan.props.get(i++); + + if (idx == null) + continue; + + GridQueryProperty prop = desc.property(propName); + + Object v = convert(row.get(idx), prop.type(), plan.tbl.rowDescriptor()); + + if (prop.key()) + prop.setValue(key, null, v); + else + newColVals[idx] = v; } - return res; + if (cctx.binaryMarshaller()) { + if (key instanceof BinaryObjectBuilder) + key = ((BinaryObjectBuilder) key).build(); + + if (key instanceof BinaryObject) + key = updateHashCodeIfNeeded(cctx, (BinaryObject) key); + } + + return new IgniteBiTuple<>(key, (EntryProcessor) (plan.mode == UpdateMode.MERGE ? new MergeModifier(newColVals, val) : + new InsertModifier(newColVals, val))); + } + + /** + * Optionally wrap an object into {@link BinaryObjectBuilder}. + * + * @param cctx Cache context. + * @param val Value to wrap. + * @return {@code val} or {@link BinaryObjectBuilder} wrapping it. + */ + private static Object toBuilderIfNeeded(GridCacheContext cctx, Object val) { + if (val == null || !cctx.binaryMarshaller() || GridQueryProcessor.isSqlType(val.getClass())) + return val; + + BinaryObject binVal = cctx.grid().binary().toBinary(val); + + return binVal.toBuilder(); } /** @@ -917,10 +1050,10 @@ private static Object[] rowToPropValues(List row, LinkedHashMap { - /** Value to set. */ - private final Object val; + /** + * Entry processor that performs atomic MERGE/UPSERT on an entry. + */ + private static class MergeModifier extends EntryModifier implements EntryProcessor { + /** New values for properties as enlisted in initial query (w/o key and its properties, as well as _val). */ + private final Object[] newColVals; + + /** New _val, if present in initial query. */ + private final Object newVal; /** */ - private InsertEntryProcessor(Object val) { - this.val = val; + private MergeModifier(Object[] newColVals, Object newVal) { + // This processor must be used only when no _val is given explicitly, + // hence new values must be properties. + assert !F.isEmpty(newColVals); + + this.newColVals = newColVals; + this.newVal = newVal; } /** {@inheritDoc} */ - @Override public Boolean process(MutableEntry entry, Object... arguments) throws EntryProcessorException { - if (entry.exists()) + @Override public Boolean process(MutableEntry e, Object... args) throws EntryProcessorException { + if (!(e instanceof CacheInvokeEntry)) + throw new EntryProcessorException("Unexpected mutable entry type - CacheInvokeEntry expected"); + + assert !F.isEmpty(args) && args[0] instanceof ModifierArgs; + + try { + applyx((CacheInvokeEntry) e, (ModifierArgs) args[0]); + } + catch (IgniteCheckedException ex) { + throw new EntryProcessorException(ex); + } + + return null; + } + + /** {@inheritDoc} */ + @Override public void applyx(CacheInvokeEntry e, ModifierArgs args) throws IgniteCheckedException { + GridCacheContext cctx = e.entry().context(); + + GridQueryTypeDescriptor typeDesc = cctx.grid().context().query().type(cctx.name(), args.typeName); + + // This processor must be used only when no _val is given explicitly, and it's impossible + // when value is of SQL type. + assert !GridQueryProcessor.isSqlType(typeDesc.valueClass()); + + Object val = null; + + if (e.exists()) { + val = e.getValue(); + + if (!cctx.binaryMarshaller()) { + if (!cctx.cache().configuration().isCopyOnRead()) { + byte[] valBytes = cctx.marshaller().marshal(val); + + // val is another object now, we can mutate it + val = cctx.marshaller().unmarshal(valBytes, U.resolveClassLoader(cctx.gridConfig())); + } + } + else { + val = cctx.grid().binary().toBinary(val); + + assert val instanceof BinaryObject; + + val = ((BinaryObject) val).toBuilder(); + } + } + else if (newVal != null) + val = toBuilderIfNeeded(cctx, newVal); + else if (cctx.binaryMarshaller()) + val = cctx.grid().binary().builder(typeDesc.valueTypeName()); + + if (val == null) + throw new IgniteSQLException("Value for MERGE must not be null", IgniteQueryErrorCode.NULL_VALUE); + + int i = 0; + + for (String propName : typeDesc.fields().keySet()) { + Integer idx = args.props.get(i++); + + if (idx == null) + continue; + + GridQueryProperty prop = typeDesc.property(propName); + + if (prop.key()) + continue; + + prop.setValue(null, val, newColVals[idx]); + } + + if (cctx.binaryMarshaller() && val instanceof BinaryObjectBuilder) { + val = ((BinaryObjectBuilder) val).build(); + + val = updateHashCodeIfNeeded(cctx, (BinaryObject) val); + } + + e.setValue(val); + } + } + + /** + * Entry processor that performs atomic MERGE/UPSERT on an entry. + */ + private static class InsertModifier extends EntryModifier implements EntryProcessor { + /** New values for properties as enlisted in initial query (w/o key and its properties, as well as _val). */ + private final Object[] newColVals; + + /** New _val, if present in initial query. */ + private final Object newVal; + + /** */ + private InsertModifier(Object[] newColVals, Object newVal) { + this.newColVals = newColVals; + this.newVal = newVal; + } + + /** {@inheritDoc} */ + @Override public Boolean process(MutableEntry e, Object... args) throws EntryProcessorException { + if (e.exists()) return false; - entry.setValue(val); - return null; // To leave out only erroneous keys - nulls are skipped on results' processing. + if (!(e instanceof CacheInvokeEntry)) + throw new EntryProcessorException("Unexpected mutable entry type - CacheInvokeEntry expected"); + + assert !F.isEmpty(args) && args[0] instanceof ModifierArgs; + + try { + applyx((CacheInvokeEntry) e, (ModifierArgs) args[0]); + } + catch (IgniteCheckedException ex) { + throw new EntryProcessorException(ex); + } + + return null; + } + + /** {@inheritDoc} */ + @Override public void applyx(CacheInvokeEntry e, ModifierArgs args) throws IgniteCheckedException { + GridCacheContext cctx = e.entry().context(); + + GridQueryTypeDescriptor typeDesc = cctx.grid().context().query().type(cctx.name(), args.typeName); + + Object val = null; + + if (newVal != null) + val = !F.isEmpty(newColVals) ? toBuilderIfNeeded(cctx, newVal) : newVal; + else if (cctx.binaryMarshaller()) // For non binary mode, newVal must be supplied. + val = cctx.grid().binary().builder(typeDesc.valueTypeName()); + + if (val == null) + throw new IgniteSQLException("Value for INSERT or MERGE must not be null", + IgniteQueryErrorCode.NULL_VALUE); + + int i = 0; + + if (!F.isEmpty(newColVals)) + for (String propName : typeDesc.fields().keySet()) { + Integer idx = args.props.get(i++); + + if (idx == null) + continue; + + GridQueryProperty prop = typeDesc.property(propName); + + if (prop.key()) + continue; + + prop.setValue(null, val, newColVals[idx]); + } + + if (cctx.binaryMarshaller() && newVal == null) { + val = ((BinaryObjectBuilder) val).build(); + + val = updateHashCodeIfNeeded(cctx, (BinaryObject) val); + } + + e.setValue(val); } } /** * Entry processor invoked by UPDATE and DELETE operations. */ - private final static class ModifyingEntryProcessor implements EntryProcessor { + private final static class ModifyingEntryProcessor implements EntryProcessor, Serializable { + /** */ + private static final long serialVersionUID = 0L; + /** Value to expect. */ private final Object val; @@ -1012,27 +1310,25 @@ private ModifyingEntryProcessor(Object val, EntryModifier entryModifier) { /** * Arguments for {@link EntryModifier}. */ - private final static class ModifierArgs { + private final static class ModifierArgs implements Serializable { + /** */ + private static final long serialVersionUID = 0L; + /** * Target type descriptor name. */ private final String typeName; /** - * + * Same as {@link UpdatePlan#props} - map from property indexes in order defined by type descriptor + * to their positions in array of new property values. */ private final LinkedHashMap props; - /** - * Value supplier. - */ - private final KeyValueSupplier valSupplier; - /** */ - private ModifierArgs(String typeName, LinkedHashMap props, KeyValueSupplier valSupplier) { + private ModifierArgs(String typeName, LinkedHashMap props) { this.typeName = typeName; this.props = props; - this.valSupplier = valSupplier; } } @@ -1047,6 +1343,8 @@ private abstract static class EntryModifier extends CIX2 e, ModifierArgs args) throws IgniteCheckedException { + assert e.exists(); + e.remove(); } }; @@ -1054,15 +1352,17 @@ private abstract static class EntryModifier extends CIX2 1); - boolean hasProps = (args.valSupplier == null || newColVals.length > 1); + val = hasProps ? toBuilderIfNeeded(cctx, val) : val; - if (cctx.binaryMarshaller() && hasProps && !(val instanceof BinaryObjectBuilder)) - val = cctx.grid().binary().builder(cctx.grid().binary().toBinary(val)); + if (val == null) + throw new IgniteSQLException("New value for UPDATE must not be null", IgniteQueryErrorCode.NULL_VALUE); int i = 0; @@ -1105,6 +1404,8 @@ private EntryValueUpdater(Object[] newColVals) { assert val instanceof BinaryObjectBuilder; val = ((BinaryObjectBuilder) val).build(); + + val = updateHashCodeIfNeeded(cctx, (BinaryObject) val); } e.setValue(val); 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 db16314d9a3f1..dc6e252f2081f 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 @@ -19,6 +19,7 @@ import java.util.LinkedHashMap; import java.util.List; +import org.apache.ignite.internal.processors.query.GridQueryTypeDescriptor; import org.apache.ignite.internal.processors.query.h2.opt.GridH2Table; import org.apache.ignite.internal.util.typedef.F; @@ -35,7 +36,12 @@ public final class UpdatePlan { /** Column names to set or update. */ public final String[] colNames; - /** Properties to set or update. */ + /** + * Properties to set or update.

+ * Keys in this map correspond to indices when iterating fields in {@link GridQueryTypeDescriptor} + * (which are ordered, and order is important) and values in this map correspond to indices of columns + * that contain new values for these properties in {@link #selectQry}. + */ public final LinkedHashMap props; /** Method to create key for INSERT or MERGE, ignored for UPDATE and DELETE. */ @@ -57,7 +63,7 @@ public final class UpdatePlan { /** Subquery flag - {@code true} if {@link #selectQry} is an actual subquery that retrieves data from some cache. */ public final boolean isLocSubqry; - /** */ + /** When INSERT or MERGE is not query based, this iterable represents rows given in initial query. */ public final Iterable> rows; /** Number of rows in rows based MERGE or INSERT. */ 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 396d553a58a8c..de37ea24f0bda 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 @@ -22,7 +22,6 @@ import java.util.HashMap; import java.util.HashSet; import java.util.LinkedHashMap; -import java.util.LinkedHashSet; import java.util.List; import java.util.Map; import java.util.Set; @@ -160,7 +159,7 @@ else if (stmt instanceof GridSqlMerge) { rowsNum = isTwoStepSubqry ? 0 : merge.rows().size(); } else throw new IgniteSQLException("Unexpected DML operation [cls=" + stmt.getClass().getName() + ']', - IgniteQueryErrorCode.UNEXPECTED_OPERATION); + IgniteQueryErrorCode.UNEXPECTED_OPERATION); if (elRows != null) { assert sel == null; @@ -246,8 +245,11 @@ else if (e instanceof GridSqlParameter) i++; } - 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, true); + + // Buildable val supplier is either for direct put based MERGE or for single row INSERT + KeyValueSupplier valSupplier = createSupplier(cctx, desc.type(), valColIdx, hasValProps, false, + (stmt instanceof GridSqlMerge && valColIdx != -1) || (stmt instanceof GridSqlInsert && rowsNum == 1)); if (stmt instanceof GridSqlMerge) return UpdatePlan.forMerge(tbl.dataTable(), colNames, props, keySupplier, valSupplier, keyColIdx, @@ -344,12 +346,17 @@ else if (stmt instanceof GridSqlDelete) { boolean hasNewVal = (valColIdx != -1); + // Actual position of new _val in SELECTed row + if (hasNewVal) + valColIdx += 2; + // Statement updates distinct properties if it does not have _val in updated columns list // or if its list of updated columns includes only _val, i.e. is single element. boolean hasProps = !hasNewVal || updatedCols.size() > 1; + // Don't pass suppliers other than those that simply take given explicit value of _val column. KeyValueSupplier newValSupplier = hasNewVal ? createSupplier(desc.context(), desc.type(), valColIdx, - hasProps, false) : null; + hasProps, false, false) : null; sel = DmlAstUtils.selectForUpdate((GridSqlUpdate) stmt, errKeysPos); @@ -377,7 +384,7 @@ else if (stmt instanceof GridSqlDelete) { */ @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, final boolean buildable) throws IgniteCheckedException { final String typeName = key ? desc.keyTypeName() : desc.valueTypeName(); //Try to find class for the key locally. @@ -408,10 +415,12 @@ else if (isSqlType) BinaryObject bin = cctx.grid().binary().toBinary(obj); - return cctx.grid().binary().builder(bin); + return buildable ? bin.toBuilder() : bin; } }; } + else if (!buildable) + return null; else { // ...and if we don't, just create a new builder. return new KeyValueSupplier() { @@ -471,8 +480,6 @@ else if (isSqlType) } } - - /** * @param target Expression to extract the table from. * @return Back end table for this element. From ebe9c324c589f46455c2c57f4fc9c14de9caef27 Mon Sep 17 00:00:00 2001 From: Alexander Paschenko Date: Tue, 10 Jan 2017 03:44:52 +0800 Subject: [PATCH 10/12] IGNITE-4489 DmlStatementsProcessor refactoring for better maintainability (moved code to separate classes) + test --- .../query/h2/DmlStatementsProcessor.java | 379 +----------------- .../query/h2/dml/DmlEntryProcessor.java | 29 ++ .../query/h2/dml/DmlEntryProcessorArgs.java | 29 ++ .../query/h2/dml/InsertProcessor.java | 97 +++++ .../query/h2/dml/MergeProcessor.java | 121 ++++++ .../query/h2/dml/ModifyingEntryProcessor.java | 64 +++ .../query/h2/dml/UpdateProcessor.java | 76 ++++ .../IgniteCacheMergeSqlQuerySelfTest.java | 19 + 8 files changed, 448 insertions(+), 366 deletions(-) create mode 100644 modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/dml/DmlEntryProcessor.java create mode 100644 modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/dml/DmlEntryProcessorArgs.java create mode 100644 modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/dml/InsertProcessor.java create mode 100644 modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/dml/MergeProcessor.java create mode 100644 modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/dml/ModifyingEntryProcessor.java create mode 100644 modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/dml/UpdateProcessor.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 acdcb99a582ae..746833bf0d46f 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,7 +17,6 @@ package org.apache.ignite.internal.processors.query.h2; -import java.io.Serializable; import java.lang.reflect.Array; import java.sql.PreparedStatement; import java.sql.SQLException; @@ -38,7 +37,7 @@ import javax.cache.processor.EntryProcessor; import javax.cache.processor.EntryProcessorException; import javax.cache.processor.EntryProcessorResult; -import javax.cache.processor.MutableEntry; + import org.apache.ignite.IgniteCache; import org.apache.ignite.IgniteCheckedException; import org.apache.ignite.IgniteException; @@ -58,20 +57,14 @@ import org.apache.ignite.internal.processors.query.GridQueryFieldMetadata; import org.apache.ignite.internal.processors.query.GridQueryFieldsResult; import org.apache.ignite.internal.processors.query.GridQueryFieldsResultAdapter; -import org.apache.ignite.internal.processors.query.GridQueryProcessor; 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.FastUpdateArgument; -import org.apache.ignite.internal.processors.query.h2.dml.FastUpdateArguments; -import org.apache.ignite.internal.processors.query.h2.dml.UpdateMode; -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.dml.*; import org.apache.ignite.internal.processors.query.h2.opt.GridH2RowDescriptor; import org.apache.ignite.internal.processors.query.h2.sql.GridSqlQueryParser; import org.apache.ignite.internal.util.GridBoundedConcurrentLinkedHashMap; import org.apache.ignite.internal.util.lang.IgniteSingletonIterator; -import org.apache.ignite.internal.util.typedef.CIX2; import org.apache.ignite.internal.util.typedef.F; import org.apache.ignite.internal.util.typedef.X; import org.apache.ignite.internal.util.typedef.internal.U; @@ -230,7 +223,7 @@ GridQueryFieldsResult updateLocalSqlFields(String spaceName, PreparedStatement s * @return Pair [number of successfully processed items; keys that have failed to be processed] * @throws IgniteCheckedException if failed. */ - @SuppressWarnings("ConstantConditions") + @SuppressWarnings({"ConstantConditions", "unchecked"}) private UpdateResult executeUpdateStatement(final GridCacheContext cctx, PreparedStatement prepStmt, SqlFieldsQuery fieldsQry, boolean loc, IndexingQueryFilter filters, GridQueryCancel cancel, Object[] failedKeys) throws IgniteCheckedException { @@ -514,7 +507,7 @@ private UpdateResult doUpdate(UpdatePlan plan, Iterable> cursor, int pag Iterator> it = cursor.iterator(); - ModifierArgs args = new ModifierArgs(desc.type().name(), plan.props); + DmlEntryProcessorArgs args = new DmlEntryProcessorArgs(desc.type().name(), plan.props); int valColIdx = plan.valColIdx; @@ -543,7 +536,7 @@ private UpdateResult doUpdate(UpdatePlan plan, Iterable> cursor, int pag if (plan.valSupplier != null) newVal = plan.valSupplier.apply(e); - rows.put(key, new ModifyingEntryProcessor(srcVal, new UpdateModifier(newColVals, newVal))); + rows.put(key, new ModifyingEntryProcessor(srcVal, new UpdateProcessor(newColVals, newVal))); if ((pageSize > 0 && rows.size() == pageSize) || (!it.hasNext())) { PageProcessingResult pageRes = processPage(cctx, rows, args); @@ -769,7 +762,7 @@ private long doComplexMerge(UpdatePlan plan, Iterable> cur, int pageSize GridCacheContext cctx = desc.context(); - ModifierArgs args = new ModifierArgs(desc.type().name(), plan.props); + DmlEntryProcessorArgs args = new DmlEntryProcessorArgs(desc.type().name(), plan.props); // If we have just one item to process, just do so if (plan.rowsNum == 1) { @@ -826,7 +819,7 @@ private long doInsert(UpdatePlan plan, Iterable> cursor, int pageSize) t IgniteQueryErrorCode.DUPLICATE_KEY); } else { - ModifierArgs args = new ModifierArgs(desc.type().name(), plan.props); + DmlEntryProcessorArgs args = new DmlEntryProcessorArgs(desc.type().name(), plan.props); Map> rows = plan.isLocSubqry ? new LinkedHashMap>(plan.rowsNum) : @@ -978,7 +971,7 @@ private static PageProcessingResult processPage(GridCacheContext cctx, * @param cctx Cache context. * @param row Column values to set. * @param plan Update plan. - * @return Tuple [key; {@link MergeModifier} for given column values] + * @return Tuple [key; {@link MergeProcessor} for given column values] * @throws IgniteCheckedException if failed. */ private IgniteBiTuple rowToInsertProc(GridCacheContext cctx, List row, @@ -1023,24 +1016,8 @@ private IgniteBiTuple rowToInsertProc(GridCacheContext c key = updateHashCodeIfNeeded(cctx, (BinaryObject) key); } - return new IgniteBiTuple<>(key, (EntryProcessor) (plan.mode == UpdateMode.MERGE ? new MergeModifier(newColVals, val) : - new InsertModifier(newColVals, val))); - } - - /** - * Optionally wrap an object into {@link BinaryObjectBuilder}. - * - * @param cctx Cache context. - * @param val Value to wrap. - * @return {@code val} or {@link BinaryObjectBuilder} wrapping it. - */ - private static Object toBuilderIfNeeded(GridCacheContext cctx, Object val) { - if (val == null || !cctx.binaryMarshaller() || GridQueryProcessor.isSqlType(val.getClass())) - return val; - - BinaryObject binVal = cctx.grid().binary().toBinary(val); - - return binVal.toBuilder(); + return new IgniteBiTuple<>(key, (EntryProcessor) (plan.mode == UpdateMode.MERGE ? new MergeProcessor(newColVals, val) : + new InsertProcessor(newColVals, val))); } /** @@ -1050,7 +1027,7 @@ private static Object toBuilderIfNeeded(GridCacheContext cctx, Object val) { * @param binObj Binary object. * @return Binary object with hash code set. */ - private static BinaryObject updateHashCodeIfNeeded(GridCacheContext cctx, BinaryObject binObj) { + public static BinaryObject updateHashCodeIfNeeded(GridCacheContext cctx, BinaryObject binObj) { if (U.isHashCodeEmpty(binObj)) { if (WARNED_TYPES.add(binObj.type().typeId())) U.warn(log, "Binary object's type does not have identity resolver explicitly set, therefore " + @@ -1071,277 +1048,10 @@ private static BinaryObject updateHashCodeIfNeeded(GridCacheContext cctx, Binary return binObj; } - /** - * Entry processor that performs atomic MERGE/UPSERT on an entry. - */ - private static class MergeModifier extends EntryModifier implements EntryProcessor { - /** New values for properties as enlisted in initial query (w/o key and its properties, as well as _val). */ - private final Object[] newColVals; - - /** New _val, if present in initial query. */ - private final Object newVal; - - /** */ - private MergeModifier(Object[] newColVals, Object newVal) { - // This processor must be used only when no _val is given explicitly, - // hence new values must be properties. - assert !F.isEmpty(newColVals); - - this.newColVals = newColVals; - this.newVal = newVal; - } - - /** {@inheritDoc} */ - @Override public Boolean process(MutableEntry e, Object... args) throws EntryProcessorException { - if (!(e instanceof CacheInvokeEntry)) - throw new EntryProcessorException("Unexpected mutable entry type - CacheInvokeEntry expected"); - - assert !F.isEmpty(args) && args[0] instanceof ModifierArgs; - - try { - applyx((CacheInvokeEntry) e, (ModifierArgs) args[0]); - } - catch (IgniteCheckedException ex) { - throw new EntryProcessorException(ex); - } - - return null; - } - - /** {@inheritDoc} */ - @Override public void applyx(CacheInvokeEntry e, ModifierArgs args) throws IgniteCheckedException { - GridCacheContext cctx = e.entry().context(); - - GridQueryTypeDescriptor typeDesc = cctx.grid().context().query().type(cctx.name(), args.typeName); - - // This processor must be used only when no _val is given explicitly, and it's impossible - // when value is of SQL type. - assert !GridQueryProcessor.isSqlType(typeDesc.valueClass()); - - Object val = null; - - if (e.exists()) { - val = e.getValue(); - - if (!cctx.binaryMarshaller()) { - if (!cctx.cache().configuration().isCopyOnRead()) { - byte[] valBytes = cctx.marshaller().marshal(val); - - // val is another object now, we can mutate it - val = cctx.marshaller().unmarshal(valBytes, U.resolveClassLoader(cctx.gridConfig())); - } - } - else { - val = cctx.grid().binary().toBinary(val); - - assert val instanceof BinaryObject; - - val = ((BinaryObject) val).toBuilder(); - } - } - else if (newVal != null) - val = toBuilderIfNeeded(cctx, newVal); - else if (cctx.binaryMarshaller()) - val = cctx.grid().binary().builder(typeDesc.valueTypeName()); - - if (val == null) - throw new IgniteSQLException("Value for MERGE must not be null", IgniteQueryErrorCode.NULL_VALUE); - - int i = 0; - - for (String propName : typeDesc.fields().keySet()) { - Integer idx = args.props.get(i++); - - if (idx == null) - continue; - - GridQueryProperty prop = typeDesc.property(propName); - - if (prop.key()) - continue; - - prop.setValue(null, val, newColVals[idx]); - } - - if (cctx.binaryMarshaller() && val instanceof BinaryObjectBuilder) { - val = ((BinaryObjectBuilder) val).build(); - - val = updateHashCodeIfNeeded(cctx, (BinaryObject) val); - } - - e.setValue(val); - } - } - - /** - * Entry processor that performs atomic MERGE/UPSERT on an entry. - */ - private static class InsertModifier extends EntryModifier implements EntryProcessor { - /** New values for properties as enlisted in initial query (w/o key and its properties, as well as _val). */ - private final Object[] newColVals; - - /** New _val, if present in initial query. */ - private final Object newVal; - - /** */ - private InsertModifier(Object[] newColVals, Object newVal) { - this.newColVals = newColVals; - this.newVal = newVal; - } - - /** {@inheritDoc} */ - @Override public Boolean process(MutableEntry e, Object... args) throws EntryProcessorException { - if (e.exists()) - return false; - - if (!(e instanceof CacheInvokeEntry)) - throw new EntryProcessorException("Unexpected mutable entry type - CacheInvokeEntry expected"); - - assert !F.isEmpty(args) && args[0] instanceof ModifierArgs; - - try { - applyx((CacheInvokeEntry) e, (ModifierArgs) args[0]); - } - catch (IgniteCheckedException ex) { - throw new EntryProcessorException(ex); - } - - return null; - } - - /** {@inheritDoc} */ - @Override public void applyx(CacheInvokeEntry e, ModifierArgs args) throws IgniteCheckedException { - GridCacheContext cctx = e.entry().context(); - - GridQueryTypeDescriptor typeDesc = cctx.grid().context().query().type(cctx.name(), args.typeName); - - Object val = null; - - if (newVal != null) - val = !F.isEmpty(newColVals) ? toBuilderIfNeeded(cctx, newVal) : newVal; - else if (cctx.binaryMarshaller()) // For non binary mode, newVal must be supplied. - val = cctx.grid().binary().builder(typeDesc.valueTypeName()); - - if (val == null) - throw new IgniteSQLException("Value for INSERT or MERGE must not be null", - IgniteQueryErrorCode.NULL_VALUE); - - int i = 0; - - if (!F.isEmpty(newColVals)) - for (String propName : typeDesc.fields().keySet()) { - Integer idx = args.props.get(i++); - - if (idx == null) - continue; - - GridQueryProperty prop = typeDesc.property(propName); - - if (prop.key()) - continue; - - prop.setValue(null, val, newColVals[idx]); - } - - if (cctx.binaryMarshaller() && newVal == null) { - val = ((BinaryObjectBuilder) val).build(); - - val = updateHashCodeIfNeeded(cctx, (BinaryObject) val); - } - - e.setValue(val); - } - } - - /** - * Entry processor invoked by UPDATE and DELETE operations. - */ - private final static class ModifyingEntryProcessor implements EntryProcessor, Serializable { - /** */ - private static final long serialVersionUID = 0L; - - /** Value to expect. */ - private final Object val; - - /** Action to perform on entry. */ - private final EntryModifier entryModifier; - - /** */ - private ModifyingEntryProcessor(Object val, EntryModifier entryModifier) { - assert val != null; - - this.val = val; - this.entryModifier = entryModifier; - } - - /** {@inheritDoc} */ - @Override public Boolean process(MutableEntry entry, Object... arguments) throws EntryProcessorException { - if (!entry.exists()) - return null; // Someone got ahead of us and removed this entry, let's skip it. - - Object entryVal = entry.getValue(); - - if (entryVal == null) - return null; - - // Something happened to the cache while we were performing map-reduce. - if (!F.eq(entryVal, val)) - return false; - - if (!(entry instanceof CacheInvokeEntry)) - throw new EntryProcessorException("Unexpected mutable entry type - CacheInvokeEntry expected"); - - boolean hasArgs = !F.isEmpty(arguments); - - assert !hasArgs || (arguments[0] != null && arguments[0] instanceof ModifierArgs); - - try { - entryModifier.apply((CacheInvokeEntry) entry, - hasArgs ? (ModifierArgs) arguments[0] : null); - } - catch (Exception e) { - throw new EntryProcessorException(e); - } - - return null; // To leave out only erroneous keys - nulls are skipped on results' processing. - } - } - - /** - * Arguments for {@link EntryModifier}. - */ - private final static class ModifierArgs implements Serializable { - /** */ - private static final long serialVersionUID = 0L; - - /** - * Target type descriptor name. - */ - private final String typeName; - - /** - * Same as {@link UpdatePlan#props} - map from property indexes in order defined by type descriptor - * to their positions in array of new property values. - */ - private final LinkedHashMap props; - - /** */ - private ModifierArgs(String typeName, LinkedHashMap props) { - this.typeName = typeName; - this.props = props; - } - } - - /** - * In-closure that mutates given entry with respect to given args. - */ - private abstract static class EntryModifier extends CIX2, ModifierArgs> { - } - /** */ - private static EntryModifier RMV = new EntryModifier() { + private static DmlEntryProcessor RMV = new DmlEntryProcessor() { /** {@inheritDoc} */ - @Override public void applyx(CacheInvokeEntry e, ModifierArgs args) + @Override public void applyx(CacheInvokeEntry e, DmlEntryProcessorArgs args) throws IgniteCheckedException { assert e.exists(); @@ -1349,69 +1059,6 @@ private abstract static class EntryModifier extends CIX2 e, ModifierArgs args) throws IgniteCheckedException { - assert e.exists(); - - GridCacheContext cctx = e.entry().context(); - - GridQueryTypeDescriptor typeDesc = cctx.grid().context().query().type(cctx.name(), args.typeName); - - // If we're here then value check has passed, so we can take old val as basis. - Object val = U.firstNotNull(newVal, e.oldVal()); - - boolean hasProps = (newVal == null || newColVals.length > 1); - - val = hasProps ? toBuilderIfNeeded(cctx, val) : val; - - if (val == null) - throw new IgniteSQLException("New value for UPDATE must not be null", IgniteQueryErrorCode.NULL_VALUE); - - int i = 0; - - for (String propName : typeDesc.fields().keySet()) { - Integer idx = args.props.get(i++); - - if (idx == null) - continue; - - GridQueryProperty prop = typeDesc.property(propName); - - assert !prop.key(); - - Object colVal = newColVals[idx]; - - prop.setValue(null, val, colVal); - } - - if (cctx.binaryMarshaller() && hasProps) { - assert val instanceof BinaryObjectBuilder; - - val = ((BinaryObjectBuilder) val).build(); - - val = updateHashCodeIfNeeded(cctx, (BinaryObject) val); - } - - e.setValue(val); - } - } - /** * Wrap result of DML operation (number of items affected) to Iterable suitable to be wrapped by cursor. * diff --git a/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/dml/DmlEntryProcessor.java b/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/dml/DmlEntryProcessor.java new file mode 100644 index 0000000000000..03a3f695d30e4 --- /dev/null +++ b/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/dml/DmlEntryProcessor.java @@ -0,0 +1,29 @@ +package org.apache.ignite.internal.processors.query.h2.dml; + +import org.apache.ignite.binary.BinaryObject; +import org.apache.ignite.binary.BinaryObjectBuilder; +import org.apache.ignite.internal.processors.cache.CacheInvokeEntry; +import org.apache.ignite.internal.processors.cache.GridCacheContext; +import org.apache.ignite.internal.processors.query.GridQueryProcessor; +import org.apache.ignite.internal.util.typedef.CIX2; + +/** + * In-closure that mutates given entry with respect to given args. + */ +public abstract class DmlEntryProcessor extends CIX2, DmlEntryProcessorArgs> { + /** + * Optionally wrap an object into {@link BinaryObjectBuilder}. + * + * @param cctx Cache context. + * @param val Value to wrap. + * @return {@code val} or {@link BinaryObjectBuilder} wrapping it. + */ + static Object toBuilderIfNeeded(GridCacheContext cctx, Object val) { + if (val == null || !cctx.binaryMarshaller() || GridQueryProcessor.isSqlType(val.getClass())) + return val; + + BinaryObject binVal = cctx.grid().binary().toBinary(val); + + return binVal.toBuilder(); + } +} diff --git a/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/dml/DmlEntryProcessorArgs.java b/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/dml/DmlEntryProcessorArgs.java new file mode 100644 index 0000000000000..a3972b6ce537f --- /dev/null +++ b/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/dml/DmlEntryProcessorArgs.java @@ -0,0 +1,29 @@ +package org.apache.ignite.internal.processors.query.h2.dml; + +import java.io.Serializable; +import java.util.LinkedHashMap; + +/** + * Arguments for {@link EntryModifier}. + */ +public final class DmlEntryProcessorArgs implements Serializable { + /** */ + private static final long serialVersionUID = 0L; + + /** + * Target type descriptor name. + */ + public final String typeName; + + /** + * Same as {@link UpdatePlan#props} - map from property indexes in order defined by type descriptor + * to their positions in array of new property values. + */ + public final LinkedHashMap props; + + /** */ + public DmlEntryProcessorArgs(String typeName, LinkedHashMap props) { + this.typeName = typeName; + this.props = props; + } +} diff --git a/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/dml/InsertProcessor.java b/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/dml/InsertProcessor.java new file mode 100644 index 0000000000000..322163f2f6731 --- /dev/null +++ b/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/dml/InsertProcessor.java @@ -0,0 +1,97 @@ +package org.apache.ignite.internal.processors.query.h2.dml; + +import org.apache.ignite.IgniteCheckedException; +import org.apache.ignite.binary.BinaryObject; +import org.apache.ignite.binary.BinaryObjectBuilder; +import org.apache.ignite.internal.processors.cache.CacheInvokeEntry; +import org.apache.ignite.internal.processors.cache.GridCacheContext; +import org.apache.ignite.internal.processors.cache.query.IgniteQueryErrorCode; +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.DmlStatementsProcessor; +import org.apache.ignite.internal.util.typedef.F; + +import javax.cache.processor.EntryProcessor; +import javax.cache.processor.EntryProcessorException; +import javax.cache.processor.MutableEntry; + +/** + * Entry processor that performs INSERT on an entry. + */ +public class InsertProcessor extends DmlEntryProcessor implements EntryProcessor { + /** New values for properties as enlisted in initial query (w/o key and its properties, as well as _val). */ + private final Object[] newColVals; + + /** New _val, if present in initial query. */ + private final Object newVal; + + /** */ + public InsertProcessor(Object[] newColVals, Object newVal) { + this.newColVals = newColVals; + this.newVal = newVal; + } + + /** {@inheritDoc} */ + @Override public Boolean process(MutableEntry e, Object... args) throws EntryProcessorException { + if (e.exists()) + return false; + + if (!(e instanceof CacheInvokeEntry)) + throw new EntryProcessorException("Unexpected mutable entry type - CacheInvokeEntry expected"); + + assert !F.isEmpty(args) && args[0] instanceof DmlEntryProcessorArgs; + + try { + applyx((CacheInvokeEntry) e, (DmlEntryProcessorArgs) args[0]); + } + catch (IgniteCheckedException ex) { + throw new EntryProcessorException(ex); + } + + return null; + } + + /** {@inheritDoc} */ + @Override public void applyx(CacheInvokeEntry e, DmlEntryProcessorArgs args) throws IgniteCheckedException { + GridCacheContext cctx = e.entry().context(); + + GridQueryTypeDescriptor typeDesc = cctx.grid().context().query().type(cctx.name(), args.typeName); + + Object val = null; + + if (newVal != null) + val = !F.isEmpty(newColVals) ? DmlEntryProcessor.toBuilderIfNeeded(cctx, newVal) : newVal; + else if (cctx.binaryMarshaller()) // For non binary mode, newVal must be supplied. + val = cctx.grid().binary().builder(typeDesc.valueTypeName()); + + if (val == null) + throw new IgniteSQLException("Value for INSERT or MERGE must not be null", + IgniteQueryErrorCode.NULL_VALUE); + + int i = 0; + + if (!F.isEmpty(newColVals)) + for (String propName : typeDesc.fields().keySet()) { + Integer idx = args.props.get(i++); + + if (idx == null) + continue; + + GridQueryProperty prop = typeDesc.property(propName); + + if (prop.key()) + continue; + + prop.setValue(null, val, newColVals[idx]); + } + + if (cctx.binaryMarshaller() && newVal == null) { + val = ((BinaryObjectBuilder) val).build(); + + val = DmlStatementsProcessor.updateHashCodeIfNeeded(cctx, (BinaryObject) val); + } + + e.setValue(val); + } +} diff --git a/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/dml/MergeProcessor.java b/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/dml/MergeProcessor.java new file mode 100644 index 0000000000000..e826727a6c13e --- /dev/null +++ b/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/dml/MergeProcessor.java @@ -0,0 +1,121 @@ +package org.apache.ignite.internal.processors.query.h2.dml; + +import org.apache.ignite.IgniteCheckedException; +import org.apache.ignite.binary.BinaryObject; +import org.apache.ignite.binary.BinaryObjectBuilder; +import org.apache.ignite.internal.processors.cache.CacheInvokeEntry; +import org.apache.ignite.internal.processors.cache.GridCacheContext; +import org.apache.ignite.internal.processors.cache.query.IgniteQueryErrorCode; +import org.apache.ignite.internal.processors.query.GridQueryProcessor; +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.DmlStatementsProcessor; +import org.apache.ignite.internal.util.typedef.F; +import org.apache.ignite.internal.util.typedef.internal.U; + +import javax.cache.processor.EntryProcessor; +import javax.cache.processor.EntryProcessorException; +import javax.cache.processor.MutableEntry; + +/** + * Entry processor that performs atomic MERGE/upsert on an entry. + */ +public class MergeProcessor extends DmlEntryProcessor implements EntryProcessor { + /** New values for properties as enlisted in initial query (w/o key and its properties, as well as _val). */ + private final Object[] newColVals; + + /** New _val, if present in initial query. */ + private final Object newVal; + + /** */ + public MergeProcessor(Object[] newColVals, Object newVal) { + // This processor must be used only when no _val is given explicitly, + // hence new values must be properties. + assert !F.isEmpty(newColVals); + + this.newColVals = newColVals; + this.newVal = newVal; + } + + /** {@inheritDoc} */ + @Override public Boolean process(MutableEntry e, Object... args) throws EntryProcessorException { + if (!(e instanceof CacheInvokeEntry)) + throw new EntryProcessorException("Unexpected mutable entry type - CacheInvokeEntry expected"); + + assert !F.isEmpty(args) && args[0] instanceof DmlEntryProcessorArgs; + + try { + applyx((CacheInvokeEntry) e, (DmlEntryProcessorArgs) args[0]); + } + catch (IgniteCheckedException ex) { + throw new EntryProcessorException(ex); + } + + return null; + } + + /** {@inheritDoc} */ + @Override public void applyx(CacheInvokeEntry e, DmlEntryProcessorArgs args) throws IgniteCheckedException { + GridCacheContext cctx = e.entry().context(); + + GridQueryTypeDescriptor typeDesc = cctx.grid().context().query().type(cctx.name(), args.typeName); + + // This processor must be used only when no _val is given explicitly, and it's impossible + // when value is of SQL type. + assert !GridQueryProcessor.isSqlType(typeDesc.valueClass()); + + Object val = null; + + if (e.exists()) { + val = e.getValue(); + + if (!cctx.binaryMarshaller()) { + if (!cctx.cache().configuration().isCopyOnRead()) { + byte[] valBytes = cctx.marshaller().marshal(val); + + // val is another object now, we can mutate it + val = cctx.marshaller().unmarshal(valBytes, U.resolveClassLoader(cctx.gridConfig())); + } + } + else { + val = cctx.grid().binary().toBinary(val); + + assert val instanceof BinaryObject; + + val = ((BinaryObject) val).toBuilder(); + } + } + else if (newVal != null) + val = toBuilderIfNeeded(cctx, newVal); + else if (cctx.binaryMarshaller()) + val = cctx.grid().binary().builder(typeDesc.valueTypeName()); + + if (val == null) + throw new IgniteSQLException("Value for MERGE must not be null", IgniteQueryErrorCode.NULL_VALUE); + + int i = 0; + + for (String propName : typeDesc.fields().keySet()) { + Integer idx = args.props.get(i++); + + if (idx == null) + continue; + + GridQueryProperty prop = typeDesc.property(propName); + + if (prop.key()) + continue; + + prop.setValue(null, val, newColVals[idx]); + } + + if (cctx.binaryMarshaller() && val instanceof BinaryObjectBuilder) { + val = ((BinaryObjectBuilder) val).build(); + + val = DmlStatementsProcessor.updateHashCodeIfNeeded(cctx, (BinaryObject) val); + } + + e.setValue(val); + } +} diff --git a/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/dml/ModifyingEntryProcessor.java b/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/dml/ModifyingEntryProcessor.java new file mode 100644 index 0000000000000..f18a17c2cda59 --- /dev/null +++ b/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/dml/ModifyingEntryProcessor.java @@ -0,0 +1,64 @@ +package org.apache.ignite.internal.processors.query.h2.dml; + +import org.apache.ignite.internal.processors.cache.CacheInvokeEntry; +import org.apache.ignite.internal.util.typedef.F; + +import javax.cache.processor.EntryProcessor; +import javax.cache.processor.EntryProcessorException; +import javax.cache.processor.MutableEntry; +import java.io.Serializable; + +/** + * Entry processor invoked by DML operations - essentially a wrapper for a {@link DmlEntryProcessor} closure. + */ +public final class ModifyingEntryProcessor implements EntryProcessor, Serializable { + /** */ + private static final long serialVersionUID = 0L; + + /** Value to expect. */ + private final Object val; + + /** Action to perform on entry. */ + private final DmlEntryProcessor entryModifier; + + /** */ + public ModifyingEntryProcessor(Object val, DmlEntryProcessor entryModifier) { + assert val != null; + + this.val = val; + this.entryModifier = entryModifier; + } + + /** {@inheritDoc} */ + @Override public Boolean process(MutableEntry entry, Object... arguments) + throws EntryProcessorException { + if (!entry.exists()) + return null; // Someone got ahead of us and removed this entry, let's skip it. + + Object entryVal = entry.getValue(); + + if (entryVal == null) + return null; + + // Something happened to the cache while we were performing map-reduce. + if (!F.eq(entryVal, val)) + return false; + + if (!(entry instanceof CacheInvokeEntry)) + throw new EntryProcessorException("Unexpected mutable entry type - CacheInvokeEntry expected"); + + boolean hasArgs = !F.isEmpty(arguments); + + assert !hasArgs || (arguments[0] != null && arguments[0] instanceof DmlEntryProcessorArgs); + + try { + entryModifier.apply((CacheInvokeEntry) entry, + hasArgs ? (DmlEntryProcessorArgs) arguments[0] : null); + } + catch (Exception e) { + throw new EntryProcessorException(e); + } + + return null; // To leave out only erroneous keys - nulls are skipped on results' processing. + } +} diff --git a/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/dml/UpdateProcessor.java b/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/dml/UpdateProcessor.java new file mode 100644 index 0000000000000..82cb7c981a14f --- /dev/null +++ b/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/dml/UpdateProcessor.java @@ -0,0 +1,76 @@ +package org.apache.ignite.internal.processors.query.h2.dml; + +import org.apache.ignite.IgniteCheckedException; +import org.apache.ignite.binary.BinaryObject; +import org.apache.ignite.binary.BinaryObjectBuilder; +import org.apache.ignite.internal.processors.cache.CacheInvokeEntry; +import org.apache.ignite.internal.processors.cache.GridCacheContext; +import org.apache.ignite.internal.processors.cache.query.IgniteQueryErrorCode; +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.DmlStatementsProcessor; +import org.apache.ignite.internal.util.typedef.internal.U; + +/** + * Entry processor for SQL UPDATE operation. + */ +public final class UpdateProcessor extends DmlEntryProcessor { + /** Values to set - in order specified by {@link DmlEntryProcessorArgs#props}. */ + private final Object[] newColVals; + + /** New value, if given in initial query. */ + private final Object newVal; + + /** */ + public UpdateProcessor(Object[] newColVals, Object newVal) { + this.newColVals = newColVals; + this.newVal = newVal; + } + + /** {@inheritDoc} */ + @Override public void applyx(CacheInvokeEntry e, DmlEntryProcessorArgs args) throws IgniteCheckedException { + assert e.exists(); + + GridCacheContext cctx = e.entry().context(); + + GridQueryTypeDescriptor typeDesc = cctx.grid().context().query().type(cctx.name(), args.typeName); + + // If we're here then value check has passed, so we can take old val as basis. + Object val = U.firstNotNull(newVal, e.oldVal()); + + boolean hasProps = (newVal == null || newColVals.length > 1); + + val = hasProps ? toBuilderIfNeeded(cctx, val) : val; + + if (val == null) + throw new IgniteSQLException("New value for UPDATE must not be null", IgniteQueryErrorCode.NULL_VALUE); + + int i = 0; + + for (String propName : typeDesc.fields().keySet()) { + Integer idx = args.props.get(i++); + + if (idx == null) + continue; + + GridQueryProperty prop = typeDesc.property(propName); + + assert !prop.key(); + + Object colVal = newColVals[idx]; + + prop.setValue(null, val, colVal); + } + + if (cctx.binaryMarshaller() && hasProps) { + assert val instanceof BinaryObjectBuilder; + + val = ((BinaryObjectBuilder) val).build(); + + val = DmlStatementsProcessor.updateHashCodeIfNeeded(cctx, (BinaryObject) val); + } + + e.setValue(val); + } +} 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 8afeef92a8917..0e6a2313497f0 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 @@ -43,6 +43,25 @@ public void testMergeWithExplicitKey() { assertEquals(createPerson(2, "Alex"), p.get("a")); } + /** + * + */ + public void testMerge() { + IgniteCache p = ignite(0).cache("S2P").withKeepBinary(); + + p.put("s", createPerson(1, null)); + + p.put("a", createPerson(2, null)); + + // Due to presence of both keys the values will be partially updated. + p.query(new SqlFieldsQuery("merge into Person (_key, name) values ('s', ?), " + + "('a', ?)").setArgs("Sergi", "Alex")); + + assertEquals(createPerson(1, "Sergi"), p.get("s")); + + assertEquals(createPerson(2, "Alex"), p.get("a")); + } + /** * */ From 570dae38653672d03e5d441b06870833a7c2052c Mon Sep 17 00:00:00 2001 From: Alexander Paschenko Date: Wed, 8 Feb 2017 13:45:28 +0300 Subject: [PATCH 11/12] IGNITE-4363: SQL: fixed inner properties update in DML. (cherry picked from commit d01406f) --- .../processors/query/GridQueryProcessor.java | 7 ++- .../query/h2/DmlStatementsProcessor.java | 2 +- .../query/h2/dml/UpdatePlanBuilder.java | 50 +++++++++++++++---- .../IgniteCacheUpdateSqlQuerySelfTest.java | 6 ++- 4 files changed, 49 insertions(+), 16 deletions(-) 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 0b2669deafd51..806fc83398be9 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 @@ -2143,11 +2143,10 @@ else if (val instanceof BinaryObject && ((BinaryObject)val).hasField(propName)) else if (obj instanceof BinaryObjectBuilder) { BinaryObjectBuilder obj0 = (BinaryObjectBuilder)obj; - return obj0.getField(propName); + return obj0.getField(name()); } else - throw new IgniteCheckedException("Unexpected type of binary object to get field value from [obj=" + obj - + ", type=" + obj.getClass() + ']'); + throw new IgniteCheckedException("Unexpected binary object class [type=" + obj.getClass() + ']'); } /** {@inheritDoc} */ @@ -2173,7 +2172,7 @@ else if (obj instanceof BinaryObjectBuilder) { needsBuild = true; - obj = ((BinaryObjectExImpl) obj).toBuilder(); + obj = ((BinaryObjectExImpl)obj).toBuilder(); } if (!(obj instanceof BinaryObjectBuilder)) 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 746833bf0d46f..e4fe91eb7c36f 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 @@ -1016,7 +1016,7 @@ private IgniteBiTuple rowToInsertProc(GridCacheContext c key = updateHashCodeIfNeeded(cctx, (BinaryObject) key); } - return new IgniteBiTuple<>(key, (EntryProcessor) (plan.mode == UpdateMode.MERGE ? new MergeProcessor(newColVals, val) : + return new IgniteBiTuple<>(key, (plan.mode == UpdateMode.MERGE ? new MergeProcessor(newColVals, val) : new InsertProcessor(newColVals, val))); } 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 de37ea24f0bda..3a40085169f04 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 @@ -54,6 +54,7 @@ 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; @@ -380,7 +381,7 @@ else if (stmt instanceof GridSqlDelete) { * @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, @@ -432,8 +433,25 @@ else if (!buildable) } } else { - if (colIdx != -1) - return new PlainValueSupplier(colIdx); + if (colIdx != -1) { + if (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; @@ -456,8 +474,12 @@ else if (!buildable) return ctor0.newInstance(); } catch (Exception e) { - throw new IgniteCheckedException("Failed to invoke default ctor for " + - (key ? "key" : "value") + " [type=" + typeName + ']', 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); } } }; @@ -471,8 +493,12 @@ else if (!buildable) return GridUnsafe.allocateInstance(cls); } catch (InstantiationException e) { - throw new IgniteCheckedException("Failed to instantiate " + - (key ? "key" : "value") + " via Unsafe [type=" + typeName + ']', 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); } } }; @@ -548,12 +574,18 @@ private static boolean updateAffectsKeyColumns(GridH2Table gridTbl, Set return false; } - /** Simple supplier that just takes specified element of a given row. */ + /** + * 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; } 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 7c2085b17bfe9..c3d9d8ed40e5d 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 @@ -188,8 +188,10 @@ public void testTypeConversions() throws ParseException { 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("update \"AllTypes\" set \"doubleCol\" = CAST('50' as INT)," + - " \"booleanCol\" = 80, \"innerLongCol\" = ?, \"innerTypeCol\" = ?, \"strCol\" = PI(), \"shortCol\" = " + + // 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\" = ?") From 8b81db265e8062d7b74501c0ca10d89d1e3e78d9 Mon Sep 17 00:00:00 2001 From: Alexander Paschenko Date: Mon, 20 Feb 2017 13:32:19 +0300 Subject: [PATCH 12/12] IGNITE-4169: SQL: implemented streaming support for INSERT operations. This closes #1350. This closes #1553. (cherry picked from commit 0130b09) --- .../JdbcAbstractDmlStatementSelfTest.java | 49 +---- .../jdbc2/JdbcInsertStatementSelfTest.java | 51 +++++ .../jdbc2/JdbcMergeStatementSelfTest.java | 51 +++++ .../internal/jdbc2/JdbcStreamingSelfTest.java | 189 +++++++++++++++++ .../jdbc2/JdbcUpdateStatementSelfTest.java | 50 +++++ .../jdbc/suite/IgniteJdbcDriverTestSuite.java | 1 + .../org/apache/ignite/IgniteJdbcDriver.java | 30 +++ .../ignite/internal/jdbc2/JdbcConnection.java | 72 +++++-- .../internal/jdbc2/JdbcPreparedStatement.java | 34 ++- .../ignite/internal/jdbc2/JdbcStatement.java | 20 +- .../jdbc2/JdbcStreamedPreparedStatement.java | 59 ++++++ .../processors/query/GridQueryIndexing.java | 35 +++ .../processors/query/GridQueryProcessor.java | 63 +++++- .../query/h2/DmlStatementsProcessor.java | 199 +++++++++++++----- .../processors/query/h2/IgniteH2Indexing.java | 55 ++++- 15 files changed, 814 insertions(+), 144 deletions(-) create mode 100644 modules/clients/src/test/java/org/apache/ignite/internal/jdbc2/JdbcStreamingSelfTest.java create mode 100644 modules/clients/src/test/java/org/apache/ignite/internal/jdbc2/JdbcUpdateStatementSelfTest.java create mode 100644 modules/core/src/main/java/org/apache/ignite/internal/jdbc2/JdbcStreamedPreparedStatement.java diff --git a/modules/clients/src/test/java/org/apache/ignite/internal/jdbc2/JdbcAbstractDmlStatementSelfTest.java b/modules/clients/src/test/java/org/apache/ignite/internal/jdbc2/JdbcAbstractDmlStatementSelfTest.java index 4a97aefcfcbe0..332bbbaf40925 100644 --- a/modules/clients/src/test/java/org/apache/ignite/internal/jdbc2/JdbcAbstractDmlStatementSelfTest.java +++ b/modules/clients/src/test/java/org/apache/ignite/internal/jdbc2/JdbcAbstractDmlStatementSelfTest.java @@ -20,8 +20,6 @@ import java.io.Serializable; import java.sql.Connection; import java.sql.DriverManager; -import java.sql.ResultSet; -import java.sql.Statement; import java.util.Collections; import org.apache.ignite.cache.CachePeekMode; import org.apache.ignite.cache.QueryEntity; @@ -54,7 +52,7 @@ public abstract class JdbcAbstractDmlStatementSelfTest extends GridCommonAbstrac static final String BASE_URL_BIN = CFG_URL_PREFIX + "modules/clients/src/test/config/jdbc-bin-config.xml"; /** SQL SELECT query for verification. */ - private static final String SQL_SELECT = "select _key, id, firstName, lastName, age from Person"; + static final String SQL_SELECT = "select _key, id, firstName, lastName, age from Person"; /** Connection. */ protected Connection conn; @@ -149,51 +147,6 @@ protected String getCfgUrl() { /** {@inheritDoc} */ @Override protected void afterTest() throws Exception { - try (Statement selStmt = conn.createStatement()) { - assert selStmt.execute(SQL_SELECT); - - ResultSet rs = selStmt.getResultSet(); - - assert rs != null; - - while (rs.next()) { - int id = rs.getInt("id"); - - switch (id) { - case 1: - assertEquals("p1", rs.getString("_key")); - assertEquals("John", rs.getString("firstName")); - assertEquals("White", rs.getString("lastName")); - assertEquals(25, rs.getInt("age")); - break; - - case 2: - assertEquals("p2", rs.getString("_key")); - assertEquals("Joe", rs.getString("firstName")); - assertEquals("Black", rs.getString("lastName")); - assertEquals(35, rs.getInt("age")); - break; - - case 3: - assertEquals("p3", rs.getString("_key")); - assertEquals("Mike", rs.getString("firstName")); - assertEquals("Green", rs.getString("lastName")); - assertEquals(40, rs.getInt("age")); - break; - - case 4: - assertEquals("p4", rs.getString("_key")); - assertEquals("Leah", rs.getString("firstName")); - assertEquals("Grey", rs.getString("lastName")); - assertEquals(22, rs.getInt("age")); - break; - - default: - assert false : "Invalid ID: " + id; - } - } - } - grid(0).cache(null).clear(); assertEquals(0, grid(0).cache(null).size(CachePeekMode.ALL)); diff --git a/modules/clients/src/test/java/org/apache/ignite/internal/jdbc2/JdbcInsertStatementSelfTest.java b/modules/clients/src/test/java/org/apache/ignite/internal/jdbc2/JdbcInsertStatementSelfTest.java index 7fc92de20595c..1bd6d34f27005 100644 --- a/modules/clients/src/test/java/org/apache/ignite/internal/jdbc2/JdbcInsertStatementSelfTest.java +++ b/modules/clients/src/test/java/org/apache/ignite/internal/jdbc2/JdbcInsertStatementSelfTest.java @@ -18,12 +18,14 @@ package org.apache.ignite.internal.jdbc2; import java.sql.PreparedStatement; +import java.sql.ResultSet; import java.sql.SQLException; import java.sql.Statement; import java.util.Arrays; import java.util.HashSet; import java.util.concurrent.Callable; import org.apache.ignite.IgniteException; +import org.apache.ignite.cache.CachePeekMode; import org.apache.ignite.testframework.GridTestUtils; /** @@ -61,6 +63,55 @@ public class JdbcInsertStatementSelfTest extends JdbcAbstractDmlStatementSelfTes /** {@inheritDoc} */ @Override protected void afterTest() throws Exception { + try (Statement selStmt = conn.createStatement()) { + assertTrue(selStmt.execute(SQL_SELECT)); + + ResultSet rs = selStmt.getResultSet(); + + assert rs != null; + + while (rs.next()) { + int id = rs.getInt("id"); + + switch (id) { + case 1: + assertEquals("p1", rs.getString("_key")); + assertEquals("John", rs.getString("firstName")); + assertEquals("White", rs.getString("lastName")); + assertEquals(25, rs.getInt("age")); + break; + + case 2: + assertEquals("p2", rs.getString("_key")); + assertEquals("Joe", rs.getString("firstName")); + assertEquals("Black", rs.getString("lastName")); + assertEquals(35, rs.getInt("age")); + break; + + case 3: + assertEquals("p3", rs.getString("_key")); + assertEquals("Mike", rs.getString("firstName")); + assertEquals("Green", rs.getString("lastName")); + assertEquals(40, rs.getInt("age")); + break; + + case 4: + assertEquals("p4", rs.getString("_key")); + assertEquals("Leah", rs.getString("firstName")); + assertEquals("Grey", rs.getString("lastName")); + assertEquals(22, rs.getInt("age")); + break; + + default: + assert false : "Invalid ID: " + id; + } + } + } + + grid(0).cache(null).clear(); + + assertEquals(0, grid(0).cache(null).size(CachePeekMode.ALL)); + super.afterTest(); if (stmt != null && !stmt.isClosed()) diff --git a/modules/clients/src/test/java/org/apache/ignite/internal/jdbc2/JdbcMergeStatementSelfTest.java b/modules/clients/src/test/java/org/apache/ignite/internal/jdbc2/JdbcMergeStatementSelfTest.java index ecf6032db745f..3c56c921bc78b 100644 --- a/modules/clients/src/test/java/org/apache/ignite/internal/jdbc2/JdbcMergeStatementSelfTest.java +++ b/modules/clients/src/test/java/org/apache/ignite/internal/jdbc2/JdbcMergeStatementSelfTest.java @@ -18,8 +18,10 @@ package org.apache.ignite.internal.jdbc2; import java.sql.PreparedStatement; +import java.sql.ResultSet; import java.sql.SQLException; import java.sql.Statement; +import org.apache.ignite.cache.CachePeekMode; /** * MERGE statement test. @@ -56,6 +58,55 @@ public class JdbcMergeStatementSelfTest extends JdbcAbstractDmlStatementSelfTest /** {@inheritDoc} */ @Override protected void afterTest() throws Exception { + try (Statement selStmt = conn.createStatement()) { + assertTrue(selStmt.execute(SQL_SELECT)); + + ResultSet rs = selStmt.getResultSet(); + + assert rs != null; + + while (rs.next()) { + int id = rs.getInt("id"); + + switch (id) { + case 1: + assertEquals("p1", rs.getString("_key")); + assertEquals("John", rs.getString("firstName")); + assertEquals("White", rs.getString("lastName")); + assertEquals(25, rs.getInt("age")); + break; + + case 2: + assertEquals("p2", rs.getString("_key")); + assertEquals("Joe", rs.getString("firstName")); + assertEquals("Black", rs.getString("lastName")); + assertEquals(35, rs.getInt("age")); + break; + + case 3: + assertEquals("p3", rs.getString("_key")); + assertEquals("Mike", rs.getString("firstName")); + assertEquals("Green", rs.getString("lastName")); + assertEquals(40, rs.getInt("age")); + break; + + case 4: + assertEquals("p4", rs.getString("_key")); + assertEquals("Leah", rs.getString("firstName")); + assertEquals("Grey", rs.getString("lastName")); + assertEquals(22, rs.getInt("age")); + break; + + default: + assert false : "Invalid ID: " + id; + } + } + } + + grid(0).cache(null).clear(); + + assertEquals(0, grid(0).cache(null).size(CachePeekMode.ALL)); + super.afterTest(); if (stmt != null && !stmt.isClosed()) diff --git a/modules/clients/src/test/java/org/apache/ignite/internal/jdbc2/JdbcStreamingSelfTest.java b/modules/clients/src/test/java/org/apache/ignite/internal/jdbc2/JdbcStreamingSelfTest.java new file mode 100644 index 0000000000000..5e206eef2248b --- /dev/null +++ b/modules/clients/src/test/java/org/apache/ignite/internal/jdbc2/JdbcStreamingSelfTest.java @@ -0,0 +1,189 @@ +/* + * 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.jdbc2; + +import java.sql.Connection; +import java.sql.DriverManager; +import java.sql.PreparedStatement; +import java.util.Properties; +import org.apache.ignite.IgniteJdbcDriver; +import org.apache.ignite.IgniteLogger; +import org.apache.ignite.configuration.CacheConfiguration; +import org.apache.ignite.configuration.ConnectorConfiguration; +import org.apache.ignite.configuration.IgniteConfiguration; +import org.apache.ignite.internal.util.typedef.internal.U; +import org.apache.ignite.spi.discovery.tcp.TcpDiscoverySpi; +import org.apache.ignite.spi.discovery.tcp.ipfinder.TcpDiscoveryIpFinder; +import org.apache.ignite.spi.discovery.tcp.ipfinder.vm.TcpDiscoveryVmIpFinder; +import org.apache.ignite.testframework.junits.common.GridCommonAbstractTest; + +import static org.apache.ignite.IgniteJdbcDriver.CFG_URL_PREFIX; +import static org.apache.ignite.cache.CacheMode.PARTITIONED; +import static org.apache.ignite.cache.CacheWriteSynchronizationMode.FULL_SYNC; + +/** + * Data streaming test. + */ +public class JdbcStreamingSelfTest extends GridCommonAbstractTest { + /** IP finder. */ + private static final TcpDiscoveryIpFinder IP_FINDER = new TcpDiscoveryVmIpFinder(true); + + /** JDBC URL. */ + private static final String BASE_URL = CFG_URL_PREFIX + "modules/clients/src/test/config/jdbc-config.xml"; + + /** Connection. */ + protected Connection conn; + + /** */ + protected transient IgniteLogger log; + + /** {@inheritDoc} */ + @Override protected IgniteConfiguration getConfiguration(String gridName) throws Exception { + return getConfiguration0(gridName); + } + + /** + * @param gridName Grid name. + * @return Grid configuration used for starting the grid. + * @throws Exception If failed. + */ + private IgniteConfiguration getConfiguration0(String gridName) throws Exception { + IgniteConfiguration cfg = super.getConfiguration(gridName); + + CacheConfiguration cache = defaultCacheConfiguration(); + + cache.setCacheMode(PARTITIONED); + cache.setBackups(1); + cache.setWriteSynchronizationMode(FULL_SYNC); + cache.setIndexedTypes( + Integer.class, Integer.class + ); + + cfg.setCacheConfiguration(cache); + + TcpDiscoverySpi disco = new TcpDiscoverySpi(); + + disco.setIpFinder(IP_FINDER); + + cfg.setDiscoverySpi(disco); + + cfg.setConnectorConfiguration(new ConnectorConfiguration()); + + return cfg; + } + + /** {@inheritDoc} */ + @Override protected void beforeTestsStarted() throws Exception { + startGridsMultiThreaded(3); + + Class.forName("org.apache.ignite.IgniteJdbcDriver"); + } + + /** {@inheritDoc} */ + @Override protected void afterTestsStopped() throws Exception { + stopAllGrids(); + } + + /** + * @param allowOverwrite Allow overwriting of existing keys. + * @return Connection to use for the test. + * @throws Exception if failed. + */ + private Connection createConnection(boolean allowOverwrite) throws Exception { + Properties props = new Properties(); + + props.setProperty(IgniteJdbcDriver.PROP_STREAMING, "true"); + props.setProperty(IgniteJdbcDriver.PROP_STREAMING_FLUSH_FREQ, "500"); + + if (allowOverwrite) + props.setProperty(IgniteJdbcDriver.PROP_STREAMING_ALLOW_OVERWRITE, "true"); + + return DriverManager.getConnection(BASE_URL, props); + } + + /** {@inheritDoc} */ + @Override protected void afterTest() throws Exception { + U.closeQuiet(conn); + + ignite(0).cache(null).clear(); + + super.afterTest(); + } + + /** + * @throws Exception if failed. + */ + public void testStreamedInsert() throws Exception { + conn = createConnection(false); + + ignite(0).cache(null).put(5, 500); + + PreparedStatement stmt = conn.prepareStatement("insert into Integer(_key, _val) values (?, ?)"); + + for (int i = 1; i <= 100000; i++) { + stmt.setInt(1, i); + stmt.setInt(2, i); + + stmt.executeUpdate(); + } + + // Data is not there yet. + assertNull(grid(0).cache(null).get(100000)); + + // Let the stream flush. + U.sleep(1500); + + // Now let's check it's all there. + assertEquals(1, grid(0).cache(null).get(1)); + assertEquals(100000, grid(0).cache(null).get(100000)); + + // 5 should still point to 500. + assertEquals(500, grid(0).cache(null).get(5)); + } + + /** + * @throws Exception if failed. + */ + public void testStreamedInsertWithOverwritesAllowed() throws Exception { + conn = createConnection(true); + + ignite(0).cache(null).put(5, 500); + + PreparedStatement stmt = conn.prepareStatement("insert into Integer(_key, _val) values (?, ?)"); + + for (int i = 1; i <= 100000; i++) { + stmt.setInt(1, i); + stmt.setInt(2, i); + + stmt.executeUpdate(); + } + + // Data is not there yet. + assertNull(grid(0).cache(null).get(100000)); + + // Let the stream flush. + U.sleep(1500); + + // Now let's check it's all there. + assertEquals(1, grid(0).cache(null).get(1)); + assertEquals(100000, grid(0).cache(null).get(100000)); + + // 5 should now point to 5 as we've turned overwriting on. + assertEquals(5, grid(0).cache(null).get(5)); + } +} diff --git a/modules/clients/src/test/java/org/apache/ignite/internal/jdbc2/JdbcUpdateStatementSelfTest.java b/modules/clients/src/test/java/org/apache/ignite/internal/jdbc2/JdbcUpdateStatementSelfTest.java new file mode 100644 index 0000000000000..8ae0e906acd69 --- /dev/null +++ b/modules/clients/src/test/java/org/apache/ignite/internal/jdbc2/JdbcUpdateStatementSelfTest.java @@ -0,0 +1,50 @@ +/* + * 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.jdbc2; + +import java.sql.SQLException; +import java.util.Arrays; +import org.apache.ignite.cache.query.SqlFieldsQuery; +import org.apache.ignite.internal.util.typedef.F; + +/** + * + */ +public class JdbcUpdateStatementSelfTest extends JdbcAbstractUpdateStatementSelfTest { + /** + * + */ + public void testExecute() throws SQLException { + conn.createStatement().execute("update Person set firstName = 'Jack' where " + + "cast(substring(_key, 2, 1) as int) % 2 = 0"); + + assertEquals(Arrays.asList(F.asList("John"), F.asList("Jack"), F.asList("Mike")), + jcache(0).query(new SqlFieldsQuery("select firstName from Person order by _key")).getAll()); + } + + /** + * + */ + public void testExecuteUpdate() throws SQLException { + conn.createStatement().executeUpdate("update Person set firstName = 'Jack' where " + + "cast(substring(_key, 2, 1) as int) % 2 = 0"); + + assertEquals(Arrays.asList(F.asList("John"), F.asList("Jack"), F.asList("Mike")), + jcache(0).query(new SqlFieldsQuery("select firstName from Person order by _key")).getAll()); + } +} diff --git a/modules/clients/src/test/java/org/apache/ignite/jdbc/suite/IgniteJdbcDriverTestSuite.java b/modules/clients/src/test/java/org/apache/ignite/jdbc/suite/IgniteJdbcDriverTestSuite.java index 048643b75dd04..050fb0a08647e 100644 --- a/modules/clients/src/test/java/org/apache/ignite/jdbc/suite/IgniteJdbcDriverTestSuite.java +++ b/modules/clients/src/test/java/org/apache/ignite/jdbc/suite/IgniteJdbcDriverTestSuite.java @@ -67,6 +67,7 @@ public static TestSuite suite() throws Exception { suite.addTest(new TestSuite(org.apache.ignite.internal.jdbc2.JdbcInsertStatementSelfTest.class)); suite.addTest(new TestSuite(org.apache.ignite.internal.jdbc2.JdbcBinaryMarshallerInsertStatementSelfTest.class)); suite.addTest(new TestSuite(org.apache.ignite.internal.jdbc2.JdbcDeleteStatementSelfTest.class)); + suite.addTest(new TestSuite(org.apache.ignite.internal.jdbc2.JdbcStreamingSelfTest.class)); return suite; } diff --git a/modules/core/src/main/java/org/apache/ignite/IgniteJdbcDriver.java b/modules/core/src/main/java/org/apache/ignite/IgniteJdbcDriver.java index d432c1e4f9fee..9790b8f9986d9 100644 --- a/modules/core/src/main/java/org/apache/ignite/IgniteJdbcDriver.java +++ b/modules/core/src/main/java/org/apache/ignite/IgniteJdbcDriver.java @@ -292,6 +292,21 @@ public class IgniteJdbcDriver implements Driver { /** Distributed joins parameter name. */ private static final String PARAM_DISTRIBUTED_JOINS = "distributedJoins"; + /** DML streaming parameter name. */ + private static final String PARAM_STREAMING = "streaming"; + + /** DML streaming auto flush frequency. */ + private static final String PARAM_STREAMING_FLUSH_FREQ = "streamingFlushFrequency"; + + /** DML streaming node buffer size. */ + private static final String PARAM_STREAMING_PER_NODE_BUF_SIZE = "streamingPerNodeBufferSize"; + + /** DML streaming parallel operations per node. */ + private static final String PARAM_STREAMING_PER_NODE_PAR_OPS = "streamingPerNodeParallelOperations"; + + /** Whether DML streaming will overwrite existing cache entries. */ + private static final String PARAM_STREAMING_ALLOW_OVERWRITE = "streamingAllowOverwrite"; + /** Hostname property name. */ public static final String PROP_HOST = PROP_PREFIX + "host"; @@ -313,6 +328,21 @@ public class IgniteJdbcDriver implements Driver { /** Distributed joins property name. */ public static final String PROP_DISTRIBUTED_JOINS = PROP_PREFIX + PARAM_DISTRIBUTED_JOINS; + /** DML streaming property name. */ + public static final String PROP_STREAMING = PROP_PREFIX + PARAM_STREAMING; + + /** DML stream auto flush frequency property name. */ + public static final String PROP_STREAMING_FLUSH_FREQ = PROP_PREFIX + PARAM_STREAMING_FLUSH_FREQ; + + /** DML stream node buffer size property name. */ + public static final String PROP_STREAMING_PER_NODE_BUF_SIZE = PROP_PREFIX + PARAM_STREAMING_PER_NODE_BUF_SIZE; + + /** DML stream parallel operations per node property name. */ + public static final String PROP_STREAMING_PER_NODE_PAR_OPS = PROP_PREFIX + PARAM_STREAMING_PER_NODE_PAR_OPS; + + /** Whether DML streaming will overwrite existing cache entries. */ + public static final String PROP_STREAMING_ALLOW_OVERWRITE = PROP_PREFIX + PARAM_STREAMING_ALLOW_OVERWRITE; + /** Cache name property name. */ public static final String PROP_CFG = PROP_PREFIX + "cfg"; diff --git a/modules/core/src/main/java/org/apache/ignite/internal/jdbc2/JdbcConnection.java b/modules/core/src/main/java/org/apache/ignite/internal/jdbc2/JdbcConnection.java index 5c4a147174b45..4244602b3c649 100644 --- a/modules/core/src/main/java/org/apache/ignite/internal/jdbc2/JdbcConnection.java +++ b/modules/core/src/main/java/org/apache/ignite/internal/jdbc2/JdbcConnection.java @@ -48,17 +48,21 @@ import org.apache.ignite.IgniteCheckedException; import org.apache.ignite.IgniteClientDisconnectedException; import org.apache.ignite.IgniteCompute; +import org.apache.ignite.IgniteDataStreamer; import org.apache.ignite.IgniteException; import org.apache.ignite.IgniteJdbcDriver; import org.apache.ignite.Ignition; import org.apache.ignite.cluster.ClusterGroup; import org.apache.ignite.compute.ComputeTaskTimeoutException; +import org.apache.ignite.configuration.CacheConfiguration; import org.apache.ignite.configuration.IgniteConfiguration; +import org.apache.ignite.internal.IgniteEx; import org.apache.ignite.internal.IgnitionEx; import org.apache.ignite.internal.processors.cache.IgniteCacheProxy; import org.apache.ignite.internal.processors.resource.GridSpringResourceContext; import org.apache.ignite.internal.util.future.GridFutureAdapter; import org.apache.ignite.internal.util.typedef.F; +import org.apache.ignite.internal.util.typedef.internal.U; import org.apache.ignite.lang.IgniteBiTuple; import org.apache.ignite.lang.IgniteCallable; import org.apache.ignite.resources.IgniteInstanceResource; @@ -73,6 +77,11 @@ import static org.apache.ignite.IgniteJdbcDriver.PROP_DISTRIBUTED_JOINS; import static org.apache.ignite.IgniteJdbcDriver.PROP_LOCAL; import static org.apache.ignite.IgniteJdbcDriver.PROP_NODE_ID; +import static org.apache.ignite.IgniteJdbcDriver.PROP_STREAMING; +import static org.apache.ignite.IgniteJdbcDriver.PROP_STREAMING_ALLOW_OVERWRITE; +import static org.apache.ignite.IgniteJdbcDriver.PROP_STREAMING_FLUSH_FREQ; +import static org.apache.ignite.IgniteJdbcDriver.PROP_STREAMING_PER_NODE_BUF_SIZE; +import static org.apache.ignite.IgniteJdbcDriver.PROP_STREAMING_PER_NODE_PAR_OPS; /** * JDBC connection implementation. @@ -118,6 +127,21 @@ public class JdbcConnection implements Connection { /** Distributed joins flag. */ private boolean distributedJoins; + /** Make this connection streaming oriented, and prepared statements - data streamer aware. */ + private final boolean stream; + + /** Auto flush frequency for streaming. */ + private final long streamFlushTimeout; + + /** Node buffer size for data streamer. */ + private final int streamNodeBufSize; + + /** Parallel ops count per node for data streamer. */ + private final int streamNodeParOps; + + /** Allow overwrites for duplicate keys on streamed {@code INSERT}s. */ + private final boolean streamAllowOverwrite; + /** Statements. */ final Set statements = new HashSet<>(); @@ -139,6 +163,14 @@ public JdbcConnection(String url, Properties props) throws SQLException { this.collocatedQry = Boolean.parseBoolean(props.getProperty(PROP_COLLOCATED)); this.distributedJoins = Boolean.parseBoolean(props.getProperty(PROP_DISTRIBUTED_JOINS)); + stream = Boolean.parseBoolean(props.getProperty(PROP_STREAMING)); + streamAllowOverwrite = Boolean.parseBoolean(props.getProperty(PROP_STREAMING_ALLOW_OVERWRITE)); + streamFlushTimeout = Long.parseLong(props.getProperty(PROP_STREAMING_FLUSH_FREQ, "0")); + streamNodeBufSize = Integer.parseInt(props.getProperty(PROP_STREAMING_PER_NODE_BUF_SIZE, + String.valueOf(IgniteDataStreamer.DFLT_PER_NODE_BUFFER_SIZE))); + streamNodeParOps = Integer.parseInt(props.getProperty(PROP_STREAMING_PER_NODE_PAR_OPS, + String.valueOf(IgniteDataStreamer.DFLT_MAX_PARALLEL_OPS))); + String nodeIdProp = props.getProperty(PROP_NODE_ID); if (nodeIdProp != null) @@ -291,6 +323,14 @@ private IgniteConfiguration loadConfiguration(String cfgUrl) { closed = true; + for (Iterator it = statements.iterator(); it.hasNext();) { + JdbcStatement stmt = it.next(); + + stmt.closeInternal(); + + it.remove(); + } + IgniteNodeFuture fut = NODES.get(cfg); if (fut != null && fut.release()) { @@ -299,14 +339,6 @@ private IgniteConfiguration loadConfiguration(String cfgUrl) { if (ignite != null) ignite.close(); } - - for (Iterator it = statements.iterator(); it.hasNext();) { - JdbcStatement stmt = it.next(); - - stmt.closeInternal(); - - it.remove(); - } } /** {@inheritDoc} */ @@ -487,7 +519,18 @@ private IgniteConfiguration loadConfiguration(String cfgUrl) { if (resSetHoldability != HOLD_CURSORS_OVER_COMMIT) throw new SQLFeatureNotSupportedException("Invalid holdability (transactions are not supported)."); - JdbcPreparedStatement stmt = new JdbcPreparedStatement(this, sql); + JdbcPreparedStatement stmt; + + if (!stream) + stmt = new JdbcPreparedStatement(this, sql); + else { + PreparedStatement nativeStmt = prepareNativeStatement(sql); + + IgniteDataStreamer streamer = ((IgniteEx) ignite).context().query().createStreamer(cacheName, + nativeStmt, streamFlushTimeout, streamNodeBufSize, streamNodeParOps, streamAllowOverwrite); + + stmt = new JdbcStreamedPreparedStatement(this, sql, streamer, nativeStmt); + } statements.add(stmt); @@ -646,12 +689,17 @@ private IgniteConfiguration loadConfiguration(String cfgUrl) { /** {@inheritDoc} */ @Override public void setSchema(String schema) throws SQLException { - cacheName = schema; + assert ignite instanceof IgniteEx; + + cacheName = ((IgniteEx)ignite).context().query().space(schema); } /** {@inheritDoc} */ + @SuppressWarnings("unchecked") @Override public String getSchema() throws SQLException { - return cacheName; + String sqlSchema = ignite.cache(cacheName).getConfiguration(CacheConfiguration.class).getSqlSchema(); + + return U.firstNotNull(sqlSchema, cacheName, ""); } /** {@inheritDoc} */ @@ -749,7 +797,7 @@ JdbcStatement createStatement0() throws SQLException { */ PreparedStatement prepareNativeStatement(String sql) throws SQLException { return ((IgniteCacheProxy) ignite().cache(cacheName())).context() - .kernalContext().query().prepareNativeStatement(cacheName(), sql); + .kernalContext().query().prepareNativeStatement(getSchema(), sql); } /** diff --git a/modules/core/src/main/java/org/apache/ignite/internal/jdbc2/JdbcPreparedStatement.java b/modules/core/src/main/java/org/apache/ignite/internal/jdbc2/JdbcPreparedStatement.java index 57badd2df728e..54e58e9900a2a 100644 --- a/modules/core/src/main/java/org/apache/ignite/internal/jdbc2/JdbcPreparedStatement.java +++ b/modules/core/src/main/java/org/apache/ignite/internal/jdbc2/JdbcPreparedStatement.java @@ -17,12 +17,28 @@ package org.apache.ignite.internal.jdbc2; -import java.io.*; -import java.math.*; -import java.net.*; -import java.sql.*; +import java.io.InputStream; +import java.io.Reader; +import java.math.BigDecimal; +import java.net.URL; +import java.sql.Array; +import java.sql.Blob; +import java.sql.Clob; import java.sql.Date; -import java.util.*; +import java.sql.NClob; +import java.sql.ParameterMetaData; +import java.sql.PreparedStatement; +import java.sql.Ref; +import java.sql.ResultSet; +import java.sql.ResultSetMetaData; +import java.sql.RowId; +import java.sql.SQLException; +import java.sql.SQLFeatureNotSupportedException; +import java.sql.SQLXML; +import java.sql.Time; +import java.sql.Timestamp; +import java.util.ArrayList; +import java.util.Calendar; /** * JDBC prepared statement implementation. @@ -31,10 +47,8 @@ public class JdbcPreparedStatement extends JdbcStatement implements PreparedStat /** SQL query. */ private final String sql; - /** - * H2's parsed statement to retrieve metadata from. - */ - private PreparedStatement nativeStatement; + /** H2's parsed statement to retrieve metadata from. */ + PreparedStatement nativeStatement; /** * Creates new prepared statement. @@ -55,8 +69,6 @@ public class JdbcPreparedStatement extends JdbcStatement implements PreparedStat throw new SQLFeatureNotSupportedException("Adding new SQL command to batch not supported for prepared statement."); } - - /** {@inheritDoc} */ @Override public ResultSet executeQuery() throws SQLException { ensureNotClosed(); diff --git a/modules/core/src/main/java/org/apache/ignite/internal/jdbc2/JdbcStatement.java b/modules/core/src/main/java/org/apache/ignite/internal/jdbc2/JdbcStatement.java index 56210580f2dc9..538a68a2294d6 100644 --- a/modules/core/src/main/java/org/apache/ignite/internal/jdbc2/JdbcStatement.java +++ b/modules/core/src/main/java/org/apache/ignite/internal/jdbc2/JdbcStatement.java @@ -142,7 +142,7 @@ public class JdbcStatement implements Statement { updateCnt = -1; - return doUpdate(sql, getArgs()); + return Long.valueOf(doUpdate(sql, getArgs())).intValue(); } /** @@ -150,9 +150,9 @@ public class JdbcStatement implements Statement { * @param sql SQL query. * @param args Update arguments. * @return Number of affected items. - * @throws SQLException + * @throws SQLException If failed. */ - int doUpdate(String sql, Object[] args) throws SQLException { + long doUpdate(String sql, Object[] args) throws SQLException { if (F.isEmpty(sql)) throw new SQLException("SQL query is empty"); @@ -174,11 +174,7 @@ int doUpdate(String sql, Object[] args) throws SQLException { JdbcQueryTaskV2.QueryResult qryRes = loc ? qryTask.call() : ignite.compute(ignite.cluster().forNodeId(nodeId)).call(qryTask); - Long res = updateCounterFromQueryResult(qryRes.getRows()); - - updateCnt = res; - - return res.intValue(); + return updateCnt = updateCounterFromQueryResult(qryRes.getRows()); } catch (IgniteSQLException e) { throw e.toJdbcException(); @@ -196,12 +192,12 @@ int doUpdate(String sql, Object[] args) throws SQLException { * @return update counter, if found * @throws SQLException if getting an update counter from result proved to be impossible. */ - private static Long updateCounterFromQueryResult(List> rows) throws SQLException { + private static long updateCounterFromQueryResult(List> rows) throws SQLException { if (F.isEmpty(rows)) - return 0L; + return -1; if (rows.size() != 1) - throw new SQLException("Expected number of rows of 1 for update operation"); + throw new SQLException("Expected fetch size of 1 for update operation"); List row = rows.get(0); @@ -213,7 +209,7 @@ private static Long updateCounterFromQueryResult(List> rows) throws SQLE if (!(objRes instanceof Long)) throw new SQLException("Unexpected update result type"); - return (Long) objRes; + return (Long)objRes; } /** {@inheritDoc} */ diff --git a/modules/core/src/main/java/org/apache/ignite/internal/jdbc2/JdbcStreamedPreparedStatement.java b/modules/core/src/main/java/org/apache/ignite/internal/jdbc2/JdbcStreamedPreparedStatement.java new file mode 100644 index 0000000000000..019923f921351 --- /dev/null +++ b/modules/core/src/main/java/org/apache/ignite/internal/jdbc2/JdbcStreamedPreparedStatement.java @@ -0,0 +1,59 @@ +/* + * 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.jdbc2; + +import java.sql.PreparedStatement; +import java.sql.SQLException; +import org.apache.ignite.IgniteDataStreamer; +import org.apache.ignite.internal.IgniteEx; + +/** + * Prepared statement associated with a data streamer. + */ +class JdbcStreamedPreparedStatement extends JdbcPreparedStatement { + /** */ + private final IgniteDataStreamer streamer; + + /** + * Creates new prepared statement. + * + * @param conn Connection. + * @param sql SQL query. + * @param streamer Data streamer to use with this statement. Will be closed on statement close. + */ + JdbcStreamedPreparedStatement(JdbcConnection conn, String sql, IgniteDataStreamer streamer, + PreparedStatement nativeStmt) { + super(conn, sql); + + this.streamer = streamer; + + nativeStatement = nativeStmt; + } + + /** {@inheritDoc} */ + @Override void closeInternal() throws SQLException { + streamer.close(false); + + super.closeInternal(); + } + + /** {@inheritDoc} */ + @Override long doUpdate(String sql, Object[] args) throws SQLException { + return ((IgniteEx)conn.ignite()).context().query().streamUpdateQuery(conn.cacheName(), streamer, sql, args); + } +} diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/query/GridQueryIndexing.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/query/GridQueryIndexing.java index 13c1b3d56d801..af1133cc5224c 100644 --- a/modules/core/src/main/java/org/apache/ignite/internal/processors/query/GridQueryIndexing.java +++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/query/GridQueryIndexing.java @@ -23,6 +23,7 @@ import java.util.List; import javax.cache.Cache; import org.apache.ignite.IgniteCheckedException; +import org.apache.ignite.IgniteDataStreamer; import org.apache.ignite.cache.query.QueryCursor; import org.apache.ignite.cache.query.SqlFieldsQuery; import org.apache.ignite.cache.query.SqlQuery; @@ -98,6 +99,19 @@ public GridQueryFieldsResult queryLocalSqlFields(@Nullable String spaceName, Str Collection params, IndexingQueryFilter filter, boolean enforceJoinOrder, int timeout, GridQueryCancel cancel) throws IgniteCheckedException; + /** + * Perform a MERGE statement using data streamer as receiver. + * + * @param spaceName Space name. + * @param qry Query. + * @param params Query parameters. + * @param streamer Data streamer to feed data to. + * @return Query result. + * @throws IgniteCheckedException If failed. + */ + public long streamUpdateQuery(@Nullable final String spaceName, final String qry, + @Nullable final Object[] params, IgniteDataStreamer streamer) throws IgniteCheckedException; + /** * Executes regular query. * @@ -238,8 +252,29 @@ public void store(@Nullable String spaceName, GridQueryTypeDescriptor type, Cach */ public PreparedStatement prepareNativeStatement(String schema, String sql) throws SQLException; + /** + * Gets space name from database schema. + * + * @param schemaName Schema name. Could not be null. Could be empty. + * @return Space name. Could be null. + */ + public String space(String schemaName); + /** * Cancels all executing queries. */ public void cancelAllQueries(); + + /** + * @param spaceName Space name. + * @param nativeStmt Native statement. + * @param autoFlushFreq Automatic data flushing frequency, disabled if {@code 0}. + * @param nodeBufSize Per node buffer size - see {@link IgniteDataStreamer#perNodeBufferSize(int)} + * @param nodeParOps Per node parallel ops count - see {@link IgniteDataStreamer#perNodeParallelOperations(int)} + * @param allowOverwrite Overwrite existing cache values on key duplication. + * @return {@link IgniteDataStreamer} tailored to specific needs of given native statement based on its metadata; + * {@code null} if given statement is a query. + */ + public IgniteDataStreamer createStreamer(String spaceName, PreparedStatement nativeStmt, long autoFlushFreq, + int nodeBufSize, int nodeParOps, boolean allowOverwrite); } \ No newline at end of file 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 806fc83398be9..47cb13d283aab 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 @@ -17,12 +17,11 @@ package org.apache.ignite.internal.processors.query; -import java.sql.PreparedStatement; -import java.sql.SQLException; -import java.util.concurrent.TimeUnit; import java.lang.reflect.Field; import java.lang.reflect.Method; import java.math.BigDecimal; +import java.sql.PreparedStatement; +import java.sql.SQLException; import java.sql.Time; import java.sql.Timestamp; import java.util.ArrayList; @@ -40,9 +39,11 @@ import java.util.UUID; import java.util.concurrent.ConcurrentMap; import java.util.concurrent.ExecutorService; +import java.util.concurrent.TimeUnit; import javax.cache.Cache; import javax.cache.CacheException; import org.apache.ignite.IgniteCheckedException; +import org.apache.ignite.IgniteDataStreamer; import org.apache.ignite.IgniteException; import org.apache.ignite.IgniteSystemProperties; import org.apache.ignite.binary.BinaryField; @@ -837,6 +838,36 @@ public QueryCursor> queryTwoStep(final GridCacheContext cctx, final } } + /** + * @param spaceName Cache name. + * @param streamer Data streamer. + * @param qry Query. + * @return Iterator. + */ + public long streamUpdateQuery(@Nullable final String spaceName, + final IgniteDataStreamer streamer, final String qry, final Object[] args) { + assert streamer != null; + + if (!busyLock.enterBusy()) + throw new IllegalStateException("Failed to execute query (grid is stopping)."); + + try { + GridCacheContext cctx = ctx.cache().cache(spaceName).context(); + + return executeQuery(GridCacheQueryType.SQL_FIELDS, qry, cctx, new IgniteOutClosureX() { + @Override public Long applyx() throws IgniteCheckedException { + return idx.streamUpdateQuery(spaceName, qry, args, streamer); + } + }, true); + } + catch (IgniteCheckedException e) { + throw new CacheException(e); + } + finally { + busyLock.leaveBusy(); + } + } + /** * @param cctx Cache context. * @param qry Query. @@ -960,7 +991,6 @@ private void sendQueryExecutedEvent(String sqlQry, Object[] params) { } /** - * * @param schema Schema. * @param sql Query. * @return {@link PreparedStatement} from underlying engine to supply metadata to Prepared - most likely H2. @@ -971,6 +1001,31 @@ public PreparedStatement prepareNativeStatement(String schema, String sql) throw return idx.prepareNativeStatement(schema, sql); } + /** + * @param schema Schema name. + * @return space (cache) name from schema name. + */ + public String space(String schema) throws SQLException { + checkxEnabled(); + + return idx.space(schema); + } + + /** + * @param spaceName Space name. + * @param nativeStmt Native statement. + * @param autoFlushFreq Automatic data flushing frequency, disabled if {@code 0}. + * @param nodeBufSize Per node buffer size - see {@link IgniteDataStreamer#perNodeBufferSize(int)} + * @param nodeParOps Per node parallel ops count - see {@link IgniteDataStreamer#perNodeParallelOperations(int)} + * @param allowOverwrite Overwrite existing cache values on key duplication. + * @see IgniteDataStreamer#allowOverwrite + * @return {@link IgniteDataStreamer} tailored to specific needs of given native statement based on its metadata. + */ + public IgniteDataStreamer createStreamer(String spaceName, PreparedStatement nativeStmt, long autoFlushFreq, + int nodeBufSize, int nodeParOps, boolean allowOverwrite) { + return idx.createStreamer(spaceName, nativeStmt, autoFlushFreq, nodeBufSize, nodeParOps, allowOverwrite); + } + /** * @param timeout Timeout. * @param timeUnit Time unit. 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 e4fe91eb7c36f..5273d188140f9 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 @@ -21,7 +21,6 @@ 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; @@ -40,6 +39,7 @@ import org.apache.ignite.IgniteCache; import org.apache.ignite.IgniteCheckedException; +import org.apache.ignite.IgniteDataStreamer; import org.apache.ignite.IgniteException; import org.apache.ignite.IgniteLogger; import org.apache.ignite.binary.BinaryArrayIdentityResolver; @@ -60,7 +60,17 @@ 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.*; +import org.apache.ignite.internal.processors.query.h2.dml.DmlEntryProcessor; +import org.apache.ignite.internal.processors.query.h2.dml.DmlEntryProcessorArgs; +import org.apache.ignite.internal.processors.query.h2.dml.FastUpdateArgument; +import org.apache.ignite.internal.processors.query.h2.dml.FastUpdateArguments; +import org.apache.ignite.internal.processors.query.h2.dml.InsertProcessor; +import org.apache.ignite.internal.processors.query.h2.dml.MergeProcessor; +import org.apache.ignite.internal.processors.query.h2.dml.ModifyingEntryProcessor; +import org.apache.ignite.internal.processors.query.h2.dml.UpdateMode; +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.dml.UpdateProcessor; import org.apache.ignite.internal.processors.query.h2.opt.GridH2RowDescriptor; import org.apache.ignite.internal.processors.query.h2.sql.GridSqlQueryParser; import org.apache.ignite.internal.util.GridBoundedConcurrentLinkedHashMap; @@ -131,7 +141,7 @@ public class DmlStatementsProcessor { * @return Update result (modified items count and failed keys). * @throws IgniteCheckedException if failed. */ - private long updateSqlFields(String spaceName, PreparedStatement stmt, SqlFieldsQuery fieldsQry, + private UpdateResult updateSqlFields(String spaceName, PreparedStatement stmt, SqlFieldsQuery fieldsQry, boolean loc, IndexingQueryFilter filters, GridQueryCancel cancel) throws IgniteCheckedException { Object[] errKeys = null; @@ -161,23 +171,27 @@ else if (!opCtx.isKeepBinary()) UpdateResult r; try { - r = executeUpdateStatement(cctx, stmt, fieldsQry, loc, filters, - cancel, errKeys); + r = executeUpdateStatement(cctx, stmt, fieldsQry, loc, filters, cancel, errKeys); } finally { cctx.operationContextPerCall(opCtx); } - if (F.isEmpty(r.errKeys)) - return r.cnt + items; - else { - items += r.cnt; - errKeys = r.errKeys; - } + items += r.cnt; + errKeys = r.errKeys; + + if (F.isEmpty(errKeys)) + break; + } + + if (F.isEmpty(errKeys)) { + if (items == 1L) + return UpdateResult.ONE; + else if (items == 0L) + return UpdateResult.ZERO; } - throw new IgniteSQLException("Failed to update or delete some keys: " + Arrays.deepToString(errKeys), - IgniteQueryErrorCode.CONCURRENT_UPDATE); + return new UpdateResult(items, errKeys); } /** @@ -191,9 +205,14 @@ else if (!opCtx.isKeepBinary()) @SuppressWarnings("unchecked") QueryCursorImpl> updateSqlFieldsTwoStep(String spaceName, PreparedStatement stmt, SqlFieldsQuery fieldsQry, GridQueryCancel cancel) throws IgniteCheckedException { - long res = updateSqlFields(spaceName, stmt, fieldsQry, false, null, cancel); + UpdateResult res = updateSqlFields(spaceName, stmt, fieldsQry, false, null, cancel); - return cursorForUpdateResult(res); + QueryCursorImpl> resCur = (QueryCursorImpl>)new QueryCursorImpl(Collections.singletonList + (Collections.singletonList(res.cnt)), null, false); + + resCur.fieldsMeta(UPDATE_RESULT_META); + + return resCur; } /** @@ -208,10 +227,93 @@ QueryCursorImpl> updateSqlFieldsTwoStep(String spaceName, PreparedStatem @SuppressWarnings("unchecked") GridQueryFieldsResult updateLocalSqlFields(String spaceName, PreparedStatement stmt, SqlFieldsQuery fieldsQry, IndexingQueryFilter filters, GridQueryCancel cancel) throws IgniteCheckedException { - long res = updateSqlFields(spaceName, stmt, fieldsQry, true, filters, cancel); + UpdateResult res = updateSqlFields(spaceName, stmt, fieldsQry, true, filters, cancel); return new GridQueryFieldsResultAdapter(UPDATE_RESULT_META, - new IgniteSingletonIterator(Collections.singletonList(res))); + new IgniteSingletonIterator(Collections.singletonList(res.cnt))); + } + + /** + * Perform given statement against given data streamer. Only rows based INSERT and MERGE are supported + * as well as key bound UPDATE and DELETE (ones with filter {@code WHERE _key = ?}). + * + * @param streamer Streamer to feed data to. + * @param stmt Statement. + * @param args Statement arguments. + * @return Number of rows in given statement for INSERT and MERGE, {@code 1} otherwise. + * @throws IgniteCheckedException if failed. + */ + @SuppressWarnings({"unchecked", "ConstantConditions"}) + long streamUpdateQuery(IgniteDataStreamer streamer, PreparedStatement stmt, Object[] args) + throws IgniteCheckedException { + args = U.firstNotNull(args, X.EMPTY_OBJECT_ARRAY); + + Prepared p = GridSqlQueryParser.prepared((JdbcPreparedStatement)stmt); + + assert p != null; + + UpdatePlan plan = UpdatePlanBuilder.planForStatement(p, null); + + if (!F.eq(streamer.cacheName(), plan.tbl.rowDescriptor().context().namex())) + throw new IgniteSQLException("Cross cache streaming is not supported, please specify cache explicitly" + + " in connection options", IgniteQueryErrorCode.UNSUPPORTED_OPERATION); + + if (plan.mode == UpdateMode.INSERT && plan.rowsNum > 0) { + assert plan.isLocSubqry; + + final GridCacheContext cctx = plan.tbl.rowDescriptor().context(); + + QueryCursorImpl> cur; + + final ArrayList> data = new ArrayList<>(plan.rowsNum); + + final GridQueryFieldsResult res = indexing.queryLocalSqlFields(cctx.name(), plan.selectQry, + F.asList(args), null, false, 0, null); + + QueryCursorImpl> stepCur = new QueryCursorImpl<>(new Iterable>() { + @Override public Iterator> iterator() { + try { + return new GridQueryCacheObjectsIterator(res.iterator(), cctx, cctx.keepBinary()); + } + catch (IgniteCheckedException e) { + throw new IgniteException(e); + } + } + }, null); + + data.addAll(stepCur.getAll()); + + cur = new QueryCursorImpl<>(new Iterable>() { + @Override public Iterator> iterator() { + return data.iterator(); + } + }, null); + + GridH2RowDescriptor desc = plan.tbl.rowDescriptor(); + + if (plan.rowsNum == 1) { + IgniteBiTuple t = rowToKeyValue(cctx, cur.iterator().next(), plan); + + streamer.addData(t.getKey(), t.getValue()); + + return 1; + } + + Map rows = new LinkedHashMap<>(plan.rowsNum); + + for (List row : cur) { + final IgniteBiTuple t = rowToKeyValue(cctx, row, plan); + + rows.put(t.getKey(), t.getValue()); + } + + streamer.addData(rows); + + return rows.size(); + } + else + throw new IgniteSQLException("Only tuple based INSERT statements are supported in streaming mode", + IgniteQueryErrorCode.UNSUPPORTED_OPERATION); } /** @@ -219,31 +321,21 @@ GridQueryFieldsResult updateLocalSqlFields(String spaceName, PreparedStatement s * @param cctx Cache context. * @param prepStmt Prepared statement for DML query. * @param filters Space name and key filter. - * @param failedKeys Keys to restrict UPDATE and DELETE operations with. Null or empty array means no restriction. - * @return Pair [number of successfully processed items; keys that have failed to be processed] + * @param failedKeys Keys to restrict UPDATE and DELETE operations with. Null or empty array means no restriction. @return Pair [number of successfully processed items; keys that have failed to be processed] * @throws IgniteCheckedException if failed. */ @SuppressWarnings({"ConstantConditions", "unchecked"}) private UpdateResult executeUpdateStatement(final GridCacheContext cctx, PreparedStatement prepStmt, - SqlFieldsQuery fieldsQry, boolean loc, IndexingQueryFilter filters, GridQueryCancel cancel, Object[] failedKeys) - throws IgniteCheckedException { + SqlFieldsQuery fieldsQry, boolean loc, IndexingQueryFilter filters, GridQueryCancel cancel, + Object[] failedKeys) throws IgniteCheckedException { Integer errKeysPos = null; - Object[] params = fieldsQry.getArgs(); - - if (!F.isEmpty(failedKeys)) { - int paramsCnt = F.isEmpty(params) ? 0 : params.length; - params = Arrays.copyOf(U.firstNotNull(params, X.EMPTY_OBJECT_ARRAY), paramsCnt + 1); - params[paramsCnt] = failedKeys; - errKeysPos = paramsCnt; // Last position - } - UpdatePlan plan = getPlanForStatement(cctx.name(), prepStmt, errKeysPos); if (plan.fastUpdateArgs != null) { assert F.isEmpty(failedKeys) && errKeysPos == null; - return new UpdateResult(doSingleUpdate(plan, params), X.EMPTY_OBJECT_ARRAY); + return doFastUpdate(plan, fieldsQry.getArgs()); } assert !F.isEmpty(plan.rows) ^ !F.isEmpty(plan.selectQry); @@ -256,7 +348,7 @@ private UpdateResult executeUpdateStatement(final GridCacheContext cctx, Prepare assert !F.isEmpty(plan.selectQry); SqlFieldsQuery newFieldsQry = new SqlFieldsQuery(plan.selectQry, fieldsQry.isCollocated()) - .setArgs(params) + .setArgs(fieldsQry.getArgs()) .setDistributedJoins(fieldsQry.isDistributedJoins()) .setEnforceJoinOrder(fieldsQry.isEnforceJoinOrder()) .setLocal(fieldsQry.isLocal()) @@ -266,8 +358,8 @@ private UpdateResult executeUpdateStatement(final GridCacheContext cctx, Prepare cur = indexing.queryTwoStep(cctx, newFieldsQry, cancel); } else if (F.isEmpty(plan.rows)) { - final GridQueryFieldsResult res = indexing.queryLocalSqlFields(cctx.name(), plan.selectQry, F.asList(params), - filters, fieldsQry.isEnforceJoinOrder(), fieldsQry.getTimeout(), cancel); + final GridQueryFieldsResult res = indexing.queryLocalSqlFields(cctx.name(), plan.selectQry, + F.asList(fieldsQry.getArgs()), filters, fieldsQry.isEnforceJoinOrder(), fieldsQry.getTimeout(), cancel); QueryCursorImpl> resCur = new QueryCursorImpl<>(new Iterable>() { @Override public Iterator> iterator() { @@ -374,38 +466,41 @@ private UpdatePlan getPlanForStatement(String spaceName, PreparedStatement prepS /** * Perform single cache operation based on given args. - * @param params Query parameters. + * @param args Query parameters. * @return 1 if an item was affected, 0 otherwise. * @throws IgniteCheckedException if failed. */ @SuppressWarnings({"unchecked", "ConstantConditions"}) - private static long doSingleUpdate(UpdatePlan plan, Object[] params) throws IgniteCheckedException { + private static UpdateResult doFastUpdate(UpdatePlan plan, Object[] args) throws IgniteCheckedException { GridCacheContext cctx = plan.tbl.rowDescriptor().context(); FastUpdateArguments singleUpdate = plan.fastUpdateArgs; assert singleUpdate != null; - int res; + boolean valBounded = (singleUpdate.val != FastUpdateArguments.NULL_ARGUMENT); - Object key = singleUpdate.key.apply(params); - Object val = singleUpdate.val.apply(params); - Object newVal = singleUpdate.newVal.apply(params); + if (singleUpdate.newVal != FastUpdateArguments.NULL_ARGUMENT) { // Single item UPDATE + Object key = singleUpdate.key.apply(args); + Object newVal = singleUpdate.newVal.apply(args); - if (newVal != null) { // Single item UPDATE - if (val == null) // No _val bound in source query - res = cctx.cache().replace(key, newVal) ? 1 : 0; + if (valBounded) { + Object val = singleUpdate.val.apply(args); + + return (cctx.cache().replace(key, val, newVal) ? UpdateResult.ONE : UpdateResult.ZERO); + } else - res = cctx.cache().replace(key, val, newVal) ? 1 : 0; + return (cctx.cache().replace(key, newVal) ? UpdateResult.ONE : UpdateResult.ZERO); } else { // Single item DELETE - if (val == null) // No _val bound in source query - res = cctx.cache().remove(key) ? 1 : 0; + Object key = singleUpdate.key.apply(args); + Object val = singleUpdate.val.apply(args); + + if (singleUpdate.val == FastUpdateArguments.NULL_ARGUMENT) // No _val bound in source query + return cctx.cache().remove(key) ? UpdateResult.ONE : UpdateResult.ZERO; else - res = cctx.cache().remove(key, val) ? 1 : 0; + return cctx.cache().remove(key, val) ? UpdateResult.ONE : UpdateResult.ZERO; } - - return res; } /** @@ -1077,6 +1172,12 @@ private static QueryCursorImpl> cursorForUpdateResult(long itemsCnt) { /** Update result - modifications count and keys to re-run query with, if needed. */ private final static class UpdateResult { + /** Result to return for operations that affected 1 item - mostly to be used for fast updates and deletes. */ + final static UpdateResult ONE = new UpdateResult(1, X.EMPTY_OBJECT_ARRAY); + + /** Result to return for operations that affected 0 items - mostly to be used for fast updates and deletes. */ + final static UpdateResult ZERO = new UpdateResult(0, X.EMPTY_OBJECT_ARRAY); + /** Number of processed items. */ final long cnt; 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 903794310c3c4..bcca0ddee48e6 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 @@ -55,6 +55,7 @@ import javax.cache.Cache; import javax.cache.CacheException; import org.apache.ignite.IgniteCheckedException; +import org.apache.ignite.IgniteDataStreamer; import org.apache.ignite.IgniteException; import org.apache.ignite.IgniteLogger; import org.apache.ignite.cache.CacheMemoryMode; @@ -134,6 +135,7 @@ import org.h2.api.JavaObjectSerializer; import org.h2.command.CommandInterface; import org.h2.command.Prepared; +import org.h2.command.dml.Insert; import org.h2.engine.Session; import org.h2.engine.SysProperties; import org.h2.index.Index; @@ -427,7 +429,32 @@ private PreparedStatement prepareStatement(Connection c, String sql, boolean use /** {@inheritDoc} */ @Override public PreparedStatement prepareNativeStatement(String schema, String sql) throws SQLException { - return prepareStatement(connectionForSpace(schema), sql, false); + return prepareStatement(connectionForSpace(space(schema)), sql, true); + } + + /** {@inheritDoc} */ + @SuppressWarnings("unchecked") + @Override public IgniteDataStreamer createStreamer(String spaceName, PreparedStatement nativeStmt, + long autoFlushFreq, int nodeBufSize, int nodeParOps, boolean allowOverwrite) { + Prepared prep = GridSqlQueryParser.prepared((JdbcPreparedStatement) nativeStmt); + + if (!(prep instanceof Insert)) + throw new IgniteSQLException("Only INSERT operations are supported in streaming mode", + IgniteQueryErrorCode.UNSUPPORTED_OPERATION); + + IgniteDataStreamer streamer = ctx.grid().dataStreamer(spaceName); + + streamer.autoFlushFrequency(autoFlushFreq); + + streamer.allowOverwrite(allowOverwrite); + + if (nodeBufSize > 0) + streamer.perNodeBufferSize(nodeBufSize); + + if (nodeParOps > 0) + streamer.perNodeParallelOperations(nodeParOps); + + return streamer; } /** @@ -842,6 +869,23 @@ private void removeTable(TableDescriptor tbl) throws IgniteCheckedException { }; } + /** {@inheritDoc} */ + @Override public long streamUpdateQuery(@Nullable String spaceName, String qry, + @Nullable Object[] params, IgniteDataStreamer streamer) throws IgniteCheckedException { + final Connection conn = connectionForSpace(spaceName); + + final PreparedStatement stmt; + + try { + stmt = prepareStatement(conn, qry, true); + } + catch (SQLException e) { + throw new IgniteSQLException(e); + } + + return dmlProc.streamUpdateQuery(streamer, stmt, params); + } + /** * @param rsMeta Metadata. * @return List of fields metadata. @@ -1638,13 +1682,8 @@ private void cleanupStatementCache() { } } - /** - * Gets space name from database schema. - * - * @param schemaName Schema name. Could not be null. Could be empty. - * @return Space name. Could be null. - */ - public String space(String schemaName) { + /** {@inheritDoc} */ + @Override public String space(String schemaName) { assert schemaName != null; Schema schema = schemas.get(schemaName);