IGNITE-14015 Tracing SQL: tracing of SELECT queries causes incorrect span inheritance (#8675)
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/tracing/TraceableIterator.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/tracing/TraceableIterator.java
index 6fed37e..80c8b4e 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/processors/tracing/TraceableIterator.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/tracing/TraceableIterator.java
@@ -18,6 +18,7 @@
 package org.apache.ignite.internal.processors.tracing;
 
 import java.util.Iterator;
+import org.apache.ignite.internal.processors.tracing.MTC.TraceSurroundings;
 
 import static org.apache.ignite.internal.processors.tracing.SpanTags.ERROR;
 
@@ -28,7 +29,7 @@
     /** Iterator to which all calls will be delegated. */
     private final Iterator<T> iter;
 
-    /** Span that reperesents trace context in which iterator runs. */
+    /** Span that represents trace context in which iterator runs. */
     private final Span span;
 
     /**
@@ -41,9 +42,7 @@
 
     /** {@inheritDoc} */
     @Override public boolean hasNext() {
-        MTC.supportInitial(span);
-
-        try {
+        try (TraceSurroundings ignored = MTC.supportContinual(span)) {
             return iter.hasNext();
         }
         catch (Throwable th) {
@@ -55,9 +54,7 @@
 
     /** {@inheritDoc} */
     @Override public T next() {
-        MTC.supportInitial(span);
-
-        try {
+        try (TraceSurroundings ignored = MTC.supportContinual(span)) {
             return iter.next();
         }
         catch (Throwable th) {
diff --git a/modules/opencensus/src/test/java/org/apache/ignite/internal/processors/monitoring/opencensus/OpenCensusSqlJdbcTracingTest.java b/modules/opencensus/src/test/java/org/apache/ignite/internal/processors/monitoring/opencensus/OpenCensusSqlJdbcTracingTest.java
index a324ada..d8adcaa 100644
--- a/modules/opencensus/src/test/java/org/apache/ignite/internal/processors/monitoring/opencensus/OpenCensusSqlJdbcTracingTest.java
+++ b/modules/opencensus/src/test/java/org/apache/ignite/internal/processors/monitoring/opencensus/OpenCensusSqlJdbcTracingTest.java
@@ -21,6 +21,7 @@
 import java.sql.PreparedStatement;
 import java.sql.ResultSet;
 import java.sql.SQLException;
+import java.util.List;
 import java.util.Objects;
 import io.opencensus.trace.SpanId;
 import org.apache.ignite.client.Config;
@@ -30,6 +31,7 @@
 import org.junit.Test;
 
 import static java.sql.DriverManager.getConnection;
+import static org.apache.ignite.cache.CacheMode.PARTITIONED;
 import static org.apache.ignite.cache.CacheMode.REPLICATED;
 import static org.apache.ignite.internal.processors.query.QueryUtils.DFLT_SCHEMA;
 import static org.apache.ignite.internal.processors.tracing.SpanTags.SQL_PAGE_ROWS;
@@ -40,6 +42,7 @@
 import static org.apache.ignite.internal.processors.tracing.SpanType.SQL_ITER_CLOSE;
 import static org.apache.ignite.internal.processors.tracing.SpanType.SQL_ITER_OPEN;
 import static org.apache.ignite.internal.processors.tracing.SpanType.SQL_PAGE_FETCH;
+import static org.apache.ignite.internal.processors.tracing.SpanType.SQL_QRY;
 import static org.apache.ignite.internal.processors.tracing.SpanType.SQL_QRY_EXECUTE;
 import static org.apache.ignite.internal.processors.tracing.SpanType.SQL_QRY_PARSE;
 import static org.apache.ignite.internal.util.IgniteUtils.resolveIgnitePath;
@@ -121,6 +124,48 @@
     }
 
     /** {@inheritDoc} */
+    @Override public void testSelectQueryUserThreadSpanNotAffected() throws Exception {
+        String prsnTable = createTableAndPopulate(Person.class, PARTITIONED, 1);
+        String orgTable = createTableAndPopulate(Organization.class, PARTITIONED, 1);
+
+        String url = JDBC_URL_PREFIX + Config.SERVER + '/' + TEST_SCHEMA;
+
+        try (
+            Connection prsntConn = getConnection(url);
+            Connection orgConn = getConnection(url);
+
+            PreparedStatement prsntStmt = prsntConn.prepareStatement("SELECT * FROM " + prsnTable);
+            PreparedStatement orgStmt = orgConn.prepareStatement("SELECT * FROM " + orgTable)
+        ) {
+            prsntStmt.executeQuery();
+            orgStmt.executeQuery();
+
+            try (
+                ResultSet prsnResultSet = prsntStmt.getResultSet();
+                ResultSet orgResultSet = orgStmt.getResultSet()
+            ) {
+                while (prsnResultSet.next() && orgResultSet.next()) {
+                    // No-op.
+                }
+            }
+        }
+        catch (SQLException e) {
+            throw new RuntimeException(e);
+        }
+
+        handler().flush();
+
+        checkDroppedSpans();
+
+        List<SpanId> rootSpans = findRootSpans(SQL_QRY);
+
+        assertEquals(2, rootSpans.size());
+
+        for (SpanId rootSpan : rootSpans)
+            checkBasicSelectQuerySpanTree(rootSpan, TEST_TABLE_POPULATION);
+    }
+
+    /** {@inheritDoc} */
     @SuppressWarnings("StatementWithEmptyBody")
     @Override protected void executeQuery(
         String sql,
@@ -132,9 +177,11 @@
         String url = JDBC_URL_PREFIX + Config.SERVER + '/' + schema +
             "?skipReducerOnUpdate=" + skipReduceOnUpdate + "&distributedJoins=" + distributedJoins;
 
-        try (Connection conn = getConnection(url)) {
-            PreparedStatement stmt = conn.prepareStatement(sql);
+        try (
+            Connection conn = getConnection(url);
 
+            PreparedStatement stmt = conn.prepareStatement(sql)
+        ) {
             stmt.setFetchSize(PAGE_SIZE);
 
             if (isQry == null)
diff --git a/modules/opencensus/src/test/java/org/apache/ignite/internal/processors/monitoring/opencensus/OpenCensusSqlNativeTracingTest.java b/modules/opencensus/src/test/java/org/apache/ignite/internal/processors/monitoring/opencensus/OpenCensusSqlNativeTracingTest.java
index 9698329..309b6f7 100644
--- a/modules/opencensus/src/test/java/org/apache/ignite/internal/processors/monitoring/opencensus/OpenCensusSqlNativeTracingTest.java
+++ b/modules/opencensus/src/test/java/org/apache/ignite/internal/processors/monitoring/opencensus/OpenCensusSqlNativeTracingTest.java
@@ -18,6 +18,7 @@
 package org.apache.ignite.internal.processors.monitoring.opencensus;
 
 import java.util.Arrays;
+import java.util.Iterator;
 import java.util.List;
 import java.util.Set;
 import java.util.UUID;
@@ -33,6 +34,7 @@
 import org.apache.ignite.IgniteCheckedException;
 import org.apache.ignite.Ignition;
 import org.apache.ignite.cache.CacheMode;
+import org.apache.ignite.cache.query.FieldsQueryCursor;
 import org.apache.ignite.cache.query.SqlFieldsQuery;
 import org.apache.ignite.cache.query.annotations.QuerySqlField;
 import org.apache.ignite.configuration.CacheConfiguration;
@@ -42,6 +44,8 @@
 import org.apache.ignite.internal.TestRecordingCommunicationSpi;
 import org.apache.ignite.internal.processors.cache.query.SqlFieldsQueryEx;
 import org.apache.ignite.internal.processors.query.h2.twostep.messages.GridQueryNextPageRequest;
+import org.apache.ignite.internal.processors.tracing.MTC;
+import org.apache.ignite.internal.processors.tracing.NoopSpan;
 import org.apache.ignite.internal.processors.tracing.SpanType;
 import org.apache.ignite.internal.util.typedef.internal.U;
 import org.apache.ignite.spi.tracing.TracingConfigurationCoordinates;
@@ -319,7 +323,7 @@
                 ).boxed().collect(Collectors.toSet());
 
                 Set<Integer> parts = Arrays.stream(matcher.group(2).split(","))
-                    .map(s -> Integer.parseInt(s.trim()))
+                    .map(s -> parseInt(s.trim()))
                     .collect(Collectors.toSet());
 
                 assertEquals(expParts, parts);
@@ -489,6 +493,77 @@
     }
 
     /**
+     * Tests that tracing of multiple SELECT queries produces separate span tree for each query and does not affect
+     * user thread {@link MTC#span()} value during execution and after it.
+     */
+    @Test
+    public void testSelectQueryUserThreadSpanNotAffected() throws Exception {
+        String prsnTable = createTableAndPopulate(Person.class, PARTITIONED, 1);
+        String orgTable = createTableAndPopulate(Organization.class, PARTITIONED, 1);
+
+        try (
+            FieldsQueryCursor<List<?>> prsnQryCursor = reducer().context().query()
+                .querySqlFields(new SqlFieldsQuery("SELECT * FROM " + prsnTable), false);
+
+            FieldsQueryCursor<List<?>> orgQryCursor = reducer().context().query()
+                .querySqlFields(new SqlFieldsQuery("SELECT * FROM " + orgTable), false)
+        ) {
+            Iterator<List<?>> prsnQryIter = prsnQryCursor.iterator();
+            Iterator<List<?>> orgQryIter = orgQryCursor.iterator();
+
+            while (prsnQryIter.hasNext() && orgQryIter.hasNext()) {
+                assertEquals(NoopSpan.INSTANCE, MTC.span());
+
+                prsnQryIter.next();
+
+                assertEquals(NoopSpan.INSTANCE, MTC.span());
+
+                orgQryIter.next();
+
+                assertEquals(NoopSpan.INSTANCE, MTC.span());
+            }
+        }
+
+        assertEquals(NoopSpan.INSTANCE, MTC.span());
+
+        handler().flush();
+
+        checkDroppedSpans();
+
+        List<SpanId> rootSpans = findRootSpans(SQL_QRY);
+
+        assertEquals(2, rootSpans.size());
+
+        for (SpanId rootSpan : rootSpans)
+            checkBasicSelectQuerySpanTree(rootSpan, TEST_TABLE_POPULATION);
+    }
+
+    /**
+     * Checks presence of basic spans that related to SELECT SQL query and are childs of the specfied span.
+     *
+     * @param expRows Number of rows as a result of SELECT query.
+     * @param rootSpan Span which childs will be checked.
+     */
+    protected void checkBasicSelectQuerySpanTree(SpanId rootSpan, int expRows) {
+        int fetchedRows = 0;
+
+        SpanId iterSpan = checkChildSpan(SQL_ITER_OPEN, rootSpan);
+
+        SpanId fetchSpan = checkChildSpan(SQL_PAGE_FETCH, iterSpan);
+
+        fetchedRows += parseInt(getAttribute(fetchSpan, SQL_PAGE_ROWS));
+
+        List<SpanId> pageFetchSpans = findChildSpans(SQL_PAGE_FETCH, rootSpan);
+
+        for (SpanId span : pageFetchSpans)
+            fetchedRows += parseInt(getAttribute(span, SQL_PAGE_ROWS));
+
+        assertEquals(expRows, fetchedRows);
+
+        assertFalse(findChildSpans(SQL_CURSOR_CLOSE, rootSpan).isEmpty());
+    }
+
+    /**
      * Executes DML query and checks corresponding span tree.
      *
      * @param qry SQL query to execute.
@@ -546,6 +621,19 @@
     }
 
     /**
+     * Finds root spans with specified type.
+     *
+     * @param type Span type.
+     * @return Ids of the found spans.
+     */
+    protected List<SpanId> findRootSpans(SpanType type) {
+        return handler().allSpans()
+            .filter(span -> span.getParentSpanId() == null && type.spanName().equals(span.getName()))
+            .map(span -> span.getContext().getSpanId())
+            .collect(Collectors.toList());
+    }
+
+    /**
      * Obtains string representation of the attribtute from span with specified id.
      *
      * @param spanId Id of the target span.
@@ -642,7 +730,7 @@
     /**
      * Checks that no spans were dropped by OpencenCensus due to exporter buffer overflow.
      */
-    private void checkDroppedSpans() {
+    protected void checkDroppedSpans() {
         Object worker = U.field(Tracing.getExportComponent().getSpanExporter(), "worker");
 
         long droppedSpans = U.field(worker, "droppedSpans");