Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions programs/client/Client.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@
#include <Common/Config/ConfigProcessor.h>
#include <Common/Config/getClientConfigPath.h>
#include <Common/CurrentThread.h>
#include <Common/DateLUT.h>
#include <Common/DateLUTImpl.h>
#include <Common/QueryScope.h>
#include <Common/Exception.h>
#include <Common/TerminalSize.h>
Expand Down Expand Up @@ -546,6 +548,13 @@ void Client::connect()
UInt64 server_version_minor = 0;
UInt64 server_version_patch = 0;

/// Capture the client local time zone before the branch below may switch the process default
/// to the server time zone. `serverTimezoneInstance()` reads the process default directly and
/// ignores `session_timezone`; `instance()` would fold in an explicit `--session_timezone` and
/// cache the wrong zone. `connect()` can run again on reconnect, so only capture once.
if (client_local_timezone.empty())
client_local_timezone = DateLUT::serverTimezoneInstance().getTimeZone();

if (hosts_and_ports.empty())
{
String host = config().getString("host", "localhost");
Expand Down
12 changes: 12 additions & 0 deletions src/Client/ClientBase.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,8 @@ namespace Setting
extern const SettingsFloatAuto promql_evaluation_time;
extern const SettingsBool into_outfile_create_parent_directories;
extern const SettingsBool ignore_format_null_for_explain;
extern const SettingsBool use_client_time_zone;
extern const SettingsTimezone session_timezone;
}

namespace ErrorCodes
Expand Down Expand Up @@ -2513,6 +2515,16 @@ void ClientBase::processParsedSingleQuery(

applySettingsFromServerIfNeeded(); // after connect() and applySettingsFromQuery()

/// With `use_client_time_zone`, DateTime string literals must be interpreted in the client time
/// zone. The client parses synchronous INSERT literals itself, but literals interpreted server-side
/// (asynchronous INSERT, SELECT) rely on `session_timezone`. Seed it with the client time zone unless
/// the user set `session_timezone` explicitly. This is transient (reverted with the other query
/// settings below), so it tracks per-query `use_client_time_zone` changes in both directions.
if (!client_local_timezone.empty()
&& client_context->getSettingsRef()[Setting::use_client_time_zone]
&& !client_context->getSettingsRef().isChanged("session_timezone"))
client_context->setSetting("session_timezone", client_local_timezone);

ASTPtr input_function;
const auto * insert = parsed_query->as<ASTInsertQuery>();
if (insert && insert->select)
Expand Down
5 changes: 5 additions & 0 deletions src/Client/ClientBase.h
Original file line number Diff line number Diff line change
Expand Up @@ -326,6 +326,11 @@ class ClientBase
ContextMutablePtr global_context;
ContextMutablePtr client_context;

/// The client local time zone, captured on the first connect() before it may switch the
/// process default to the server time zone. Used to seed `session_timezone` per query when
/// `use_client_time_zone` is set, so server-side literal parsing matches the client side.
String client_local_timezone;

String default_database;
String query_id;
Int32 suggestion_limit{};
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
flag_async 1500036000
flag_sync 1500036000
reset_default_async 1500036000
set_async 1500036000
set_sync 1500036000
settings_async 1500036000
reset_async matches server tz 1
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
#!/usr/bin/env bash
# Tags: no-fasttest, no-random-settings
# ^ no-random-settings: the runner must not inject a randomized `session_timezone`; an explicit
# `session_timezone` (even empty) is an intentional user override and disables the client-time-zone
# propagation this test exercises.

CURDIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)
# shellcheck source=../shell_config.sh
. "$CURDIR"/../shell_config.sh

# With use_client_time_zone=1, a DateTime string literal must be interpreted in the client time zone
# regardless of whether the INSERT is synchronous or asynchronous. The async path parses the VALUES
# block on the server, so the client has to propagate its local time zone as session_timezone, and it
# must do so for every query (not only at connect time), tracking use_client_time_zone changes in both
# directions. America/Hermosillo is a fixed UTC-7 zone (no DST), so 2017-07-14 05:40:00 there is the
# stable instant 1500036000.

TZC="env TZ=America/Hermosillo ${CLICKHOUSE_CLIENT}"

${CLICKHOUSE_CLIENT} -q "CREATE TABLE ${CLICKHOUSE_DATABASE}.dt (a DateTime, kind String) ENGINE = Memory"

# use_client_time_zone via the command line flag (async and sync).
$TZC --use_client_time_zone=1 -q \
"INSERT INTO ${CLICKHOUSE_DATABASE}.dt SETTINGS async_insert = 1, wait_for_async_insert = 1 VALUES ('2017-07-14 05:40:00', 'flag_async')"
$TZC --use_client_time_zone=1 -q \
"INSERT INTO ${CLICKHOUSE_DATABASE}.dt SETTINGS async_insert = 0 VALUES ('2017-07-14 05:40:00', 'flag_sync')"

# use_client_time_zone turned on mid-session with SET, on an already-open connection.
$TZC -mn -q "
SET use_client_time_zone = 1;
INSERT INTO ${CLICKHOUSE_DATABASE}.dt SETTINGS async_insert = 1, wait_for_async_insert = 1 VALUES ('2017-07-14 05:40:00', 'set_async');
INSERT INTO ${CLICKHOUSE_DATABASE}.dt SETTINGS async_insert = 0 VALUES ('2017-07-14 05:40:00', 'set_sync');
"

# use_client_time_zone set only per query via SETTINGS, without the command line flag.
$TZC -q \
"INSERT INTO ${CLICKHOUSE_DATABASE}.dt SETTINGS use_client_time_zone = 1, async_insert = 1, wait_for_async_insert = 1 VALUES ('2017-07-14 05:40:00', 'settings_async')"

# Starting with an explicit --session_timezone override and then clearing it with
# SET session_timezone = DEFAULT must fall back to the client time zone, not to the override value.
$TZC --use_client_time_zone=1 --session_timezone=UTC -mn -q "
SET session_timezone = DEFAULT;
INSERT INTO ${CLICKHOUSE_DATABASE}.dt SETTINGS async_insert = 1, wait_for_async_insert = 1 VALUES ('2017-07-14 05:40:00', 'reset_default_async');
"

# All of the above interpret the literal in the client time zone: 2017-07-14 05:40:00 = 1500036000.
${CLICKHOUSE_CLIENT} -q "SELECT kind, toUnixTimestamp(a) FROM ${CLICKHOUSE_DATABASE}.dt ORDER BY kind"

# Turning use_client_time_zone back off must restore server-side parsing (the stored instant no longer
# depends on the client time zone). The exact value depends on the server time zone, so compare it with
# a plain default insert instead of hard-coding it.
$TZC -q \
"INSERT INTO ${CLICKHOUSE_DATABASE}.dt SETTINGS async_insert = 1, wait_for_async_insert = 1 VALUES ('2017-07-14 05:40:00', 'server_ref')"
$TZC --use_client_time_zone=1 -mn -q "
SET use_client_time_zone = 0;
INSERT INTO ${CLICKHOUSE_DATABASE}.dt SETTINGS async_insert = 1, wait_for_async_insert = 1 VALUES ('2017-07-14 05:40:00', 'reset_async');
"
${CLICKHOUSE_CLIENT} -q "
SELECT 'reset_async matches server tz', (
(SELECT toUnixTimestamp(a) FROM ${CLICKHOUSE_DATABASE}.dt WHERE kind = 'reset_async')
= (SELECT toUnixTimestamp(a) FROM ${CLICKHOUSE_DATABASE}.dt WHERE kind = 'server_ref'))
"

${CLICKHOUSE_CLIENT} -q "DROP TABLE ${CLICKHOUSE_DATABASE}.dt"
Loading