This is an automated email from the ASF dual-hosted git repository.

apkhmv pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/ignite-3.git


The following commit(s) were added to refs/heads/main by this push:
     new 2eb9367a1d6 IGNITE-24602 CLI: Add verbose output for SQL commands (-v, 
-vv, -vvv) (#7572)
2eb9367a1d6 is described below

commit 2eb9367a1d6014e7c9b401208211e5a39c24ec36
Author: Aleksandr Pakhomov <[email protected]>
AuthorDate: Thu Feb 12 15:31:04 2026 +0300

    IGNITE-24602 CLI: Add verbose output for SQL commands (-v, -vv, -vvv) 
(#7572)
---
 .../cli/commands/sql/ItSqlReplCommandTest.java     | 40 ++++++++++
 .../cli/commands/sql/ItSqlVerbosityTest.java       | 92 ++++++++++++++++++++++
 .../internal/cli/commands/sql/SqlCommand.java      |  4 +
 .../internal/cli/commands/sql/SqlReplCommand.java  |  4 +
 .../ignite/internal/cli/logger/CliLoggers.java     | 25 ++++++
 .../apache/ignite/internal/cli/sql/SqlManager.java | 47 ++++++++++-
 6 files changed, 210 insertions(+), 2 deletions(-)

diff --git 
a/modules/cli/src/integrationTest/java/org/apache/ignite/internal/cli/commands/sql/ItSqlReplCommandTest.java
 
b/modules/cli/src/integrationTest/java/org/apache/ignite/internal/cli/commands/sql/ItSqlReplCommandTest.java
index 995d8c921b2..33bdfad7d4a 100644
--- 
a/modules/cli/src/integrationTest/java/org/apache/ignite/internal/cli/commands/sql/ItSqlReplCommandTest.java
+++ 
b/modules/cli/src/integrationTest/java/org/apache/ignite/internal/cli/commands/sql/ItSqlReplCommandTest.java
@@ -118,6 +118,46 @@ class ItSqlReplCommandTest extends CliIntegrationTest {
         );
     }
 
+    @Test
+    @DisplayName("Verbose flag should produce JDBC and SQL diagnostic output 
on stderr")
+    void verboseBasic() {
+        execute("-v", "SELECT 1;", "--jdbc-url", JDBC_URL);
+
+        assertAll(
+                () -> assertOutputIsNotEmpty(),
+                () -> assertErrOutputContains("--> JDBC"),
+                () -> assertErrOutputContains("--> SQL"),
+                () -> assertErrOutputContains("<-- 1 row(s)")
+        );
+    }
+
+    @Test
+    @DisplayName("Double verbose flag should also produce column metadata")
+    void verboseColumns() {
+        execute("-v", "-v", "SELECT 1;", "--jdbc-url", JDBC_URL);
+
+        assertAll(
+                () -> assertOutputIsNotEmpty(),
+                () -> assertErrOutputContains("--> JDBC"),
+                () -> assertErrOutputContains("--> SQL"),
+                () -> assertErrOutputContains("<-- Columns:")
+        );
+    }
+
+    @Test
+    @DisplayName("Triple verbose flag should also produce driver info")
+    void verboseDriver() {
+        execute("-v", "-v", "-v", "SELECT 1;", "--jdbc-url", JDBC_URL);
+
+        assertAll(
+                () -> assertOutputIsNotEmpty(),
+                () -> assertErrOutputContains("--> JDBC"),
+                () -> assertErrOutputContains("--> Driver:"),
+                () -> assertErrOutputContains("--> SQL"),
+                () -> assertErrOutputContains("<-- Columns:")
+        );
+    }
+
     @Bean
     @Replaces(ReplExecutorProvider.class)
     public ReplExecutorProvider replExecutorProvider() {
diff --git 
a/modules/cli/src/integrationTest/java/org/apache/ignite/internal/cli/commands/sql/ItSqlVerbosityTest.java
 
b/modules/cli/src/integrationTest/java/org/apache/ignite/internal/cli/commands/sql/ItSqlVerbosityTest.java
new file mode 100644
index 00000000000..3e539f1660b
--- /dev/null
+++ 
b/modules/cli/src/integrationTest/java/org/apache/ignite/internal/cli/commands/sql/ItSqlVerbosityTest.java
@@ -0,0 +1,92 @@
+/*
+ * 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.cli.commands.sql;
+
+import static org.junit.jupiter.api.Assertions.assertAll;
+
+import org.apache.ignite.internal.cli.CliIntegrationTest;
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Test;
+
+/**
+ * Tests for SQL verbose output ({@code -v}, {@code -vv}, {@code -vvv}).
+ */
+class ItSqlVerbosityTest extends CliIntegrationTest {
+    @Override
+    protected int initialNodes() {
+        return 1;
+    }
+
+    @Test
+    @DisplayName("No verbose flag should not produce stderr output")
+    void noVerbose() {
+        execute("sql", "select 1", "--jdbc-url", JDBC_URL);
+
+        assertAll(
+                this::assertExitCodeIsZero,
+                this::assertOutputIsNotEmpty,
+                this::assertErrOutputIsEmpty
+        );
+    }
+
+    @Test
+    @DisplayName("-v should show JDBC URL and SQL query in stderr")
+    void verboseBasic() {
+        execute("sql", "select 1", "--jdbc-url", JDBC_URL, "-v");
+
+        assertAll(
+                this::assertExitCodeIsZero,
+                this::assertOutputIsNotEmpty,
+                () -> assertErrOutputContains("--> JDBC"),
+                () -> assertErrOutputContains("--> SQL select 1"),
+                () -> assertErrOutputContains("<-- 1 row(s)"),
+                () -> assertErrOutputDoesNotContain("<-- Columns:"),
+                () -> assertErrOutputDoesNotContain("--> Driver:")
+        );
+    }
+
+    @Test
+    @DisplayName("-vv should additionally show column metadata")
+    void verboseHeaders() {
+        execute("sql", "select 1", "--jdbc-url", JDBC_URL, "-vv");
+
+        assertAll(
+                this::assertExitCodeIsZero,
+                this::assertOutputIsNotEmpty,
+                () -> assertErrOutputContains("--> JDBC"),
+                () -> assertErrOutputContains("--> SQL select 1"),
+                () -> assertErrOutputContains("<-- Columns:"),
+                () -> assertErrOutputDoesNotContain("--> Driver:")
+        );
+    }
+
+    @Test
+    @DisplayName("-vvv should additionally show driver info")
+    void verboseBody() {
+        execute("sql", "select 1", "--jdbc-url", JDBC_URL, "-vvv");
+
+        assertAll(
+                this::assertExitCodeIsZero,
+                this::assertOutputIsNotEmpty,
+                () -> assertErrOutputContains("--> JDBC"),
+                () -> assertErrOutputContains("--> Driver:"),
+                () -> assertErrOutputContains("--> SQL select 1"),
+                () -> assertErrOutputContains("<-- Columns:")
+        );
+    }
+}
diff --git 
a/modules/cli/src/main/java/org/apache/ignite/internal/cli/commands/sql/SqlCommand.java
 
b/modules/cli/src/main/java/org/apache/ignite/internal/cli/commands/sql/SqlCommand.java
index 43fb51096d0..b0bd4302f56 100644
--- 
a/modules/cli/src/main/java/org/apache/ignite/internal/cli/commands/sql/SqlCommand.java
+++ 
b/modules/cli/src/main/java/org/apache/ignite/internal/cli/commands/sql/SqlCommand.java
@@ -27,6 +27,7 @@ import static 
org.apache.ignite.internal.cli.commands.Options.Constants.SCRIPT_F
 import static 
org.apache.ignite.internal.cli.commands.Options.Constants.SCRIPT_FILE_OPTION_DESC;
 import static 
org.apache.ignite.internal.cli.commands.Options.Constants.TIMED_OPTION;
 import static 
org.apache.ignite.internal.cli.commands.Options.Constants.TIMED_OPTION_DESC;
+import static 
org.apache.ignite.internal.cli.commands.Options.Constants.VERBOSE_OPTION_SHORT;
 
 import java.util.ArrayList;
 import java.util.Collections;
@@ -123,6 +124,9 @@ public class SqlCommand extends BaseCommand implements 
Callable<Integer> {
         if (file != null) {
             result.add(SCRIPT_FILE_OPTION + "=" + file);
         }
+        for (int i = 0; i < verbose.length; i++) {
+            result.add(VERBOSE_OPTION_SHORT);
+        }
         return result.toArray(new String[0]);
     }
 }
diff --git 
a/modules/cli/src/main/java/org/apache/ignite/internal/cli/commands/sql/SqlReplCommand.java
 
b/modules/cli/src/main/java/org/apache/ignite/internal/cli/commands/sql/SqlReplCommand.java
index b54f5efbe79..166393bc35e 100644
--- 
a/modules/cli/src/main/java/org/apache/ignite/internal/cli/commands/sql/SqlReplCommand.java
+++ 
b/modules/cli/src/main/java/org/apache/ignite/internal/cli/commands/sql/SqlReplCommand.java
@@ -31,6 +31,7 @@ import static 
org.apache.ignite.internal.cli.commands.Options.Constants.SCRIPT_F
 import static 
org.apache.ignite.internal.cli.commands.Options.Constants.SCRIPT_FILE_OPTION_DESC;
 import static 
org.apache.ignite.internal.cli.commands.Options.Constants.TIMED_OPTION;
 import static 
org.apache.ignite.internal.cli.commands.Options.Constants.TIMED_OPTION_DESC;
+import static 
org.apache.ignite.internal.cli.commands.Options.Constants.VERBOSE_OPTION_SHORT;
 
 import java.util.ArrayList;
 import java.util.Collections;
@@ -138,6 +139,9 @@ public class SqlReplCommand extends BaseCommand implements 
Callable<Integer> {
         if (file != null) {
             result.add(SCRIPT_FILE_OPTION + "=" + file);
         }
+        for (int i = 0; i < verbose.length; i++) {
+            result.add(VERBOSE_OPTION_SHORT);
+        }
         return result.toArray(new String[0]);
     }
 }
diff --git 
a/modules/cli/src/main/java/org/apache/ignite/internal/cli/logger/CliLoggers.java
 
b/modules/cli/src/main/java/org/apache/ignite/internal/cli/logger/CliLoggers.java
index 85c998dacef..086a81ba6b6 100644
--- 
a/modules/cli/src/main/java/org/apache/ignite/internal/cli/logger/CliLoggers.java
+++ 
b/modules/cli/src/main/java/org/apache/ignite/internal/cli/logger/CliLoggers.java
@@ -89,6 +89,9 @@ public class CliLoggers {
      * Stops redirecting output previously started by {@link 
CliLoggers#startOutputRedirect(PrintWriter, boolean[])}.
      */
     public static void stopOutputRedirect() {
+        if (output != null) {
+            output.flush();
+        }
         output = null;
         isVerbose = false;
         verbose = new boolean[0];
@@ -104,6 +107,28 @@ public class CliLoggers {
         return isVerbose;
     }
 
+    /**
+     * Returns the current verbose level (0 = not verbose, 1 = -v, 2 = -vv, 3 
= -vvv).
+     *
+     * @return verbosity level.
+     */
+    public static int getVerboseLevel() {
+        return verbose == null ? 0 : verbose.length;
+    }
+
+    /**
+     * Writes a verbose message if the current verbose level is at least 
{@code minLevel}.
+     *
+     * @param minLevel Minimum verbosity level required for this message.
+     * @param message Message to print.
+     */
+    public static void verboseLog(int minLevel, String message) {
+        if (isVerbose && verbose.length >= minLevel) {
+            output.println(message);
+            output.flush();
+        }
+    }
+
     private static class CliLogger implements Logger {
 
         private final Logger delegate;
diff --git 
a/modules/cli/src/main/java/org/apache/ignite/internal/cli/sql/SqlManager.java 
b/modules/cli/src/main/java/org/apache/ignite/internal/cli/sql/SqlManager.java
index 0a9989066b9..2ba05801d03 100644
--- 
a/modules/cli/src/main/java/org/apache/ignite/internal/cli/sql/SqlManager.java
+++ 
b/modules/cli/src/main/java/org/apache/ignite/internal/cli/sql/SqlManager.java
@@ -21,8 +21,11 @@ import java.sql.Connection;
 import java.sql.DatabaseMetaData;
 import java.sql.DriverManager;
 import java.sql.ResultSet;
+import java.sql.ResultSetMetaData;
 import java.sql.SQLException;
 import java.sql.Statement;
+import java.util.StringJoiner;
+import org.apache.ignite.internal.cli.logger.CliLoggers;
 import org.apache.ignite.internal.cli.sql.SqlQueryResult.SqlQueryResultBuilder;
 import org.apache.ignite.internal.cli.sql.table.Table;
 
@@ -30,12 +33,17 @@ import org.apache.ignite.internal.cli.sql.table.Table;
  * Manager to work with any sql operation.
  */
 public class SqlManager implements AutoCloseable {
+    private final String jdbcUrl;
+
     private final Connection connection;
 
+    private boolean connectionInfoLogged;
+
     /**
      * Default constructor.
      */
     public SqlManager(String jdbcUrl) throws SQLException {
+        this.jdbcUrl = jdbcUrl;
         connection = DriverManager.getConnection(jdbcUrl);
     }
 
@@ -47,6 +55,8 @@ public class SqlManager implements AutoCloseable {
      * @throws SQLException in any case when SQL command can't be executed.
      */
     public SqlQueryResult execute(String sql) throws SQLException {
+        logConnectionInfo();
+        CliLoggers.verboseLog(1, "--> SQL " + sql);
 
         SqlQueryResultBuilder sqlQueryResultBuilder = new 
SqlQueryResultBuilder();
 
@@ -54,17 +64,23 @@ public class SqlManager implements AutoCloseable {
         try (Statement statement = connection.createStatement()) {
             statement.execute(sql);
 
+            int totalRows = 0;
             do {
                 ResultSet rs = statement.getResultSet();
                 if (rs != null) {
-                    sqlQueryResultBuilder.addTable(Table.fromResultSet(rs));
+                    logColumnMetadata(rs.getMetaData());
+                    Table<String> table = Table.fromResultSet(rs);
+                    totalRows += table.content().length;
+                    sqlQueryResultBuilder.addTable(table);
                 } else {
                     int updateCount = statement.getUpdateCount();
                     sqlQueryResultBuilder.addMessage(updateCount >= 0 ? 
"Updated " + updateCount + " rows." : "OK!");
                 }
             } while (statement.getMoreResults() || statement.getUpdateCount() 
!= -1);
 
-            sqlQueryResultBuilder.setDurationMs(System.currentTimeMillis() - 
startTime);
+            long durationMs = System.currentTimeMillis() - startTime;
+            sqlQueryResultBuilder.setDurationMs(durationMs);
+            CliLoggers.verboseLog(1, "<-- " + totalRows + " row(s) (" + 
durationMs + "ms)");
             return sqlQueryResultBuilder.build();
         }
     }
@@ -77,4 +93,31 @@ public class SqlManager implements AutoCloseable {
     public DatabaseMetaData getMetadata() throws SQLException {
         return connection.getMetaData();
     }
+
+    private void logConnectionInfo() throws SQLException {
+        if (connectionInfoLogged) {
+            return;
+        }
+        connectionInfoLogged = true;
+
+        CliLoggers.verboseLog(1, "--> JDBC " + jdbcUrl);
+
+        if (CliLoggers.getVerboseLevel() >= 3) {
+            DatabaseMetaData meta = connection.getMetaData();
+            CliLoggers.verboseLog(3, "--> Driver: " + meta.getDriverName() + " 
" + meta.getDriverVersion());
+        }
+    }
+
+    private static void logColumnMetadata(ResultSetMetaData metaData) throws 
SQLException {
+        if (CliLoggers.getVerboseLevel() < 2) {
+            return;
+        }
+
+        int columnCount = metaData.getColumnCount();
+        StringJoiner joiner = new StringJoiner(", ");
+        for (int i = 1; i <= columnCount; i++) {
+            joiner.add(metaData.getColumnLabel(i) + " (" + 
metaData.getColumnTypeName(i) + ")");
+        }
+        CliLoggers.verboseLog(2, "<-- Columns: " + joiner);
+    }
 }

Reply via email to