From ddb439976d2ded9f44c3b6efa7d91e0f0e55bbca Mon Sep 17 00:00:00 2001 From: "Daniel Q. Kim" Date: Fri, 24 Apr 2026 16:37:21 +0200 Subject: [PATCH] Backport: Re-add {database} macro support in clickhouse-client prompt Backport of ClickHouse/ClickHouse#100607 to antalya-25.8. - Dynamic {database} substitution in ClientBase::getPrompt() from default_database (updated by USE and --database). - ClientBase::syncDefaultDatabase() runs SELECT currentDatabase() after the handshake so the prompt reflects server-side default_database when --database is not passed. - Sync call in Client::connect() is guarded by is_interactive, default_database.empty(), and prompt contains {database}. - Expect tests for both clickhouse-client and clickhouse-local. Closes ClickHouse#49118 --- programs/client/Client.cpp | 12 +++++ programs/client/clickhouse-client.xml | 1 + src/Client/ClientBase.cpp | 20 +++++++- src/Client/ClientBase.h | 5 ++ .../04056_client_prompt_database.expect | 48 +++++++++++++++++++ .../04056_client_prompt_database.reference | 0 .../04057_local_prompt_database.expect | 40 ++++++++++++++++ 7 files changed, 125 insertions(+), 1 deletion(-) create mode 100755 tests/queries/0_stateless/04056_client_prompt_database.expect create mode 100644 tests/queries/0_stateless/04056_client_prompt_database.reference create mode 100755 tests/queries/0_stateless/04057_local_prompt_database.expect diff --git a/programs/client/Client.cpp b/programs/client/Client.cpp index f3d59a96750c..cc41a4ba549a 100644 --- a/programs/client/Client.cpp +++ b/programs/client/Client.cpp @@ -622,6 +622,18 @@ void Client::connect() boost::replace_all(prompt, "{" + key + "}", value); prompt = appendSmileyIfNeeded(prompt); + + /// Sync the current database from the server after the handshake, but only when all + /// three conditions hold: + /// - interactive: in batch mode the prompt is never displayed, so the extra + /// round-trip to the server would add latency with no visible benefit; + /// - no explicit --database flag: if the user passed --database, default_database + /// is already set to the correct value and must not be overridden by whatever + /// the server considers the current database; + /// - {database} appears in the prompt string: querying the server is pointless + /// when the macro is absent and the result would never be shown. + if (is_interactive && default_database.empty() && prompt.find("{database}") != String::npos) + syncDefaultDatabase(); } // Prints changed settings to stderr. Useful for debugging fuzzing failures. diff --git a/programs/client/clickhouse-client.xml b/programs/client/clickhouse-client.xml index e9408eeded24..f0df66427a55 100644 --- a/programs/client/clickhouse-client.xml +++ b/programs/client/clickhouse-client.xml @@ -23,6 +23,7 @@ {port} {user} {display_name} + {database} You can also use colored prompt, like in [1]. [1]: https://misc.flogisoft.com/bash/tip_colors_and_formatting diff --git a/src/Client/ClientBase.cpp b/src/Client/ClientBase.cpp index 8fe062107fe7..7710aa192e8e 100644 --- a/src/Client/ClientBase.cpp +++ b/src/Client/ClientBase.cpp @@ -2916,7 +2916,9 @@ bool ClientBase::processQueryText(const String & text) String ClientBase::getPrompt() const { - return prompt; + String result = prompt; + boost::replace_all(result, "{database}", default_database.empty() ? "default" : default_database); + return result; } @@ -3130,6 +3132,22 @@ std::string ClientBase::executeQueryForSingleString(const std::string & query) } } +void ClientBase::syncDefaultDatabase() +{ + try + { + const auto db = executeQueryForSingleString("SELECT currentDatabase()"); + if (!db.empty()) + default_database = db; + else + LOG_WARNING(getLogger("ClientBase"), "syncDefaultDatabase: server returned empty result for SELECT currentDatabase()"); + } + catch (...) + { + LOG_WARNING(getLogger("ClientBase"), "syncDefaultDatabase: failed to query current database from server: {}", getCurrentExceptionMessage(false)); + } +} + bool ClientBase::checkAIProviderAcknowledgment() { // If API key came from environment and user hasn't acknowledged yet, ask for confirmation diff --git a/src/Client/ClientBase.h b/src/Client/ClientBase.h index ef44dd610ce0..82360ac7191f 100644 --- a/src/Client/ClientBase.h +++ b/src/Client/ClientBase.h @@ -301,6 +301,11 @@ class ClientBase String appendSmileyIfNeeded(const String & prompt); + /// Query the server for the current database and update default_database. + /// Called after the connection handshake so that the prompt reflects the + /// server-side default when no --database flag was passed. + void syncDefaultDatabase(); + /// Should be one of the first, to be destroyed the last, /// since other members can use them. /// This holder may not be initialized in case if we run the client in the embedded mode (SSH). diff --git a/tests/queries/0_stateless/04056_client_prompt_database.expect b/tests/queries/0_stateless/04056_client_prompt_database.expect new file mode 100755 index 000000000000..1132e28e20ff --- /dev/null +++ b/tests/queries/0_stateless/04056_client_prompt_database.expect @@ -0,0 +1,48 @@ +#!/usr/bin/expect -f + +# Test that {database} macro in the clickhouse-client prompt reflects the current database +# and updates dynamically after USE statements. +# https://github.com/ClickHouse/ClickHouse/issues/49118 + +set basedir [file dirname $argv0] +set basename [file tail $argv0] +if {[info exists env(CLICKHOUSE_TMP)]} { + set CLICKHOUSE_TMP $env(CLICKHOUSE_TMP) +} else { + set CLICKHOUSE_TMP "." +} +exp_internal -f $CLICKHOUSE_TMP/$basename.debuglog 0 +set history_file $CLICKHOUSE_TMP/$basename.history + +log_user 0 +set timeout 60 +match_max 100000 + +expect_after { + # Do not ignore eof from expect + -i $any_spawn_id eof { exp_continue } + # A default timeout action is to do nothing, change it to fail + -i $any_spawn_id timeout { exit 1 } +} + +# Test 1: {database} in prompt shows the database set via --database flag. +# CLICKHOUSE_CLIENT_EXPECT_OPT includes --database=test (CLICKHOUSE_DATABASE). +spawn bash -c "source $basedir/../shell_config.sh ; \$CLICKHOUSE_CLIENT_BINARY \$CLICKHOUSE_CLIENT_EXPECT_OPT --prompt '{database}' --history_file=$history_file" +expect -re "\ntest :\\) $" + +# Test 2: {database} updates in the prompt after a USE statement. +send -- "USE system;\r" +expect "\nOk." +expect -re "\nsystem :\\) $" + +send -- "\4" +expect eof + +# Test 3: {database} shows the server's current database when --database flag is not passed. +# syncDefaultDatabase() fires SELECT currentDatabase() after the handshake and writes +# the result into default_database, so the prompt reflects the server-side default. +spawn bash -c "source $basedir/../shell_config.sh ; opts=\$(echo \"\$CLICKHOUSE_CLIENT_EXPECT_OPT\" | sed 's/--database=\[^ \]*//g') ; \$CLICKHOUSE_CLIENT_BINARY \$opts --prompt '{database}' --history_file=$history_file" +expect -re "\ndefault :\\) $" + +send -- "\4" +expect eof diff --git a/tests/queries/0_stateless/04056_client_prompt_database.reference b/tests/queries/0_stateless/04056_client_prompt_database.reference new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/tests/queries/0_stateless/04057_local_prompt_database.expect b/tests/queries/0_stateless/04057_local_prompt_database.expect new file mode 100755 index 000000000000..36883beb5697 --- /dev/null +++ b/tests/queries/0_stateless/04057_local_prompt_database.expect @@ -0,0 +1,40 @@ +#!/usr/bin/expect -f + +# Test that {database} macro in the clickhouse-local prompt reflects the current database +# and updates dynamically after USE statements. +# https://github.com/ClickHouse/ClickHouse/issues/49118 + +set basedir [file dirname $argv0] +set basename [file tail $argv0] +if {[info exists env(CLICKHOUSE_TMP)]} { + set CLICKHOUSE_TMP $env(CLICKHOUSE_TMP) +} else { + set CLICKHOUSE_TMP "." +} +exp_internal -f $CLICKHOUSE_TMP/$basename.debuglog 0 +set history_file $CLICKHOUSE_TMP/$basename.history + +log_user 0 +set timeout 60 +match_max 100000 + +expect_after { + # Do not ignore eof from expect + -i $any_spawn_id eof { exp_continue } + # A default timeout action is to do nothing, change it to fail + -i $any_spawn_id timeout { exit 1 } +} + +# Test 1: {database} in prompt shows "default" when --database flag is not passed. +# default_database is empty in that case; getPrompt() falls back to the literal +# string "default" so no server round-trip is needed. +spawn bash -c "source $basedir/../shell_config.sh ; \$CLICKHOUSE_LOCAL --disable_suggestion --enable-progress-table-toggle=0 --prompt '{database}' --history_file=$history_file" +expect -re "\ndefault :\\) $" + +# Test 2: {database} updates in the prompt after a USE statement. +send -- "USE system;\r" +expect "\nOk." +expect -re "\nsystem :\\) $" + +send -- "\4" +expect eof