From d2b3297fe0c1ab9921729d52d0712ccd4cca3bc8 Mon Sep 17 00:00:00 2001 From: HTHou Date: Thu, 12 Nov 2026 11:52:48 +0800 Subject: [PATCH 1/2] Upgrade JLine to 3.30.17 --- LICENSE-binary | 2 +- pom.xml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/LICENSE-binary b/LICENSE-binary index 14264f0f701d..6c79ced653d6 100644 --- a/LICENSE-binary +++ b/LICENSE-binary @@ -304,7 +304,7 @@ org.yaml:snakeyaml:2.6 BSD 3-Clause ------------ org.antlr:antlr4-runtime:4.13.2 -org.jline:jline:3.26.2 +org.jline:jline:3.30.17 org.ow2.asm:asm:9.3 diff --git a/pom.xml b/pom.xml index 9dfb37abed86..6286660c83c7 100644 --- a/pom.xml +++ b/pom.xml @@ -99,7 +99,7 @@ 1.0-1 3.1.11 12.0.41 - 3.26.2 + 3.30.17 5.14.0 3.1 4.13.2 From 7653619f7246350c8406abde33022ad4e51c217f Mon Sep 17 00:00:00 2001 From: HTHou Date: Thu, 12 Nov 2026 14:23:32 +0800 Subject: [PATCH 2/2] Preserve CLI exit confirmation on Ctrl+C Typing part of a statement and pressing Ctrl+C can surface as EOF on JLine 3.30.17. Consume the interrupt flag to show the existing exit confirmation. Ctrl+D at an empty prompt continues to exit directly. --- .../main/java/org/apache/iotdb/cli/Cli.java | 11 ++- .../java/org/apache/iotdb/cli/CliTest.java | 85 +++++++++++++++++++ 2 files changed, 93 insertions(+), 3 deletions(-) create mode 100644 iotdb-client/cli/src/test/java/org/apache/iotdb/cli/CliTest.java diff --git a/iotdb-client/cli/src/main/java/org/apache/iotdb/cli/Cli.java b/iotdb-client/cli/src/main/java/org/apache/iotdb/cli/Cli.java index 8962a5a5526b..6546e36e6236 100644 --- a/iotdb-client/cli/src/main/java/org/apache/iotdb/cli/Cli.java +++ b/iotdb-client/cli/src/main/java/org/apache/iotdb/cli/Cli.java @@ -283,7 +283,7 @@ private static String buildJdbcUrl(String host, String port) { return Config.IOTDB_URL_PREFIX + formatEndpoint(host, port) + "/"; } - private static boolean readerReadLine(CliContext ctx, IoTDBConnection connection) { + static boolean readerReadLine(CliContext ctx, IoTDBConnection connection) { String s; try { s = ctx.getLineReader().readLine(cliPrefix + "> ", null); @@ -295,8 +295,13 @@ private static boolean readerReadLine(CliContext ctx, IoTDBConnection connection // Exit on signal INT requires confirmation. readLine(ctx); } catch (EndOfFileException e) { - // Exit on EOF (usually by pressing CTRL+D). - ctx.exit(CODE_OK); + // JLine 3.30.16 and later can report a SIGINT-interrupted PTY read as EOF. + if (Thread.interrupted()) { + readLine(ctx); + } else { + // Exit on EOF (usually by pressing CTRL+D). + ctx.exit(CODE_OK); + } } catch (IllegalArgumentException e) { if (e.getMessage().contains("history")) { return false; diff --git a/iotdb-client/cli/src/test/java/org/apache/iotdb/cli/CliTest.java b/iotdb-client/cli/src/test/java/org/apache/iotdb/cli/CliTest.java new file mode 100644 index 000000000000..6681fb7af5a1 --- /dev/null +++ b/iotdb-client/cli/src/test/java/org/apache/iotdb/cli/CliTest.java @@ -0,0 +1,85 @@ +/* + * 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.iotdb.cli; + +import org.apache.iotdb.cli.i18n.CliMessages; +import org.apache.iotdb.cli.utils.CliContext; + +import org.jline.reader.EndOfFileException; +import org.jline.reader.LineReader; +import org.junit.Before; +import org.junit.Test; + +import static org.junit.Assert.assertFalse; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.ArgumentMatchers.isNull; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +public class CliTest { + private final CliContext context = mock(CliContext.class); + private final LineReader reader = mock(LineReader.class); + + @Before + public void setUp() { + when(context.getLineReader()).thenReturn(reader); + } + + @Test + public void testInterruptedEofRequiresExitConfirmation() { + when(reader.readLine(anyString(), isNull())) + .thenAnswer( + invocation -> { + Thread.currentThread().interrupt(); + throw new EndOfFileException(); + }); + when(reader.readLine(anyString(), eq('\0'))) + .thenAnswer( + invocation -> { + assertFalse(Thread.currentThread().isInterrupted()); + return ""; + }); + + try { + assertFalse(Cli.readerReadLine(context, null)); + verify(reader) + .readLine( + CliMessages.EXCEPTION_PRESS_CTRL_C_AGAIN_TO_EXIT_OR_PRESS_ENTER_TO_CONTINUE_8CA49A92, + '\0'); + verify(context, never()).exit(anyInt()); + } finally { + Thread.interrupted(); + } + } + + @Test + public void testEofExitsWithoutConfirmation() { + when(reader.readLine(anyString(), isNull())).thenThrow(new EndOfFileException()); + + Cli.readerReadLine(context, null); + + verify(context).exit(AbstractCli.CODE_OK); + verify(reader, never()).readLine(anyString(), eq('\0')); + } +}