IGNITE-6337 .NET: Thin client: SQL queries
authorPavel Tupitsyn <ptupitsyn@apache.org>
Fri, 17 Nov 2017 14:16:38 +0000 (17:16 +0300)
committerPavel Tupitsyn <ptupitsyn@apache.org>
Fri, 17 Nov 2017 14:16:38 +0000 (17:16 +0300)
This closes #2832

33 files changed:
modules/core/src/main/java/org/apache/ignite/internal/processors/odbc/jdbc/JdbcQueryExecuteRequest.java
modules/core/src/main/java/org/apache/ignite/internal/processors/odbc/jdbc/JdbcStatementType.java
modules/core/src/main/java/org/apache/ignite/internal/processors/platform/cache/PlatformCache.java
modules/core/src/main/java/org/apache/ignite/internal/processors/platform/client/ClientMessageParser.java
modules/core/src/main/java/org/apache/ignite/internal/processors/platform/client/cache/ClientCacheEntryQueryCursor.java [new file with mode: 0644]
modules/core/src/main/java/org/apache/ignite/internal/processors/platform/client/cache/ClientCacheFieldsQueryCursor.java [new file with mode: 0644]
modules/core/src/main/java/org/apache/ignite/internal/processors/platform/client/cache/ClientCacheQueryCursor.java [moved from modules/core/src/main/java/org/apache/ignite/internal/processors/platform/client/cache/ClientCacheScanQueryCursor.java with 81% similarity]
modules/core/src/main/java/org/apache/ignite/internal/processors/platform/client/cache/ClientCacheQueryNextPageRequest.java [moved from modules/core/src/main/java/org/apache/ignite/internal/processors/platform/client/cache/ClientCacheScanQueryNextPageRequest.java with 83% similarity]
modules/core/src/main/java/org/apache/ignite/internal/processors/platform/client/cache/ClientCacheQueryNextPageResponse.java [moved from modules/core/src/main/java/org/apache/ignite/internal/processors/platform/client/cache/ClientCacheScanQueryNextPageResponse.java with 86% similarity]
modules/core/src/main/java/org/apache/ignite/internal/processors/platform/client/cache/ClientCacheQueryResponse.java [moved from modules/core/src/main/java/org/apache/ignite/internal/processors/platform/client/cache/ClientCacheScanQueryResponse.java with 88% similarity]
modules/core/src/main/java/org/apache/ignite/internal/processors/platform/client/cache/ClientCacheRequest.java
modules/core/src/main/java/org/apache/ignite/internal/processors/platform/client/cache/ClientCacheScanQueryRequest.java
modules/core/src/main/java/org/apache/ignite/internal/processors/platform/client/cache/ClientCacheSqlFieldsQueryRequest.java [new file with mode: 0644]
modules/core/src/main/java/org/apache/ignite/internal/processors/platform/client/cache/ClientCacheSqlFieldsQueryResponse.java [new file with mode: 0644]
modules/core/src/main/java/org/apache/ignite/internal/processors/platform/client/cache/ClientCacheSqlQueryRequest.java [new file with mode: 0644]
modules/platforms/dotnet/Apache.Ignite.Core.Tests/Apache.Ignite.Core.Tests.csproj
modules/platforms/dotnet/Apache.Ignite.Core.Tests/Client/Cache/CacheTestNoMeta.cs
modules/platforms/dotnet/Apache.Ignite.Core.Tests/Client/Cache/Person.cs
modules/platforms/dotnet/Apache.Ignite.Core.Tests/Client/Cache/SqlQueryTest.cs [new file with mode: 0644]
modules/platforms/dotnet/Apache.Ignite.Core.Tests/Client/ClientTestBase.cs
modules/platforms/dotnet/Apache.Ignite.Core/Apache.Ignite.Core.csproj
modules/platforms/dotnet/Apache.Ignite.Core/Cache/Query/IFieldsQueryCursor.cs [new file with mode: 0644]
modules/platforms/dotnet/Apache.Ignite.Core/Client/Cache/ICacheClient.cs
modules/platforms/dotnet/Apache.Ignite.Core/Impl/Cache/Query/FieldsQueryCursor.cs
modules/platforms/dotnet/Apache.Ignite.Core/Impl/Cache/Query/PlatformQueryQursorBase.cs
modules/platforms/dotnet/Apache.Ignite.Core/Impl/Cache/Query/QueryCursor.cs
modules/platforms/dotnet/Apache.Ignite.Core/Impl/Cache/Query/QueryCursorBase.cs
modules/platforms/dotnet/Apache.Ignite.Core/Impl/Client/Cache/CacheClient.cs
modules/platforms/dotnet/Apache.Ignite.Core/Impl/Client/Cache/Query/ClientFieldsQueryCursor.cs [new file with mode: 0644]
modules/platforms/dotnet/Apache.Ignite.Core/Impl/Client/Cache/Query/ClientQueryCursor.cs
modules/platforms/dotnet/Apache.Ignite.Core/Impl/Client/Cache/Query/ClientQueryCursorBase.cs [new file with mode: 0644]
modules/platforms/dotnet/Apache.Ignite.Core/Impl/Client/Cache/Query/StatementType.cs [new file with mode: 0644]
modules/platforms/dotnet/Apache.Ignite.Core/Impl/Client/ClientOp.cs

index 1c6262e..3e54fc8 100644 (file)
@@ -156,7 +156,7 @@ public class JdbcQueryExecuteRequest extends JdbcRequest {
 
         try {
             if (reader.available() > 0)
-                stmtType = JdbcStatementType.values()[reader.readByte()];
+                stmtType = JdbcStatementType.fromOrdinal(reader.readByte());
             else
                 stmtType = JdbcStatementType.ANY_STATEMENT_TYPE;
         }
index aec2d12..ebe303f 100644 (file)
@@ -29,4 +29,17 @@ public enum JdbcStatementType {
 
     /** DML / DDL statement type. */
     UPDATE_STMT_TYPE;
+
+    /** Enumerated values. */
+    private static final JdbcStatementType[] VALS = values();
+
+    /**
+     * Efficiently gets enumerated value from its ordinal.
+     *
+     * @param ord Ordinal value.
+     * @return Enumerated value or {@code null} if ordinal out of range.
+     */
+    public static JdbcStatementType fromOrdinal(int ord) {
+        return ord >= 0 && ord < VALS.length ? VALS[ord] : null;
+    }
 }
index 0e227f5..bbdd6d2 100644 (file)
@@ -930,7 +930,7 @@ public class PlatformCache extends PlatformAbstractTarget {
      * @param reader Reader.
      * @return Arguments.
      */
-    @Nullable private Object[] readQueryArgs(BinaryRawReaderEx reader) {
+    @Nullable public static Object[] readQueryArgs(BinaryRawReaderEx reader) {
         int cnt = reader.readInt();
 
         if (cnt > 0) {
index 4ad6a90..626b7ff 100644 (file)
@@ -54,14 +54,16 @@ import org.apache.ignite.internal.processors.platform.client.cache.ClientCacheGe
 import org.apache.ignite.internal.processors.platform.client.cache.ClientCachePutAllRequest;
 import org.apache.ignite.internal.processors.platform.client.cache.ClientCachePutIfAbsentRequest;
 import org.apache.ignite.internal.processors.platform.client.cache.ClientCachePutRequest;
-import org.apache.ignite.internal.processors.platform.client.cache.ClientCacheRemoveIfEqualsRequest;
+import org.apache.ignite.internal.processors.platform.client.cache.ClientCacheQueryNextPageRequest;
 import org.apache.ignite.internal.processors.platform.client.cache.ClientCacheRemoveAllRequest;
-import org.apache.ignite.internal.processors.platform.client.cache.ClientCacheRemoveKeysRequest;
+import org.apache.ignite.internal.processors.platform.client.cache.ClientCacheRemoveIfEqualsRequest;
 import org.apache.ignite.internal.processors.platform.client.cache.ClientCacheRemoveKeyRequest;
+import org.apache.ignite.internal.processors.platform.client.cache.ClientCacheRemoveKeysRequest;
 import org.apache.ignite.internal.processors.platform.client.cache.ClientCacheReplaceIfEqualsRequest;
 import org.apache.ignite.internal.processors.platform.client.cache.ClientCacheReplaceRequest;
-import org.apache.ignite.internal.processors.platform.client.cache.ClientCacheScanQueryNextPageRequest;
 import org.apache.ignite.internal.processors.platform.client.cache.ClientCacheScanQueryRequest;
+import org.apache.ignite.internal.processors.platform.client.cache.ClientCacheSqlFieldsQueryRequest;
+import org.apache.ignite.internal.processors.platform.client.cache.ClientCacheSqlQueryRequest;
 
 /**
  * Thin client message parser.
@@ -172,6 +174,18 @@ public class ClientMessageParser implements ClientListenerMessageParser {
     /** */
     private static final short OP_CACHE_GET_OR_CREATE_WITH_CONFIGURATION = 35;
 
+    /** */
+    private static final short OP_QUERY_SQL = 36;
+
+    /** */
+    private static final short OP_QUERY_SQL_CURSOR_GET_PAGE = 37;
+
+    /** */
+    private static final short OP_QUERY_SQL_FIELDS = 38;
+
+    /** */
+    private static final short OP_QUERY_SQL_FIELDS_CURSOR_GET_PAGE = 39;
+
     /** Marshaller. */
     private final GridBinaryMarshaller marsh;
 
@@ -229,7 +243,7 @@ public class ClientMessageParser implements ClientListenerMessageParser {
                 return new ClientCacheScanQueryRequest(reader);
 
             case OP_QUERY_SCAN_CURSOR_GET_PAGE:
-                return new ClientCacheScanQueryNextPageRequest(reader);
+                return new ClientCacheQueryNextPageRequest(reader);
 
             case OP_RESOURCE_CLOSE:
                 return new ClientResourceCloseRequest(reader);
@@ -311,6 +325,18 @@ public class ClientMessageParser implements ClientListenerMessageParser {
 
             case OP_CACHE_GET_OR_CREATE_WITH_CONFIGURATION:
                 return new ClientCacheGetOrCreateWithConfigurationRequest(reader);
+
+            case OP_QUERY_SQL:
+                return new ClientCacheSqlQueryRequest(reader);
+
+            case OP_QUERY_SQL_CURSOR_GET_PAGE:
+                return new ClientCacheQueryNextPageRequest(reader);
+
+            case OP_QUERY_SQL_FIELDS:
+                return new ClientCacheSqlFieldsQueryRequest(reader);
+
+            case OP_QUERY_SQL_FIELDS_CURSOR_GET_PAGE:
+                return new ClientCacheQueryNextPageRequest(reader);
         }
 
         return new ClientRawRequest(reader.readLong(), ClientStatus.INVALID_OP_CODE,
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/platform/client/cache/ClientCacheEntryQueryCursor.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/platform/client/cache/ClientCacheEntryQueryCursor.java
new file mode 100644 (file)
index 0000000..5269342
--- /dev/null
@@ -0,0 +1,46 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ignite.internal.processors.platform.client.cache;
+
+import org.apache.ignite.cache.query.QueryCursor;
+import org.apache.ignite.internal.binary.BinaryRawWriterEx;
+import org.apache.ignite.internal.processors.platform.client.ClientConnectionContext;
+
+import javax.cache.Cache;
+
+/**
+ * Query cursor holder.
+  */
+class ClientCacheEntryQueryCursor extends ClientCacheQueryCursor<Cache.Entry> {
+    /**
+     * Ctor.
+     *
+     * @param cursor   Cursor.
+     * @param pageSize Page size.
+     * @param ctx      Context.
+     */
+    ClientCacheEntryQueryCursor(QueryCursor<Cache.Entry> cursor, int pageSize, ClientConnectionContext ctx) {
+        super(cursor, pageSize, ctx);
+    }
+
+    /** {@inheritDoc} */
+    @Override void writeEntry(BinaryRawWriterEx writer, Cache.Entry e) {
+        writer.writeObjectDetached(e.getKey());
+        writer.writeObjectDetached(e.getValue());
+    }
+}
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/platform/client/cache/ClientCacheFieldsQueryCursor.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/platform/client/cache/ClientCacheFieldsQueryCursor.java
new file mode 100644 (file)
index 0000000..98b747b
--- /dev/null
@@ -0,0 +1,53 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ignite.internal.processors.platform.client.cache;
+
+import org.apache.ignite.cache.query.FieldsQueryCursor;
+import org.apache.ignite.internal.binary.BinaryRawWriterEx;
+import org.apache.ignite.internal.processors.platform.client.ClientConnectionContext;
+
+import java.util.List;
+
+/**
+ * Query cursor holder.
+  */
+class ClientCacheFieldsQueryCursor extends ClientCacheQueryCursor<List> {
+    /** Column count. */
+    private final int columnCount;
+
+    /**
+     * Ctor.
+     *
+     * @param cursor   Cursor.
+     * @param pageSize Page size.
+     * @param ctx      Context.
+     */
+    ClientCacheFieldsQueryCursor(FieldsQueryCursor<List> cursor, int pageSize, ClientConnectionContext ctx) {
+        super(cursor, pageSize, ctx);
+
+        columnCount = cursor.getColumnsCount();
+    }
+
+    /** {@inheritDoc} */
+    @Override void writeEntry(BinaryRawWriterEx writer, List e) {
+        assert e.size() == columnCount;
+
+        for (Object o : e)
+            writer.writeObjectDetached(o);
+    }
+}
 
 package org.apache.ignite.internal.processors.platform.client.cache;
 
+import org.apache.ignite.cache.query.QueryCursor;
 import org.apache.ignite.internal.binary.BinaryRawWriterEx;
-import org.apache.ignite.internal.processors.cache.query.QueryCursorEx;
 import org.apache.ignite.internal.processors.platform.client.ClientCloseableResource;
 import org.apache.ignite.internal.processors.platform.client.ClientConnectionContext;
 
-import javax.cache.Cache;
 import java.util.Iterator;
 import java.util.concurrent.atomic.AtomicBoolean;
 
 /**
- * Query cursor holder.
+ * Base query cursor holder.
   */
-class ClientCacheScanQueryCursor implements ClientCloseableResource {
+abstract class ClientCacheQueryCursor<T> implements ClientCloseableResource {
     /** Cursor. */
-    private final QueryCursorEx<Cache.Entry> cursor;
+    private final QueryCursor<T> cursor;
 
     /** Page size. */
     private final int pageSize;
@@ -43,7 +42,7 @@ class ClientCacheScanQueryCursor implements ClientCloseableResource {
     private long id;
 
     /** Iterator. */
-    private Iterator<Cache.Entry> iterator;
+    private Iterator<T> iterator;
 
     /** Close guard. */
     private final AtomicBoolean closeGuard = new AtomicBoolean();
@@ -54,7 +53,7 @@ class ClientCacheScanQueryCursor implements ClientCloseableResource {
      * @param pageSize Page size.
      * @param ctx Context.
      */
-    ClientCacheScanQueryCursor(QueryCursorEx<Cache.Entry> cursor, int pageSize, ClientConnectionContext ctx) {
+    ClientCacheQueryCursor(QueryCursor<T> cursor, int pageSize, ClientConnectionContext ctx) {
         assert cursor != null;
         assert pageSize > 0;
         assert ctx != null;
@@ -70,16 +69,15 @@ class ClientCacheScanQueryCursor implements ClientCloseableResource {
      * @param writer Writer.
      */
     void writePage(BinaryRawWriterEx writer) {
-        Iterator<Cache.Entry> iter = iterator();
+        Iterator<T> iter = iterator();
 
         int cntPos = writer.reserveInt();
         int cnt = 0;
 
         while (cnt < pageSize && iter.hasNext()) {
-            Cache.Entry e = iter.next();
+            T e = iter.next();
 
-            writer.writeObjectDetached(e.getKey());
-            writer.writeObjectDetached(e.getValue());
+            writeEntry(writer, e);
 
             cnt++;
         }
@@ -93,6 +91,14 @@ class ClientCacheScanQueryCursor implements ClientCloseableResource {
     }
 
     /**
+     * Writes cursor entry.
+     *
+     * @param writer Writer.
+     * @param e Entry.
+     */
+    abstract void writeEntry(BinaryRawWriterEx writer, T e);
+
+    /**
      * Closes the cursor.
      */
     @Override public void close() {
@@ -126,7 +132,7 @@ class ClientCacheScanQueryCursor implements ClientCloseableResource {
      *
      * @return Iterator.
      */
-    private Iterator<Cache.Entry> iterator() {
+    private Iterator<T> iterator() {
         if (iterator == null)
             iterator = cursor.iterator();
 
@@ -25,7 +25,7 @@ import org.apache.ignite.internal.processors.platform.client.ClientResponse;
 /**
  * Query cursor next page request.
  */
-public class ClientCacheScanQueryNextPageRequest extends ClientRequest {
+public class ClientCacheQueryNextPageRequest extends ClientRequest {
     /** Cursor id. */
     private final long cursorId;
 
@@ -34,7 +34,7 @@ public class ClientCacheScanQueryNextPageRequest extends ClientRequest {
      *
      * @param reader Reader.
      */
-    public ClientCacheScanQueryNextPageRequest(BinaryRawReader reader) {
+    public ClientCacheQueryNextPageRequest(BinaryRawReader reader) {
         super(reader);
 
         cursorId = reader.readLong();
@@ -42,8 +42,8 @@ public class ClientCacheScanQueryNextPageRequest extends ClientRequest {
 
     /** {@inheritDoc} */
     @Override public ClientResponse process(ClientConnectionContext ctx) {
-        ClientCacheScanQueryCursor cur = ctx.resources().get(cursorId);
+        ClientCacheQueryCursor cur = ctx.resources().get(cursorId);
 
-        return new ClientCacheScanQueryNextPageResponse(requestId(), cur);
+        return new ClientCacheQueryNextPageResponse(requestId(), cur);
     }
 }
@@ -23,9 +23,9 @@ import org.apache.ignite.internal.processors.platform.client.ClientResponse;
 /**
  * Query cursor next page response.
  */
-class ClientCacheScanQueryNextPageResponse extends ClientResponse {
+class ClientCacheQueryNextPageResponse extends ClientResponse {
     /** Cursor. */
-    private final ClientCacheScanQueryCursor cursor;
+    private final ClientCacheQueryCursor cursor;
 
     /**
      * Ctor.
@@ -33,7 +33,7 @@ class ClientCacheScanQueryNextPageResponse extends ClientResponse {
      * @param requestId Request id.
      * @param cursor Cursor.
      */
-    ClientCacheScanQueryNextPageResponse(long requestId, ClientCacheScanQueryCursor cursor) {
+    ClientCacheQueryNextPageResponse(long requestId, ClientCacheQueryCursor cursor) {
         super(requestId);
 
         assert cursor != null;
@@ -23,9 +23,9 @@ import org.apache.ignite.internal.processors.platform.client.ClientResponse;
 /**
  * Scan query response.
  */
-class ClientCacheScanQueryResponse extends ClientResponse {
+class ClientCacheQueryResponse extends ClientResponse {
     /** Cursor. */
-    private final ClientCacheScanQueryCursor cursor;
+    private final ClientCacheQueryCursor cursor;
 
     /**
      * Ctor.
@@ -33,7 +33,7 @@ class ClientCacheScanQueryResponse extends ClientResponse {
      * @param requestId Request id.
      * @param cursor Cursor.
      */
-    ClientCacheScanQueryResponse(long requestId, ClientCacheScanQueryCursor cursor) {
+    ClientCacheQueryResponse(long requestId, ClientCacheQueryCursor cursor) {
         super(requestId);
 
         assert cursor != null;
index b290a5b..44416be 100644 (file)
@@ -77,14 +77,34 @@ class ClientCacheRequest extends ClientRequest {
      * @return Cache.
      */
     protected IgniteCache rawCache(ClientConnectionContext ctx) {
-        DynamicCacheDescriptor cacheDesc = ctx.kernalContext().cache().cacheDescriptor(cacheId);
-
-        if (cacheDesc == null)
-            throw new IgniteClientException(ClientStatus.CACHE_DOES_NOT_EXIST, "Cache does not exist [cacheId= " +
-                cacheId + "]", null);
+        DynamicCacheDescriptor cacheDesc = cacheDescriptor(ctx);
 
         String cacheName = cacheDesc.cacheName();
 
         return ctx.kernalContext().grid().cache(cacheName);
     }
+
+    /**
+     * Gets the cache descriptor.
+     *
+     * @return Cache descriptor.
+     */
+    protected DynamicCacheDescriptor cacheDescriptor(ClientConnectionContext ctx) {
+        DynamicCacheDescriptor desc = ctx.kernalContext().cache().cacheDescriptor(cacheId);
+
+        if (desc == null)
+            throw new IgniteClientException(ClientStatus.CACHE_DOES_NOT_EXIST, "Cache does not exist [cacheId= " +
+                    cacheId + "]", null);
+
+        return desc;
+    }
+
+    /**
+     * Gets the cache id.
+     *
+     * @return Cache id.
+     */
+    protected int cacheId() {
+        return cacheId;
+    }
 }
index 7c163e3..26ab236 100644 (file)
@@ -23,7 +23,6 @@ import org.apache.ignite.binary.BinaryObject;
 import org.apache.ignite.cache.query.QueryCursor;
 import org.apache.ignite.cache.query.ScanQuery;
 import org.apache.ignite.internal.binary.BinaryRawReaderEx;
-import org.apache.ignite.internal.processors.cache.query.QueryCursorEx;
 import org.apache.ignite.internal.processors.platform.PlatformContext;
 import org.apache.ignite.internal.processors.platform.client.ClientConnectionContext;
 import org.apache.ignite.internal.processors.platform.client.ClientResponse;
@@ -94,13 +93,13 @@ public class ClientCacheScanQueryRequest extends ClientCacheRequest {
         try {
             QueryCursor cur = cache.query(qry);
 
-            ClientCacheScanQueryCursor cliCur = new ClientCacheScanQueryCursor((QueryCursorEx)cur, pageSize, ctx);
+            ClientCacheEntryQueryCursor cliCur = new ClientCacheEntryQueryCursor(cur, pageSize, ctx);
 
             long cursorId = ctx.resources().put(cliCur);
 
             cliCur.id(cursorId);
 
-            return new ClientCacheScanQueryResponse(requestId(), cliCur);
+            return new ClientCacheQueryResponse(requestId(), cliCur);
         }
         catch (Exception e) {
             ctx.decrementCursors();
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/platform/client/cache/ClientCacheSqlFieldsQueryRequest.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/platform/client/cache/ClientCacheSqlFieldsQueryRequest.java
new file mode 100644 (file)
index 0000000..ca3595d
--- /dev/null
@@ -0,0 +1,126 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ignite.internal.processors.platform.client.cache;
+
+import org.apache.ignite.cache.query.FieldsQueryCursor;
+import org.apache.ignite.cache.query.SqlFieldsQuery;
+import org.apache.ignite.internal.binary.BinaryRawReaderEx;
+import org.apache.ignite.internal.processors.cache.DynamicCacheDescriptor;
+import org.apache.ignite.internal.processors.cache.query.SqlFieldsQueryEx;
+import org.apache.ignite.internal.processors.odbc.jdbc.JdbcStatementType;
+import org.apache.ignite.internal.processors.platform.cache.PlatformCache;
+import org.apache.ignite.internal.processors.platform.client.ClientConnectionContext;
+import org.apache.ignite.internal.processors.platform.client.ClientResponse;
+import org.apache.ignite.internal.processors.query.QueryUtils;
+
+import java.util.List;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Sql query request.
+ */
+@SuppressWarnings("unchecked")
+public class ClientCacheSqlFieldsQueryRequest extends ClientCacheRequest {
+    /** Query. */
+    private final SqlFieldsQuery qry;
+
+    /** Include field names flag. */
+    private final boolean includeFieldNames;
+
+    /**
+     * Ctor.
+     *
+     * @param reader Reader.
+     */
+    public ClientCacheSqlFieldsQueryRequest(BinaryRawReaderEx reader) {
+        super(reader);
+
+        // Same request format as in JdbcQueryExecuteRequest.
+        String schema = reader.readString();
+        int pageSize = reader.readInt();
+        reader.readInt();  // maxRows
+        String sql = reader.readString();
+        Object[] args = PlatformCache.readQueryArgs(reader);
+        JdbcStatementType stmtType = JdbcStatementType.fromOrdinal(reader.readByte());
+        boolean distributedJoins = reader.readBoolean();
+        boolean loc = reader.readBoolean();
+        boolean replicatedOnly = reader.readBoolean();
+        boolean enforceJoinOrder = reader.readBoolean();
+        boolean collocated = reader.readBoolean();
+        boolean lazy = reader.readBoolean();
+        int timeout = (int) reader.readLong();
+        includeFieldNames = reader.readBoolean();
+
+        SqlFieldsQuery qry = stmtType == JdbcStatementType.ANY_STATEMENT_TYPE
+                ? new SqlFieldsQuery(sql)
+                : new SqlFieldsQueryEx(sql,stmtType == JdbcStatementType.SELECT_STATEMENT_TYPE);
+
+        qry.setSchema(schema)
+                .setPageSize(pageSize)
+                .setArgs(args)
+                .setDistributedJoins(distributedJoins)
+                .setLocal(loc)
+                .setReplicatedOnly(replicatedOnly)
+                .setEnforceJoinOrder(enforceJoinOrder)
+                .setCollocated(collocated)
+                .setLazy(lazy)
+                .setTimeout(timeout, TimeUnit.MILLISECONDS);
+
+        this.qry = qry;
+    }
+
+    /** {@inheritDoc} */
+    @Override public ClientResponse process(ClientConnectionContext ctx) {
+        ctx.incrementCursors();
+
+        try {
+            // If cacheId is provided, we must check the cache for existence.
+            if (cacheId() != 0) {
+                DynamicCacheDescriptor desc = cacheDescriptor(ctx);
+
+                if (qry.getSchema() == null) {
+                    String schema = QueryUtils.normalizeSchemaName(desc.cacheName(),
+                            desc.cacheConfiguration().getSqlSchema());
+
+                    qry.setSchema(schema);
+                }
+            }
+
+            List<FieldsQueryCursor<List<?>>> curs = ctx.kernalContext().query()
+                    .querySqlFieldsNoCache(qry, true, true);
+
+            assert curs.size() == 1;
+
+            FieldsQueryCursor cur = curs.get(0);
+
+            ClientCacheFieldsQueryCursor cliCur = new ClientCacheFieldsQueryCursor(
+                    cur, qry.getPageSize(), ctx);
+
+            long cursorId = ctx.resources().put(cliCur);
+
+            cliCur.id(cursorId);
+
+            return new ClientCacheSqlFieldsQueryResponse(requestId(), cliCur, cur, includeFieldNames);
+        }
+        catch (Exception e) {
+            ctx.decrementCursors();
+
+            throw e;
+        }
+    }
+}
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/platform/client/cache/ClientCacheSqlFieldsQueryResponse.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/platform/client/cache/ClientCacheSqlFieldsQueryResponse.java
new file mode 100644 (file)
index 0000000..1ff2ea5
--- /dev/null
@@ -0,0 +1,75 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ignite.internal.processors.platform.client.cache;
+
+import org.apache.ignite.cache.query.FieldsQueryCursor;
+import org.apache.ignite.internal.binary.BinaryRawWriterEx;
+import org.apache.ignite.internal.processors.platform.client.ClientResponse;
+
+import java.util.List;
+
+/**
+ * Scan query response.
+ */
+class ClientCacheSqlFieldsQueryResponse extends ClientResponse {
+    /** Cursor. */
+    private final ClientCacheQueryCursor cursor;
+
+    /** Fields cursor. */
+    private final FieldsQueryCursor<List> fieldsCursor;
+
+    /** Include field names flag. */
+    private final boolean includeFieldNames;
+
+    /**
+     * Ctor.
+     * @param requestId Request id.
+     * @param cursor Client cursor.
+     * @param fieldsCursor Fields cursor.
+     * @param includeFieldNames Whether to include field names.
+     */
+    ClientCacheSqlFieldsQueryResponse(long requestId, ClientCacheQueryCursor cursor,
+                                      FieldsQueryCursor<List> fieldsCursor, boolean includeFieldNames) {
+        super(requestId);
+
+        assert cursor != null;
+        assert fieldsCursor != null;
+
+        this.cursor = cursor;
+        this.fieldsCursor = fieldsCursor;
+        this.includeFieldNames = includeFieldNames;
+    }
+
+    /** {@inheritDoc} */
+    @Override public void encode(BinaryRawWriterEx writer) {
+        super.encode(writer);
+
+        writer.writeLong(cursor.id());
+
+        int cnt = fieldsCursor.getColumnsCount();
+        writer.writeInt(cnt);
+
+        if (includeFieldNames) {
+            for (int i = 0; i < cnt; i++) {
+                writer.writeString(fieldsCursor.getFieldName(i));
+            }
+        }
+
+        cursor.writePage(writer);
+    }
+}
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/platform/client/cache/ClientCacheSqlQueryRequest.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/platform/client/cache/ClientCacheSqlQueryRequest.java
new file mode 100644 (file)
index 0000000..8c21be1
--- /dev/null
@@ -0,0 +1,79 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ignite.internal.processors.platform.client.cache;
+
+import org.apache.ignite.IgniteCache;
+import org.apache.ignite.cache.query.QueryCursor;
+import org.apache.ignite.cache.query.SqlQuery;
+import org.apache.ignite.internal.binary.BinaryRawReaderEx;
+import org.apache.ignite.internal.processors.platform.cache.PlatformCache;
+import org.apache.ignite.internal.processors.platform.client.ClientConnectionContext;
+import org.apache.ignite.internal.processors.platform.client.ClientResponse;
+
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Sql query request.
+ */
+@SuppressWarnings("unchecked")
+public class ClientCacheSqlQueryRequest extends ClientCacheRequest {
+    /** Query. */
+    private final SqlQuery qry;
+
+    /**
+     * Ctor.
+     *
+     * @param reader Reader.
+     */
+    public ClientCacheSqlQueryRequest(BinaryRawReaderEx reader) {
+        super(reader);
+
+        qry = new SqlQuery(reader.readString(), reader.readString())
+                .setArgs(PlatformCache.readQueryArgs(reader))
+                .setDistributedJoins(reader.readBoolean())
+                .setLocal(reader.readBoolean())
+                .setReplicatedOnly(reader.readBoolean())
+                .setPageSize(reader.readInt())
+                .setTimeout((int) reader.readLong(), TimeUnit.MILLISECONDS);
+    }
+
+    /** {@inheritDoc} */
+    @Override public ClientResponse process(ClientConnectionContext ctx) {
+        IgniteCache cache = cache(ctx);
+
+        ctx.incrementCursors();
+
+        try {
+            QueryCursor cur = cache.query(qry);
+
+            ClientCacheEntryQueryCursor cliCur = new ClientCacheEntryQueryCursor(
+                    cur, qry.getPageSize(), ctx);
+
+            long cursorId = ctx.resources().put(cliCur);
+
+            cliCur.id(cursorId);
+
+            return new ClientCacheQueryResponse(requestId(), cliCur);
+        }
+        catch (Exception e) {
+            ctx.decrementCursors();
+
+            throw e;
+        }
+    }
+}
index 2e34ba2..2d5a54b 100644 (file)
     <Compile Include="Client\Cache\CreateCacheTest.cs" />
     <Compile Include="Client\Cache\ScanQueryTest.cs" />
     <Compile Include="Client\Cache\Person.cs" />
+    <Compile Include="Client\Cache\SqlQueryTest.cs" />
     <Compile Include="Client\ClientTestBase.cs" />
     <Compile Include="Client\RawSocketTest.cs" />
     <Compile Include="Client\ClientConnectionTest.cs" />
index 782e3cc..a2ca65d 100644 (file)
@@ -52,16 +52,7 @@ namespace Apache.Ignite.Core.Tests.Client.Cache
             using (var client = Ignition.StartClient(cfg))
             {
                 var serverCache = Ignition.GetIgnite().GetOrCreateCache<int?, Person>(
-                    new CacheConfiguration("person", new QueryEntity
-                    {
-                        KeyType = typeof(int),
-                        ValueType = typeof(Person),
-                        Fields = new[]
-                        {
-                            new QueryField("id", typeof(int)),
-                            new QueryField("name", typeof(string))
-                        }
-                    }));
+                    new CacheConfiguration("person", typeof(Person)));
 
                 var clientCache = client.GetCache<int?, Person>(serverCache.Name);
 
index a6bc9d7..327e707 100644 (file)
 
 namespace Apache.Ignite.Core.Tests.Client.Cache
 {
+    using System;
+    using Apache.Ignite.Core.Cache.Configuration;
+
     /// <summary>
     /// Test person.
     /// </summary>
     public class Person
     {
         /// <summary>
+        /// Initializes a new instance of the <see cref="Person"/> class.
+        /// </summary>
+        public Person()
+        {
+            DateTime = DateTime.UtcNow;
+        }
+
+        /// <summary>
+        /// Initializes a new instance of the <see cref="Person"/> class.
+        /// </summary>
+        public Person(int id)
+        {
+            Id = id;
+            Name = "Person " + id;
+            DateTime = DateTime.UtcNow.AddDays(id);
+        }
+
+        /// <summary>
         /// Gets or sets the identifier.
         /// </summary>
+        [QuerySqlField(IsIndexed = true)]
         public int Id { get; set; }
 
         /// <summary>
         /// Gets or sets the name.
         /// </summary>
+        [QuerySqlField]
         public string Name { get; set; }
 
         /// <summary>
+        /// Gets or sets the date time.
+        /// </summary>
+        [QuerySqlField]
+        public DateTime DateTime { get; set; }
+
+        /// <summary>
         /// Gets or sets the parent.
         /// </summary>
         public Person Parent { get;set; }
diff --git a/modules/platforms/dotnet/Apache.Ignite.Core.Tests/Client/Cache/SqlQueryTest.cs b/modules/platforms/dotnet/Apache.Ignite.Core.Tests/Client/Cache/SqlQueryTest.cs
new file mode 100644 (file)
index 0000000..720a71b
--- /dev/null
@@ -0,0 +1,268 @@
+/*
+ * 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.
+ */
+
+namespace Apache.Ignite.Core.Tests.Client.Cache
+{
+    using System;
+    using System.Linq;
+    using Apache.Ignite.Core.Cache.Configuration;
+    using Apache.Ignite.Core.Cache.Query;
+    using Apache.Ignite.Core.Client;
+    using NUnit.Framework;
+
+    /// <summary>
+    /// Tests SQL queries via thin client.
+    /// </summary>
+    public class SqlQueryTest : ClientTestBase
+    {
+        /// <summary>
+        /// Cache item count.
+        /// </summary>
+        private const int Count = 10;
+
+        /// <summary>
+        /// Second cache name.
+        /// </summary>
+        private const string CacheName2 = CacheName + "2";
+
+        /// <summary>
+        /// Initializes a new instance of the <see cref="ScanQueryTest"/> class.
+        /// </summary>
+        public SqlQueryTest() : base(2)
+        {
+            // No-op.
+        }
+
+        /// <summary>
+        /// Sets up the test.
+        /// </summary>
+        public override void TestSetUp()
+        {
+            InitCache(CacheName);
+            InitCache(CacheName2);
+        }
+
+        /// <summary>
+        /// Tests the SQL query.
+        /// </summary>
+        [Test]
+        public void TestSqlQuery()
+        {
+            var cache = GetClientCache<Person>();
+
+            // All items.
+            var qry = new SqlQuery(typeof(Person), "where 1 = 1");
+            Assert.AreEqual(Count, cache.Query(qry).Count());
+
+            // All items local.
+            qry.Local = true;
+            Assert.Greater(Count, cache.Query(qry).Count());
+
+            // Filter.
+            qry = new SqlQuery(typeof(Person), "where Name like '%7'");
+            Assert.AreEqual(7, cache.Query(qry).Single().Key);
+
+            // Args.
+            qry = new SqlQuery(typeof(Person), "where Id = ?", 3);
+            Assert.AreEqual(3, cache.Query(qry).Single().Value.Id);
+
+            // DateTime.
+            qry = new SqlQuery(typeof(Person), "where DateTime > ?", DateTime.UtcNow.AddDays(Count - 1));
+            Assert.AreEqual(Count, cache.Query(qry).Single().Key);
+
+            // Invalid args.
+            qry.Sql = null;
+            Assert.Throws<ArgumentNullException>(() => cache.Query(qry));
+
+            qry.Sql = "abc";
+            qry.QueryType = null;
+            Assert.Throws<ArgumentNullException>(() => cache.Query(qry));
+        }
+
+        /// <summary>
+        /// Tests the SQL query with distributed joins.
+        /// </summary>
+        [Test]
+        public void TestSqlQueryDistributedJoins()
+        {
+            var cache = GetClientCache<Person>();
+
+            // Non-distributed join returns incomplete results.
+            var qry = new SqlQuery(typeof(Person),
+                string.Format("from \"{0}\".Person, \"{1}\".Person as p2 where Person.Id = 11 - p2.Id",
+                    CacheName, CacheName2));
+            
+            Assert.Greater(Count, cache.Query(qry).Count());
+
+            // Distributed join fixes the problem.
+            qry.EnableDistributedJoins = true;
+            Assert.AreEqual(Count, cache.Query(qry).Count());
+        }
+
+        /// <summary>
+        /// Tests the fields query.
+        /// </summary>
+        [Test]
+        public void TestFieldsQuery()
+        {
+            var cache = GetClientCache<Person>();
+
+            // All items.
+            var qry = new SqlFieldsQuery("select Id from Person");
+            var cursor = cache.Query(qry);
+            CollectionAssert.AreEquivalent(Enumerable.Range(1, Count), cursor.Select(x => (int) x[0]));
+            Assert.AreEqual("ID", cursor.FieldNames.Single());
+
+            // All items local.
+            // TODO: IGNITE-5571 - exception should be fixed.
+            qry.Local = true;
+            Assert.Throws<IgniteClientException>(() => Assert.Greater(Count, cache.Query(qry).Count()));
+
+            // Filter.
+            qry = new SqlFieldsQuery("select Name from Person where Id = ?", 1)
+            {
+                Lazy = true,
+                PageSize = 5,
+            };
+            Assert.AreEqual("Person 1", cache.Query(qry).Single().Single());
+
+            // DateTime.
+            qry = new SqlFieldsQuery("select Id, DateTime from Person where DateTime > ?", DateTime.UtcNow.AddDays(9));
+            Assert.AreEqual(cache[Count].DateTime, cache.Query(qry).Single().Last());
+
+            // Invalid args.
+            qry.Sql = null;
+            Assert.Throws<ArgumentNullException>(() => cache.Query(qry));
+        }
+
+        /// <summary>
+        /// Tests the SQL fields query with distributed joins.
+        /// </summary>
+        [Test]
+        public void TestFieldsQueryDistributedJoins()
+        {
+            var cache = GetClientCache<Person>();
+
+            // Non-distributed join returns incomplete results.
+            var qry = new SqlFieldsQuery(string.Format(
+                "select p2.Name from \"{0}\".Person, \"{1}\".Person as p2 where Person.Id = 11 - p2.Id", 
+                CacheName, CacheName2));
+
+            Assert.Greater(Count, cache.Query(qry).Count());
+
+            // Distributed join fixes the problem.
+            qry.EnableDistributedJoins = true;
+            Assert.AreEqual(Count, cache.Query(qry).Count());
+        }
+
+        /// <summary>
+        /// Tests the fields query timeout.
+        /// </summary>
+        [Test]
+        public void TestFieldsQueryTimeout()
+        {
+            var cache = GetClientCache<Person>();
+
+            cache.PutAll(Enumerable.Range(1, 30000).ToDictionary(x => x, x => new Person(x)));
+
+            var qry = new SqlFieldsQuery("select * from Person where Name like '%ers%'")
+            {
+                Timeout = TimeSpan.FromMilliseconds(1)
+            };
+
+            Assert.Throws<IgniteClientException>(() => cache.Query(qry).GetAll());
+        }
+
+        /// <summary>
+        /// Tests the fields query on a missing cache.
+        /// </summary>
+        [Test]
+        public void TestFieldsQueryMissingCache()
+        {
+            var cache = Client.GetCache<int, Person>("I do not exist");
+            var qry = new SqlFieldsQuery("select name from person")
+            {
+                Schema = CacheName
+            };
+
+            // Schema is set => we still check for cache existence.
+            var ex = Assert.Throws<IgniteClientException>(() => cache.Query(qry).GetAll());
+            Assert.AreEqual("Cache doesn't exist: I do not exist", ex.Message);
+
+            // Schema not set => also exception.
+            qry.Schema = null;
+            ex = Assert.Throws<IgniteClientException>(() => cache.Query(qry).GetAll());
+            Assert.AreEqual("Cache doesn't exist: I do not exist", ex.Message);
+        }
+
+        /// <summary>
+        /// Tests fields query with custom schema.
+        /// </summary>
+        [Test]
+        public void TestFieldsQueryCustomSchema()
+        {
+            var cache1 = Client.GetCache<int, Person>(CacheName);
+            var cache2 = Client.GetCache<int, Person>(CacheName2);
+
+            cache1.RemoveAll();
+
+            var qry = new SqlFieldsQuery("select name from person");
+
+            // Schema not set: cache name is used.
+            Assert.AreEqual(0, cache1.Query(qry).Count());
+            Assert.AreEqual(Count, cache2.Query(qry).Count());
+
+            // Schema set to first cache: no results both cases.
+            qry.Schema = cache1.Name;
+            Assert.AreEqual(0, cache1.Query(qry).Count());
+            Assert.AreEqual(0, cache2.Query(qry).Count());
+
+            // Schema set to second cache: full results both cases.
+            qry.Schema = cache2.Name;
+            Assert.AreEqual(Count, cache1.Query(qry).Count());
+            Assert.AreEqual(Count, cache2.Query(qry).Count());
+        }
+
+        /// <summary>
+        /// Tests the DML.
+        /// </summary>
+        [Test]
+        public void TestDml()
+        {
+            var cache = GetClientCache<Person>();
+
+            var qry = new SqlFieldsQuery("insert into Person (_key, id, name) values (?, ?, ?)", -10, 1, "baz");
+            var res = cache.Query(qry).GetAll();
+
+            Assert.AreEqual(1, res[0][0]);
+            Assert.AreEqual("baz", cache[-10].Name);
+        }
+
+        /// <summary>
+        /// Initializes the cache.
+        /// </summary>
+        private static void InitCache(string cacheName)
+        {
+            var cache = Ignition.GetIgnite().GetOrCreateCache<int, Person>(
+                new CacheConfiguration(cacheName, new QueryEntity(typeof(int), typeof(Person))));
+
+            cache.RemoveAll();
+
+            cache.PutAll(Enumerable.Range(1, Count).ToDictionary(x => x, x => new Person(x)));
+        }
+    }
+}
index 9b7a566..e1d30b9 100644 (file)
@@ -20,6 +20,7 @@ namespace Apache.Ignite.Core.Tests.Client
     using System.Net;
     using Apache.Ignite.Core.Cache;
     using Apache.Ignite.Core.Client;
+    using Apache.Ignite.Core.Client.Cache;
     using NUnit.Framework;
 
     /// <summary>
@@ -81,7 +82,7 @@ namespace Apache.Ignite.Core.Tests.Client
         /// Sets up the test.
         /// </summary>
         [SetUp]
-        public void TestSetUp()
+        public virtual void TestSetUp()
         {
             GetCache<int>().RemoveAll();
         }
@@ -100,6 +101,14 @@ namespace Apache.Ignite.Core.Tests.Client
         }
 
         /// <summary>
+        /// Gets the client cache.
+        /// </summary>
+        protected ICacheClient<int, T> GetClientCache<T>()
+        {
+            return Client.GetCache<int, T>(CacheName);
+        }
+
+        /// <summary>
         /// Gets the client.
         /// </summary>
         protected IIgniteClient GetClient()
index 0076d47..21738a2 100644 (file)
     <Compile Include="Cache\Configuration\MemoryPolicyConfiguration.cs" />
     <Compile Include="Cache\Configuration\PartitionLossPolicy.cs" />
     <Compile Include="Cache\IMemoryMetrics.cs" />
+    <Compile Include="Cache\Query\IFieldsQueryCursor.cs" />
     <Compile Include="Client\Cache\ICacheClient.cs" />
     <Compile Include="Client\IgniteClientConfiguration.cs" />
     <Compile Include="Client\IgniteClientException.cs" />
     <Compile Include="Impl\Binary\MultidimensionalArraySerializer.cs" />
     <Compile Include="Impl\Client\Cache\CacheFlags.cs" />
     <Compile Include="Impl\Client\Cache\ClientCacheConfigurationSerializer.cs" />
+    <Compile Include="Impl\Client\Cache\Query\ClientFieldsQueryCursor.cs" />
     <Compile Include="Impl\Client\Cache\Query\ClientQueryCursor.cs" />
     <Compile Include="Impl\Cache\Query\PlatformQueryQursorBase.cs" />
     <Compile Include="Impl\Binary\BinaryProcessorClient.cs" />
     <Compile Include="Impl\Binary\IBinaryProcessor.cs" />
+    <Compile Include="Impl\Client\Cache\Query\ClientQueryCursorBase.cs" />
+    <Compile Include="Impl\Client\Cache\Query\StatementType.cs" />
     <Compile Include="Impl\Client\ClientStatus.cs" />
     <Compile Include="Events\LocalEventListener.cs" />
     <Compile Include="Impl\DataStorageMetrics.cs" />
diff --git a/modules/platforms/dotnet/Apache.Ignite.Core/Cache/Query/IFieldsQueryCursor.cs b/modules/platforms/dotnet/Apache.Ignite.Core/Cache/Query/IFieldsQueryCursor.cs
new file mode 100644 (file)
index 0000000..fbeaf8c
--- /dev/null
@@ -0,0 +1,34 @@
+/*
+ * 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.
+ */
+
+namespace Apache.Ignite.Core.Cache.Query
+{
+    using System.Collections.Generic;
+    using System.Diagnostics.CodeAnalysis;
+
+    /// <summary>
+    /// Fields query cursor.
+    /// </summary>
+    [SuppressMessage("Microsoft.Naming", "CA1710:IdentifiersShouldHaveCorrectSuffix")]
+    public interface IFieldsQueryCursor : IQueryCursor<IList<object>>
+    {
+        /// <summary>
+        /// Gets the field names.
+        /// </summary>
+        IList<string> FieldNames { get; }
+    }
+}
index a3964c6..eb91b0a 100644 (file)
@@ -100,6 +100,20 @@ namespace Apache.Ignite.Core.Client.Cache
         IQueryCursor<ICacheEntry<TK, TV>> Query(ScanQuery<TK, TV> scanQuery);
 
         /// <summary>
+        /// Executes an SQL query.
+        /// </summary>
+        /// <param name="sqlQuery">SQL query.</param>
+        /// <returns>Query cursor.</returns>
+        IQueryCursor<ICacheEntry<TK, TV>> Query(SqlQuery sqlQuery);
+
+        /// <summary>
+        /// Executes an SQL Fields query.
+        /// </summary>
+        /// <param name="sqlFieldsQuery">SQL query.</param>
+        /// <returns>Query cursor.</returns>
+        IFieldsQueryCursor Query(SqlFieldsQuery sqlFieldsQuery);
+
+        /// <summary>
         /// Associates the specified value with the specified key in this cache,
         /// returning an existing value if one existed.
         /// </summary>
index 17dc93b..c60e010 100644 (file)
 namespace Apache.Ignite.Core.Impl.Cache.Query
 {
     using System;
-    using System.Diagnostics;
-    using System.Diagnostics.CodeAnalysis;
     using Apache.Ignite.Core.Binary;
-    using Apache.Ignite.Core.Impl.Binary;
 
     /// <summary>
     /// Cursor for entry-based queries.
     /// </summary>
     internal class FieldsQueryCursor<T> : PlatformQueryQursorBase<T>
     {
-        /** */
-        private readonly Func<IBinaryRawReader, int, T> _readerFunc;
-
         /// <summary>
         /// Constructor.
         /// </summary>
@@ -39,23 +33,18 @@ namespace Apache.Ignite.Core.Impl.Cache.Query
         /// <param name="readerFunc">The reader function.</param>
         public FieldsQueryCursor(IPlatformTargetInternal target, bool keepBinary, 
             Func<IBinaryRawReader, int, T> readerFunc)
-            : base(target, keepBinary)
-        {
-            Debug.Assert(readerFunc != null);
-
-            _readerFunc = readerFunc;
-        }
+            : base(target, keepBinary, r =>
+            {
+                // Reading and skipping row size in bytes.
+                r.ReadInt();
 
-        /** <inheritdoc /> */
-        [SuppressMessage("Microsoft.Design", "CA1062:Validate arguments of public methods")]
-        protected override T Read(BinaryReader reader)
-        {
-            // Reading and skipping row size in bytes.
-            reader.ReadInt();
+                int cnt = r.ReadInt();
 
-            int cnt = reader.ReadInt();
+                return readerFunc(r, cnt);
 
-            return _readerFunc(reader, cnt);
+            })
+        {
+            // No-op.
         }
     }
 }
index 8a51dab..fc78392 100644 (file)
@@ -17,7 +17,9 @@
 
 namespace Apache.Ignite.Core.Impl.Cache.Query
 {
+    using System;
     using System.Collections.Generic;
+    using Apache.Ignite.Core.Impl.Binary;
 
     /// <summary>
     /// Base for platform cursors.
@@ -44,8 +46,10 @@ namespace Apache.Ignite.Core.Impl.Cache.Query
         /// </summary>
         /// <param name="target">The target.</param>
         /// <param name="keepBinary">Keep binary flag.</param>
-        protected PlatformQueryQursorBase(IPlatformTargetInternal target, bool keepBinary) 
-            : base(target.Marshaller, keepBinary)
+        /// <param name="readFunc"></param>
+        protected PlatformQueryQursorBase(IPlatformTargetInternal target, bool keepBinary, 
+            Func<BinaryReader, T> readFunc) 
+            : base(target.Marshaller, keepBinary, readFunc)
         {
             _target = target;
         }
index b967d6a..ca773fe 100644 (file)
@@ -17,9 +17,7 @@
 
 namespace Apache.Ignite.Core.Impl.Cache.Query
 {
-    using System.Diagnostics.CodeAnalysis;
     using Apache.Ignite.Core.Cache;
-    using Apache.Ignite.Core.Impl.Binary;
 
     /// <summary>
     /// Cursor for entry-based queries.
@@ -31,19 +29,11 @@ namespace Apache.Ignite.Core.Impl.Cache.Query
         /// </summary>
         /// <param name="target">Target.</param>
         /// <param name="keepBinary">Keep poratble flag.</param>
-        public QueryCursor(IPlatformTargetInternal target, bool keepBinary) : base(target, keepBinary)
+        public QueryCursor(IPlatformTargetInternal target, bool keepBinary)
+            : base(target, keepBinary,
+                r => new CacheEntry<TK, TV>(r.ReadObject<TK>(), r.ReadObject<TV>()))
         {
             // No-op.
         }
-
-        /** <inheritdoc /> */
-        [SuppressMessage("Microsoft.Design", "CA1062:Validate arguments of public methods")]
-        protected override ICacheEntry<TK, TV> Read(BinaryReader reader)
-        {
-            TK key = reader.ReadObject<TK>();
-            TV val = reader.ReadObject<TV>();
-
-            return new CacheEntry<TK, TV>(key, val);
-        }
     }
 }
index 216d7ea..c8c02ad 100644 (file)
@@ -21,7 +21,6 @@ namespace Apache.Ignite.Core.Impl.Cache.Query
     using System.Collections;
     using System.Collections.Generic;
     using System.Diagnostics;
-    using System.Diagnostics.CodeAnalysis;
     using Apache.Ignite.Core.Cache.Query;
     using Apache.Ignite.Core.Impl.Binary;
     using Apache.Ignite.Core.Impl.Binary.IO;
@@ -40,6 +39,9 @@ namespace Apache.Ignite.Core.Impl.Cache.Query
         /** Marshaller. */
         private readonly Marshaller _marsh;
 
+        /** Read func. */
+        private readonly Func<BinaryReader, T> _readFunc;
+
         /** Wherther "GetAll" was called. */
         private bool _getAllCalled;
 
@@ -63,14 +65,15 @@ namespace Apache.Ignite.Core.Impl.Cache.Query
         /// </summary>
         /// <param name="marsh">Marshaller.</param>
         /// <param name="keepBinary">Keep binary flag.</param>
+        /// <param name="readFunc">The read function.</param>
         /// <param name="initialBatchStream">Optional stream with initial batch.</param>
-        [SuppressMessage("Microsoft.Usage", "CA2214:DoNotCallOverridableMethodsInConstructors",
-            Justification = "ConvertGetBatch calls Read, which does not rely on constructor being run.")]
-        protected QueryCursorBase(Marshaller marsh, bool keepBinary, IBinaryStream initialBatchStream = null)
+        protected QueryCursorBase(Marshaller marsh, bool keepBinary, Func<BinaryReader, T> readFunc, 
+            IBinaryStream initialBatchStream = null)
         {
             Debug.Assert(marsh != null);
 
             _keepBinary = keepBinary;
+            _readFunc = readFunc;
             _marsh = marsh;
 
             if (initialBatchStream != null)
@@ -198,13 +201,6 @@ namespace Apache.Ignite.Core.Impl.Cache.Query
         protected abstract IList<T> GetAllInternal();
 
         /// <summary>
-        /// Reads entry from the reader.
-        /// </summary> 
-        /// <param name="reader">Reader.</param>
-        /// <returns>Entry.</returns>
-        protected abstract T Read(BinaryReader reader);
-
-        /// <summary>
         /// Requests next batch.
         /// </summary>
         private void RequestBatch()
@@ -233,7 +229,7 @@ namespace Apache.Ignite.Core.Impl.Cache.Query
             var res = new List<T>(size);
 
             for (var i = 0; i < size; i++)
-                res.Add(Read(reader));
+                res.Add(_readFunc(reader));
 
             return res;
         }
@@ -259,7 +255,7 @@ namespace Apache.Ignite.Core.Impl.Cache.Query
 
             for (var i = 0; i < size; i++)
             {
-                res[i] = Read(reader);
+                res[i] = _readFunc(reader);
             }
 
             _hasNext = stream.ReadBool();
index a3b42b8..76c7b00 100644 (file)
@@ -37,7 +37,7 @@ namespace Apache.Ignite.Core.Impl.Client.Cache
     /// <summary>
     /// Client cache implementation.
     /// </summary>
-    internal class CacheClient<TK, TV> : ICacheClient<TK, TV>
+    internal sealed class CacheClient<TK, TV> : ICacheClient<TK, TV>
     {
         /** Scan query filter platform code: .NET filter. */
         private const byte FilterPlatformDotnet = 2;
@@ -159,14 +159,39 @@ namespace Apache.Ignite.Core.Impl.Client.Cache
         /** <inheritDoc /> */
         public IQueryCursor<ICacheEntry<TK, TV>> Query(ScanQuery<TK, TV> scanQuery)
         {
-            IgniteArgumentCheck.NotNull(scanQuery, "query");
+            IgniteArgumentCheck.NotNull(scanQuery, "scanQuery");
 
             // Filter is a binary object for all platforms.
             // For .NET it is a CacheEntryFilterHolder with a predefined id (BinaryTypeId.CacheEntryPredicateHolder).
             return DoOutInOp(ClientOp.QueryScan, w => WriteScanQuery(w, scanQuery),
-                s => new ClientQueryCursor<TK, TV>(_ignite, s.ReadLong(), _keepBinary, s));
+                s => new ClientQueryCursor<TK, TV>(
+                    _ignite, s.ReadLong(), _keepBinary, s, ClientOp.QueryScanCursorGetPage));
         }
-        
+
+        /** <inheritDoc /> */
+        public IQueryCursor<ICacheEntry<TK, TV>> Query(SqlQuery sqlQuery)
+        {
+            IgniteArgumentCheck.NotNull(sqlQuery, "sqlQuery");
+            IgniteArgumentCheck.NotNull(sqlQuery.Sql, "sqlQuery.Sql");
+            IgniteArgumentCheck.NotNull(sqlQuery.QueryType, "sqlQuery.QueryType");
+
+            return DoOutInOp(ClientOp.QuerySql, w => WriteSqlQuery(w, sqlQuery),
+                s => new ClientQueryCursor<TK, TV>(
+                    _ignite, s.ReadLong(), _keepBinary, s, ClientOp.QuerySqlCursorGetPage));
+        }
+
+        /** <inheritDoc /> */
+        public IFieldsQueryCursor Query(SqlFieldsQuery sqlFieldsQuery)
+        {
+            IgniteArgumentCheck.NotNull(sqlFieldsQuery, "sqlFieldsQuery");
+            IgniteArgumentCheck.NotNull(sqlFieldsQuery.Sql, "sqlFieldsQuery.Sql");
+
+            return DoOutInOp(ClientOp.QuerySqlFields, w => WriteSqlFieldsQuery(w, sqlFieldsQuery),
+                s => new ClientFieldsQueryCursor(
+                    _ignite, s.ReadLong(), _keepBinary, s, ClientOp.QuerySqlFieldsCursorGetPage,
+                    ClientFieldsQueryCursor.ReadColumns(_marsh.StartUnmarshal(s))));
+        }
+
         /** <inheritDoc /> */
         public CacheResult<TV> GetAndPut(TK key, TV val)
         {
@@ -426,6 +451,53 @@ namespace Apache.Ignite.Core.Impl.Client.Cache
         }
 
         /// <summary>
+        /// Writes the SQL query.
+        /// </summary>
+        private static void WriteSqlQuery(IBinaryRawWriter writer, SqlQuery qry)
+        {
+            Debug.Assert(qry != null);
+
+            writer.WriteString(qry.QueryType);
+            writer.WriteString(qry.Sql);
+            QueryBase.WriteQueryArgs(writer, qry.Arguments);
+            writer.WriteBoolean(qry.EnableDistributedJoins);
+            writer.WriteBoolean(qry.Local);
+            writer.WriteBoolean(qry.ReplicatedOnly);
+            writer.WriteInt(qry.PageSize);
+            writer.WriteTimeSpanAsLong(qry.Timeout);
+        }
+
+        /// <summary>
+        /// Writes the SQL fields query.
+        /// </summary>
+        private static void WriteSqlFieldsQuery(IBinaryRawWriter writer, SqlFieldsQuery qry)
+        {
+            Debug.Assert(qry != null);
+
+            writer.WriteString(qry.Schema);
+            writer.WriteInt(qry.PageSize);
+            writer.WriteInt(-1);  // maxRows: unlimited
+            writer.WriteString(qry.Sql);
+            QueryBase.WriteQueryArgs(writer, qry.Arguments);
+
+            // .NET client does not discern between different statements for now.
+            // We cound have ExecuteNonQuery method, which uses StatementType.Update, for example.
+            writer.WriteByte((byte)StatementType.Any);
+
+            writer.WriteBoolean(qry.EnableDistributedJoins);
+            writer.WriteBoolean(qry.Local);
+            writer.WriteBoolean(qry.ReplicatedOnly);
+            writer.WriteBoolean(qry.EnforceJoinOrder);
+            writer.WriteBoolean(qry.Colocated);
+            writer.WriteBoolean(qry.Lazy);
+            writer.WriteTimeSpanAsLong(qry.Timeout);
+
+            // Always include field names.
+            writer.WriteBoolean(true);
+
+        }
+
+        /// <summary>
         /// Handles the error.
         /// </summary>
         private T HandleError<T>(ClientStatus status, string msg)
diff --git a/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Client/Cache/Query/ClientFieldsQueryCursor.cs b/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Client/Cache/Query/ClientFieldsQueryCursor.cs
new file mode 100644 (file)
index 0000000..2e57863
--- /dev/null
@@ -0,0 +1,79 @@
+/*
+ * 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.
+ */
+
+namespace Apache.Ignite.Core.Impl.Client.Cache.Query
+{
+    using System.Collections.Generic;
+    using System.Collections.ObjectModel;
+    using System.Diagnostics;
+    using Apache.Ignite.Core.Binary;
+    using Apache.Ignite.Core.Cache.Query;
+    using Apache.Ignite.Core.Impl.Binary.IO;
+
+    /// <summary>
+    /// Client fields cursor.
+    /// </summary>
+    internal class ClientFieldsQueryCursor : ClientQueryCursorBase<IList<object>>, IFieldsQueryCursor
+    {
+        /// <summary>
+        /// Initializes a new instance of the <see cref="ClientQueryCursor{TK, TV}" /> class.
+        /// </summary>
+        /// <param name="ignite">The ignite.</param>
+        /// <param name="cursorId">The cursor identifier.</param>
+        /// <param name="keepBinary">Keep binary flag.</param>
+        /// <param name="initialBatchStream">Optional stream with initial batch.</param>
+        /// <param name="getPageOp">The get page op.</param>
+        /// <param name="columns">The columns.</param>
+        public ClientFieldsQueryCursor(IgniteClient ignite, long cursorId, bool keepBinary,
+            IBinaryStream initialBatchStream, ClientOp getPageOp, IList<string> columns)
+            : base(ignite, cursorId, keepBinary, initialBatchStream, getPageOp,
+                r =>
+                {
+                    var res = new List<object>(columns.Count);
+
+                    for (var i = 0; i < columns.Count; i++)
+                    {
+                        res.Add(r.ReadObject<object>());
+                    }
+
+                    return res;
+                })
+        {
+            Debug.Assert(columns != null);
+
+            FieldNames = new ReadOnlyCollection<string>(columns);
+        }
+
+        /** <inheritdoc /> */
+        public IList<string> FieldNames { get; private set; }
+
+        /// <summary>
+        /// Reads the columns.
+        /// </summary>
+        internal static string[] ReadColumns(IBinaryRawReader reader)
+        {
+            var res = new string[reader.ReadInt()];
+
+            for (var i = 0; i < res.Length; i++)
+            {
+                res[i] = reader.ReadString();
+            }
+
+            return res;
+        }
+    }
+}
index ff891db..8e09af7 100644 (file)
 
 namespace Apache.Ignite.Core.Impl.Client.Cache.Query
 {
-    using System.Collections.Generic;
-    using System.Diagnostics.CodeAnalysis;
-    using System.Linq;
     using Apache.Ignite.Core.Cache;
-    using Apache.Ignite.Core.Impl.Binary;
     using Apache.Ignite.Core.Impl.Binary.IO;
     using Apache.Ignite.Core.Impl.Cache;
-    using Apache.Ignite.Core.Impl.Cache.Query;
     using Apache.Ignite.Core.Impl.Client;
 
     /// <summary>
     /// Client query cursor.
     /// </summary>
-    internal class ClientQueryCursor<TK, TV> : QueryCursorBase<ICacheEntry<TK, TV>>
+    internal sealed class ClientQueryCursor<TK, TV> : ClientQueryCursorBase<ICacheEntry<TK, TV>>
     {
-        /** Ignite. */
-        private readonly IgniteClient _ignite;
-
-        /** Cursor ID. */
-        private readonly long _cursorId;
-
         /// <summary>
         /// Initializes a new instance of the <see cref="ClientQueryCursor{TK, TV}" /> class.
         /// </summary>
@@ -45,52 +34,13 @@ namespace Apache.Ignite.Core.Impl.Client.Cache.Query
         /// <param name="cursorId">The cursor identifier.</param>
         /// <param name="keepBinary">Keep binary flag.</param>
         /// <param name="initialBatchStream">Optional stream with initial batch.</param>
-        public ClientQueryCursor(IgniteClient ignite, long cursorId, bool keepBinary, 
-            IBinaryStream initialBatchStream) 
-            : base(ignite.Marshaller, keepBinary, initialBatchStream)
-        {
-            _ignite = ignite;
-            _cursorId = cursorId;
-        }
-
-        /** <inheritdoc /> */
-        protected override void InitIterator()
+        /// <param name="getPageOp">The get page op.</param>
+        public ClientQueryCursor(IgniteClient ignite, long cursorId, bool keepBinary,
+            IBinaryStream initialBatchStream, ClientOp getPageOp)
+            : base(ignite, cursorId, keepBinary, initialBatchStream, getPageOp,
+                r => new CacheEntry<TK, TV>(r.ReadObject<TK>(), r.ReadObject<TV>()))
         {
             // No-op.
         }
-
-        /** <inheritdoc /> */
-        protected override IList<ICacheEntry<TK, TV>> GetAllInternal()
-        {
-            return this.ToArray();
-        }
-
-        /** <inheritdoc /> */
-        [SuppressMessage("Microsoft.Design", "CA1062:Validate arguments of public methods")]
-        protected override ICacheEntry<TK, TV> Read(BinaryReader reader)
-        {
-            return new CacheEntry<TK, TV>(reader.ReadObject<TK>(), reader.ReadObject<TV>());
-        }
-
-        /** <inheritdoc /> */
-        protected override ICacheEntry<TK, TV>[] GetBatch()
-        {
-            return _ignite.Socket.DoOutInOp(ClientOp.QueryScanCursorGetPage,
-                w => w.WriteLong(_cursorId),
-                s => ConvertGetBatch(s));
-        }
-
-        /** <inheritdoc /> */
-        protected override void Dispose(bool disposing)
-        {
-            try
-            {
-                _ignite.Socket.DoOutInOp<object>(ClientOp.ResourceClose, w => w.WriteLong(_cursorId), null);
-            }
-            finally
-            {
-                base.Dispose(disposing);
-            }
-        }
     }
 }
diff --git a/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Client/Cache/Query/ClientQueryCursorBase.cs b/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Client/Cache/Query/ClientQueryCursorBase.cs
new file mode 100644 (file)
index 0000000..5123537
--- /dev/null
@@ -0,0 +1,89 @@
+/*
+ * 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.
+ */
+namespace Apache.Ignite.Core.Impl.Client.Cache.Query
+{
+    using System;
+    using System.Collections.Generic;
+    using System.Linq;
+    using Apache.Ignite.Core.Impl.Binary;
+    using Apache.Ignite.Core.Impl.Binary.IO;
+    using Apache.Ignite.Core.Impl.Cache.Query;
+
+    /// <summary>
+    /// Client query cursor base.
+    /// </summary>
+    internal abstract class ClientQueryCursorBase<T> : QueryCursorBase<T>
+    {
+        /** Ignite. */
+        private readonly IgniteClient _ignite;
+
+        /** Cursor ID. */
+        private readonly long _cursorId;
+
+        /** Page op code. */
+        private readonly ClientOp _getPageOp;
+
+        /// <summary>
+        /// Initializes a new instance of the <see cref="ClientQueryCursorBase{T}" /> class.
+        /// </summary>
+        /// <param name="ignite">The ignite.</param>
+        /// <param name="cursorId">The cursor identifier.</param>
+        /// <param name="keepBinary">Keep binary flag.</param>
+        /// <param name="initialBatchStream">Optional stream with initial batch.</param>
+        /// <param name="getPageOp">The get page op.</param>
+        /// <param name="readFunc">Read func.</param>
+        protected ClientQueryCursorBase(IgniteClient ignite, long cursorId, bool keepBinary, 
+            IBinaryStream initialBatchStream, ClientOp getPageOp, Func<BinaryReader, T> readFunc) 
+            : base(ignite.Marshaller, keepBinary, readFunc, initialBatchStream)
+        {
+            _ignite = ignite;
+            _cursorId = cursorId;
+            _getPageOp = getPageOp;
+        }
+
+        /** <inheritdoc /> */
+        protected override void InitIterator()
+        {
+            // No-op.
+        }
+
+        /** <inheritdoc /> */
+        protected override IList<T> GetAllInternal()
+        {
+            return this.ToArray();
+        }
+
+        /** <inheritdoc /> */
+        protected override T[] GetBatch()
+        {
+            return _ignite.Socket.DoOutInOp(_getPageOp, w => w.WriteLong(_cursorId), s => ConvertGetBatch(s));
+        }
+
+        /** <inheritdoc /> */
+        protected override void Dispose(bool disposing)
+        {
+            try
+            {
+                _ignite.Socket.DoOutInOp<object>(ClientOp.ResourceClose, w => w.WriteLong(_cursorId), null);
+            }
+            finally
+            {
+                base.Dispose(disposing);
+            }
+        }
+    }
+}
\ No newline at end of file
diff --git a/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Client/Cache/Query/StatementType.cs b/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Client/Cache/Query/StatementType.cs
new file mode 100644 (file)
index 0000000..c5143ea
--- /dev/null
@@ -0,0 +1,42 @@
+/*
+ * 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.
+ */
+
+namespace Apache.Ignite.Core.Impl.Client.Cache.Query
+{
+    /// <summary>
+    /// Query request type.
+    /// <para />
+    /// When the client knows expected kind of query, we can fail earlier on server.
+    /// </summary>
+    internal enum StatementType : byte
+    {
+        /// <summary>
+        /// Any query, SQL or DML.
+        /// </summary>
+        Any = 0,
+
+        /// <summary>
+        /// Select query, "SELECT .. FROM".
+        /// </summary>
+        Select = 1,
+
+        /// <summary>
+        /// Update (DML) query, "UPDATE .. ", "INSERT INTO ..".
+        /// </summary>
+        Update = 2
+    }
+}
index 779b73e..3af089a 100644 (file)
@@ -56,6 +56,10 @@ namespace Apache.Ignite.Core.Impl.Client
         CacheGetNames = 32,
         CacheGetConfiguration = 33,
         CacheCreateWithConfiguration = 34,
-        CacheGetOrCreateWithConfiguration = 35
+        CacheGetOrCreateWithConfiguration = 35,
+        QuerySql = 36,
+        QuerySqlCursorGetPage = 37,
+        QuerySqlFields = 38,
+        QuerySqlFieldsCursorGetPage = 39
     }
 }