diff --git a/.gitattributes b/.gitattributes
index bcc7d57b904..a23f027122b 100644
--- a/.gitattributes
+++ b/.gitattributes
@@ -1,2 +1,3 @@
contrib/* linguist-vendored
*.h linguist-language=C++
+tests/queries/0_stateless/data_json/* binary
diff --git a/docker/test/integration/base/Dockerfile b/docker/test/integration/base/Dockerfile
index 91b26735fe5..eaf0f01e36d 100644
--- a/docker/test/integration/base/Dockerfile
+++ b/docker/test/integration/base/Dockerfile
@@ -60,5 +60,5 @@ clientPort=2181 \n\
maxClientCnxns=80' > /opt/zookeeper/conf/zoo.cfg
RUN mkdir /zookeeper && chmod -R 777 /zookeeper
-ENV TZ=Europe/Moscow
+ENV TZ=Etc/UTC
RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone
diff --git a/docker/test/integration/runner/Dockerfile b/docker/test/integration/runner/Dockerfile
index 22dd2e14456..b5c6a39a965 100644
--- a/docker/test/integration/runner/Dockerfile
+++ b/docker/test/integration/runner/Dockerfile
@@ -40,7 +40,7 @@ RUN apt-get update \
/tmp/* \
&& apt-get clean
-ENV TZ=Europe/Moscow
+ENV TZ=Etc/UTC
RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone
ENV DOCKER_CHANNEL stable
diff --git a/docs/en/sql-reference/functions/ip-address-functions.md b/docs/en/sql-reference/functions/ip-address-functions.md
index 469a66d460f..cf3f92580aa 100644
--- a/docs/en/sql-reference/functions/ip-address-functions.md
+++ b/docs/en/sql-reference/functions/ip-address-functions.md
@@ -13,10 +13,18 @@ Alias: `INET_NTOA`.
## IPv4StringToNum(s) {#ipv4stringtonums}
-The reverse function of IPv4NumToString. If the IPv4 address has an invalid format, it returns 0.
+The reverse function of IPv4NumToString. If the IPv4 address has an invalid format, it throws exception.
Alias: `INET_ATON`.
+## IPv4StringToNumOrDefault(s) {#ipv4stringtonums}
+
+Same as `IPv4StringToNum`, but if the IPv4 address has an invalid format, it returns 0.
+
+## IPv4StringToNumOrNull(s) {#ipv4stringtonums}
+
+Same as `IPv4StringToNum`, but if the IPv4 address has an invalid format, it returns null.
+
## IPv4NumToStringClassC(num) {#ipv4numtostringclasscnum}
Similar to IPv4NumToString, but using xxx instead of the last octet.
@@ -123,7 +131,7 @@ LIMIT 10
## IPv6StringToNum {#ipv6stringtonums}
-The reverse function of [IPv6NumToString](#ipv6numtostringx). If the IPv6 address has an invalid format, it returns a string of null bytes.
+The reverse function of [IPv6NumToString](#ipv6numtostringx). If the IPv6 address has an invalid format, it throws exception.
If the input string contains a valid IPv4 address, returns its IPv6 equivalent.
HEX can be uppercase or lowercase.
@@ -168,6 +176,14 @@ Result:
- [cutIPv6](#cutipv6x-bytestocutforipv6-bytestocutforipv4).
+## IPv6StringToNumOrDefault(s) {#ipv6stringtonums}
+
+Same as `IPv6StringToNum`, but if the IPv6 address has an invalid format, it returns 0.
+
+## IPv6StringToNumOrNull(s) {#ipv6stringtonums}
+
+Same as `IPv6StringToNum`, but if the IPv6 address has an invalid format, it returns null.
+
## IPv4ToIPv6(x) {#ipv4toipv6x}
Takes a `UInt32` number. Interprets it as an IPv4 address in [big endian](https://en.wikipedia.org/wiki/Endianness). Returns a `FixedString(16)` value containing the IPv6 address in binary format. Examples:
@@ -261,6 +277,14 @@ SELECT
└───────────────────────────────────┴──────────────────────────┘
```
+## toIPv4OrDefault(string) {#toipv4ordefaultstring}
+
+Same as `toIPv4`, but if the IPv4 address has an invalid format, it returns 0.
+
+## toIPv4OrNull(string) {#toipv4ornullstring}
+
+Same as `toIPv4`, but if the IPv4 address has an invalid format, it returns null.
+
## toIPv6 {#toipv6string}
Converts a string form of IPv6 address to [IPv6](../../sql-reference/data-types/domains/ipv6.md) type. If the IPv6 address has an invalid format, returns an empty value.
@@ -317,6 +341,14 @@ Result:
└─────────────────────┘
```
+## IPv6StringToNumOrDefault(s) {#toipv6ordefaultstring}
+
+Same as `toIPv6`, but if the IPv6 address has an invalid format, it returns 0.
+
+## IPv6StringToNumOrNull(s) {#toipv6ornullstring}
+
+Same as `toIPv6`, but if the IPv6 address has an invalid format, it returns null.
+
## isIPv4String {#isipv4string}
Determines whether the input string is an IPv4 address or not. If `string` is IPv6 address returns `0`.
diff --git a/docs/en/sql-reference/functions/statistics.md b/docs/en/sql-reference/functions/statistics.md
new file mode 100644
index 00000000000..3f337b05cbc
--- /dev/null
+++ b/docs/en/sql-reference/functions/statistics.md
@@ -0,0 +1,48 @@
+---
+toc_priority: 69
+toc_title: Statistics
+---
+
+# Functions for Working with Statistics {#functions-for-working-with-statistics}
+
+# proportionsZTest {#proportionsztest}
+
+Applies proportion z-test to samples from two populations (X and Y). The alternative is 'two-sided'.
+
+**Syntax**
+
+``` sql
+proportionsZTest(successes_x, successes_y, trials_x, trials_y, significance_level, usevar)
+```
+
+**Arguments**
+
+- `successes_x` — The number of successes for X in trials.
+- `successes_y` — The number of successes for X in trials.
+- `trials_x` — The number of trials for X.
+- `trials_y` — The number of trials for Y.
+- `significance_level`
+- `usevar` - It can be `'pooled'` or `'unpooled'`.
+ - `'pooled'` - The variance of the two populations are assumed to be equal.
+ - `'unpooled'` - The assumption of equal variances is dropped.
+
+**Returned value**
+
+- A tuple with the (z-statistic, p-value, confidence-interval-lower, confidence-interval-upper).
+
+Type: [Tuple](../../sql-reference/data-types/tuple.md).
+
+**Example**
+
+Query:
+
+``` sql
+SELECT proportionsZTest(10, 11, 100, 101, 0.95, 'unpooled');
+```
+
+Result:
+
+``` text
+(-0.20656724435948853,0.8363478437079654,-0.09345975390115283,0.07563797172293502)
+```
+
diff --git a/docs/tools/requirements.txt b/docs/tools/requirements.txt
index 4e0789b5d24..8bf1a5f477c 100644
--- a/docs/tools/requirements.txt
+++ b/docs/tools/requirements.txt
@@ -1,4 +1,4 @@
-Babel==2.8.0
+Babel==2.9.1
backports-abc==0.5
backports.functools-lru-cache==1.6.1
beautifulsoup4==4.9.1
@@ -10,22 +10,22 @@ cssmin==0.2.0
future==0.18.2
htmlmin==0.1.12
idna==2.10
-Jinja2>=2.11.3
+Jinja2>=3.0.3
jinja2-highlight==0.6.1
jsmin==3.0.0
-livereload==2.6.2
+livereload==2.6.3
Markdown==3.3.2
-MarkupSafe==1.1.1
+MarkupSafe==2.1.0
mkdocs==1.1.2
mkdocs-htmlproofer-plugin==0.0.3
mkdocs-macros-plugin==0.4.20
-nltk==3.5
+nltk==3.7
nose==1.3.7
protobuf==3.14.0
numpy==1.21.2
pymdown-extensions==8.0
python-slugify==4.0.1
-PyYAML==5.4.1
+PyYAML==6.0
repackage==0.7.3
requests==2.25.1
singledispatch==3.4.0.3
@@ -34,5 +34,6 @@ soupsieve==2.0.1
termcolor==1.1.0
tornado==6.1
Unidecode==1.1.1
-urllib3>=1.26.5
-Pygments>=2.7.4
+urllib3>=1.26.8
+Pygments>=2.11.2
+
diff --git a/programs/client/Client.cpp b/programs/client/Client.cpp
index e51f2aff064..f3e7f1775b8 100644
--- a/programs/client/Client.cpp
+++ b/programs/client/Client.cpp
@@ -787,6 +787,7 @@ void Client::printHelpMessage(const OptionsDescription & options_description)
{
std::cout << options_description.main_description.value() << "\n";
std::cout << options_description.external_description.value() << "\n";
+ std::cout << options_description.hosts_and_ports_description.value() << "\n";
std::cout << "In addition, --param_name=value can be specified for substitution of parameters for parametrized queries.\n";
}
diff --git a/programs/local/LocalServer.cpp b/programs/local/LocalServer.cpp
index 8afb9c663a3..26d42a11315 100644
--- a/programs/local/LocalServer.cpp
+++ b/programs/local/LocalServer.cpp
@@ -304,8 +304,8 @@ void LocalServer::setupUsers()
ConfigurationPtr users_config;
auto & access_control = global_context->getAccessControl();
- access_control.setPlaintextPasswordSetting(config().getBool("allow_plaintext_password", true));
- access_control.setNoPasswordSetting(config().getBool("allow_no_password", true));
+ access_control.setNoPasswordAllowed(config().getBool("allow_no_password", true));
+ access_control.setPlaintextPasswordAllowed(config().getBool("allow_plaintext_password", true));
if (config().has("users_config") || config().has("config-file") || fs::exists("config.xml"))
{
const auto users_config_path = config().getString("users_config", config().getString("config-file", "config.xml"));
diff --git a/programs/server/Server.cpp b/programs/server/Server.cpp
index d372ff8ea65..f278e94c7a2 100644
--- a/programs/server/Server.cpp
+++ b/programs/server/Server.cpp
@@ -1074,9 +1074,10 @@ if (ThreadFuzzer::instance().isEffective())
auto & access_control = global_context->getAccessControl();
if (config().has("custom_settings_prefixes"))
access_control.setCustomSettingsPrefixes(config().getString("custom_settings_prefixes"));
- ///set the allow_plaintext_and_no_password setting in context.
- access_control.setPlaintextPasswordSetting(config().getBool("allow_plaintext_password", true));
- access_control.setNoPasswordSetting(config().getBool("allow_no_password", true));
+
+ access_control.setNoPasswordAllowed(config().getBool("allow_no_password", true));
+ access_control.setPlaintextPasswordAllowed(config().getBool("allow_plaintext_password", true));
+
/// Initialize access storages.
try
{
diff --git a/programs/server/config.xml b/programs/server/config.xml
index d34340ac995..6ca64dc30c5 100644
--- a/programs/server/config.xml
+++ b/programs/server/config.xml
@@ -243,7 +243,7 @@
openssl dhparam -out /etc/clickhouse-server/dhparam.pem 4096
Only file format with BEGIN DH PARAMETERS is supported.
-->
-
+
none
true
true
@@ -368,7 +368,7 @@
/var/lib/clickhouse/tmp/
-
+
`
diff --git a/src/Access/AccessControl.cpp b/src/Access/AccessControl.cpp
index ef8eccb85fa..91ffd7f04ab 100644
--- a/src/Access/AccessControl.cpp
+++ b/src/Access/AccessControl.cpp
@@ -173,7 +173,8 @@ void AccessControl::addUsersConfigStorage(const String & storage_name_, const Po
auto check_setting_name_function = [this](const std::string_view & setting_name) { checkSettingNameIsAllowed(setting_name); };
auto is_no_password_allowed_function = [this]() -> bool { return isNoPasswordAllowed(); };
auto is_plaintext_password_allowed_function = [this]() -> bool { return isPlaintextPasswordAllowed(); };
- auto new_storage = std::make_shared(storage_name_, check_setting_name_function,is_no_password_allowed_function,is_plaintext_password_allowed_function);
+ auto new_storage = std::make_shared(storage_name_, check_setting_name_function,
+ is_no_password_allowed_function, is_plaintext_password_allowed_function);
new_storage->setConfig(users_config_);
addStorage(new_storage);
LOG_DEBUG(getLogger(), "Added {} access storage '{}', path: {}",
@@ -209,7 +210,8 @@ void AccessControl::addUsersConfigStorage(
auto check_setting_name_function = [this](const std::string_view & setting_name) { checkSettingNameIsAllowed(setting_name); };
auto is_no_password_allowed_function = [this]() -> bool { return isNoPasswordAllowed(); };
auto is_plaintext_password_allowed_function = [this]() -> bool { return isPlaintextPasswordAllowed(); };
- auto new_storage = std::make_shared(storage_name_, check_setting_name_function,is_no_password_allowed_function,is_plaintext_password_allowed_function);
+ auto new_storage = std::make_shared(storage_name_, check_setting_name_function,
+ is_no_password_allowed_function, is_plaintext_password_allowed_function);
new_storage->load(users_config_path_, include_from_path_, preprocessed_dir_, get_zookeeper_function_);
addStorage(new_storage);
LOG_DEBUG(getLogger(), "Added {} access storage '{}', path: {}", String(new_storage->getStorageType()), new_storage->getStorageName(), new_storage->getPath());
@@ -411,7 +413,8 @@ UUID AccessControl::authenticate(const Credentials & credentials, const Poco::Ne
{
try
{
- return MultipleAccessStorage::authenticate(credentials, address, *external_authenticators,allow_no_password, allow_plaintext_password);
+ return MultipleAccessStorage::authenticate(credentials, address, *external_authenticators, allow_no_password,
+ allow_plaintext_password);
}
catch (...)
{
@@ -447,26 +450,38 @@ void AccessControl::setCustomSettingsPrefixes(const String & comma_separated_pre
setCustomSettingsPrefixes(prefixes);
}
-void AccessControl::setPlaintextPasswordSetting(bool allow_plaintext_password_)
-{
- allow_plaintext_password = allow_plaintext_password_;
-}
-void AccessControl::setNoPasswordSetting(bool allow_no_password_)
-{
- allow_no_password = allow_no_password_;
-}
-
-bool AccessControl::isSettingNameAllowed(const std::string_view & setting_name) const
+bool AccessControl::isSettingNameAllowed(const std::string_view setting_name) const
{
return custom_settings_prefixes->isSettingNameAllowed(setting_name);
}
-void AccessControl::checkSettingNameIsAllowed(const std::string_view & setting_name) const
+void AccessControl::checkSettingNameIsAllowed(const std::string_view setting_name) const
{
custom_settings_prefixes->checkSettingNameIsAllowed(setting_name);
}
+void AccessControl::setNoPasswordAllowed(bool allow_no_password_)
+{
+ allow_no_password = allow_no_password_;
+}
+
+bool AccessControl::isNoPasswordAllowed() const
+{
+ return allow_no_password;
+}
+
+void AccessControl::setPlaintextPasswordAllowed(bool allow_plaintext_password_)
+{
+ allow_plaintext_password = allow_plaintext_password_;
+}
+
+bool AccessControl::isPlaintextPasswordAllowed() const
+{
+ return allow_plaintext_password;
+}
+
+
std::shared_ptr AccessControl::getContextAccess(
const UUID & user_id,
const std::vector & current_roles,
@@ -550,15 +565,6 @@ std::vector AccessControl::getAllQuotasUsage() const
return quota_cache->getAllQuotasUsage();
}
-bool AccessControl::isPlaintextPasswordAllowed() const
-{
- return allow_plaintext_password;
-}
-
-bool AccessControl::isNoPasswordAllowed() const
-{
- return allow_no_password;
-}
std::shared_ptr AccessControl::getEnabledSettings(
const UUID & user_id,
diff --git a/src/Access/AccessControl.h b/src/Access/AccessControl.h
index 14f4dae9424..0ac3d9cb0c2 100644
--- a/src/Access/AccessControl.h
+++ b/src/Access/AccessControl.h
@@ -49,8 +49,6 @@ class AccessControl : public MultipleAccessStorage
public:
AccessControl();
~AccessControl() override;
- std::atomic_bool allow_plaintext_password;
- std::atomic_bool allow_no_password;
/// Parses access entities from a configuration loaded from users.xml.
/// This function add UsersConfigAccessStorage if it wasn't added before.
@@ -113,12 +111,16 @@ public:
/// This function also enables custom prefixes to be used.
void setCustomSettingsPrefixes(const Strings & prefixes);
void setCustomSettingsPrefixes(const String & comma_separated_prefixes);
- bool isSettingNameAllowed(const std::string_view & name) const;
- void checkSettingNameIsAllowed(const std::string_view & name) const;
+ bool isSettingNameAllowed(const std::string_view name) const;
+ void checkSettingNameIsAllowed(const std::string_view name) const;
- //sets allow_plaintext_password and allow_no_password setting
- void setPlaintextPasswordSetting(const bool allow_plaintext_password_);
- void setNoPasswordSetting(const bool allow_no_password_);
+ /// Allows users without password (by default it's allowed).
+ void setNoPasswordAllowed(const bool allow_no_password_);
+ bool isNoPasswordAllowed() const;
+
+ /// Allows users with plaintext password (by default it's allowed).
+ void setPlaintextPasswordAllowed(const bool allow_plaintext_password_);
+ bool isPlaintextPasswordAllowed() const;
UUID authenticate(const Credentials & credentials, const Poco::Net::IPAddress & address) const;
void setExternalAuthenticatorsConfig(const Poco::Util::AbstractConfiguration & config);
@@ -153,9 +155,6 @@ public:
std::vector getAllQuotasUsage() const;
- bool isPlaintextPasswordAllowed() const;
- bool isNoPasswordAllowed() const;
-
std::shared_ptr getEnabledSettings(
const UUID & user_id,
const SettingsProfileElements & settings_from_user,
@@ -177,6 +176,8 @@ private:
std::unique_ptr settings_profiles_cache;
std::unique_ptr external_authenticators;
std::unique_ptr custom_settings_prefixes;
+ std::atomic_bool allow_plaintext_password = true;
+ std::atomic_bool allow_no_password = true;
};
}
diff --git a/src/Access/AccessEntityIO.cpp b/src/Access/AccessEntityIO.cpp
index acf2a972b13..9d229bbc43b 100644
--- a/src/Access/AccessEntityIO.cpp
+++ b/src/Access/AccessEntityIO.cpp
@@ -120,7 +120,7 @@ AccessEntityPtr deserializeAccessEntityImpl(const String & definition)
if (res)
throw Exception("Two access entities attached in the same file", ErrorCodes::INCORRECT_ACCESS_ENTITY_DEFINITION);
res = user = std::make_unique();
- InterpreterCreateUserQuery::updateUserFromQuery(*user, *create_user_query);
+ InterpreterCreateUserQuery::updateUserFromQuery(*user, *create_user_query, /* allow_no_password = */ true, /* allow_plaintext_password = */ true);
}
else if (auto * create_role_query = query->as())
{
diff --git a/src/Access/IAccessStorage.cpp b/src/Access/IAccessStorage.cpp
index 33bef719eff..8c53216c638 100644
--- a/src/Access/IAccessStorage.cpp
+++ b/src/Access/IAccessStorage.cpp
@@ -441,7 +441,9 @@ void IAccessStorage::notify(const Notifications & notifications)
UUID IAccessStorage::authenticate(
const Credentials & credentials,
const Poco::Net::IPAddress & address,
- const ExternalAuthenticators & external_authenticators, bool allow_no_password, bool allow_plaintext_password) const
+ const ExternalAuthenticators & external_authenticators,
+ bool allow_no_password,
+ bool allow_plaintext_password) const
{
return *authenticateImpl(credentials, address, external_authenticators, /* throw_if_user_not_exists = */ true, allow_no_password, allow_plaintext_password);
}
@@ -451,7 +453,9 @@ std::optional IAccessStorage::authenticate(
const Credentials & credentials,
const Poco::Net::IPAddress & address,
const ExternalAuthenticators & external_authenticators,
- bool throw_if_user_not_exists, bool allow_no_password, bool allow_plaintext_password) const
+ bool throw_if_user_not_exists,
+ bool allow_no_password,
+ bool allow_plaintext_password) const
{
return authenticateImpl(credentials, address, external_authenticators, throw_if_user_not_exists, allow_no_password, allow_plaintext_password);
}
@@ -461,7 +465,9 @@ std::optional IAccessStorage::authenticateImpl(
const Credentials & credentials,
const Poco::Net::IPAddress & address,
const ExternalAuthenticators & external_authenticators,
- bool throw_if_user_not_exists, bool allow_no_password, bool allow_plaintext_password) const
+ bool throw_if_user_not_exists,
+ bool allow_no_password,
+ bool allow_plaintext_password) const
{
if (auto id = find(credentials.getUserName()))
{
@@ -469,8 +475,11 @@ std::optional IAccessStorage::authenticateImpl(
{
if (!isAddressAllowed(*user, address))
throwAddressNotAllowed(address);
- if (isNoPasswordAllowed(*user, allow_no_password) || isPlaintextPasswordAllowed(*user, allow_plaintext_password))
- throwPasswordTypeNotAllowed();
+
+ auto auth_type = user->auth_data.getType();
+ if (((auth_type == AuthenticationType::NO_PASSWORD) && !allow_no_password) ||
+ ((auth_type == AuthenticationType::PLAINTEXT_PASSWORD) && !allow_plaintext_password))
+ throwAuthenticationTypeNotAllowed(auth_type);
if (!areCredentialsValid(*user, credentials, external_authenticators))
throwInvalidCredentials();
@@ -506,15 +515,6 @@ bool IAccessStorage::isAddressAllowed(const User & user, const Poco::Net::IPAddr
return user.allowed_client_hosts.contains(address);
}
-bool IAccessStorage::isPlaintextPasswordAllowed(const User & user, bool allow_plaintext_password)
-{
- return !allow_plaintext_password && user.auth_data.getType() == AuthenticationType::PLAINTEXT_PASSWORD;
-}
-
-bool IAccessStorage::isNoPasswordAllowed(const User & user, bool allow_no_password)
-{
- return !allow_no_password && user.auth_data.getType() == AuthenticationType::NO_PASSWORD;
-}
UUID IAccessStorage::generateRandomID()
{
@@ -610,11 +610,12 @@ void IAccessStorage::throwAddressNotAllowed(const Poco::Net::IPAddress & address
throw Exception("Connections from " + address.toString() + " are not allowed", ErrorCodes::IP_ADDRESS_NOT_ALLOWED);
}
-void IAccessStorage::throwPasswordTypeNotAllowed()
+void IAccessStorage::throwAuthenticationTypeNotAllowed(AuthenticationType auth_type)
{
throw Exception(
- "Authentication denied for users configured with AuthType PLAINTEXT_PASSWORD and NO_PASSWORD. Please check with Clickhouse admin to allow allow PLAINTEXT_PASSWORD and NO_PASSWORD through server configuration ",
- ErrorCodes::AUTHENTICATION_FAILED);
+ ErrorCodes::AUTHENTICATION_FAILED,
+ "Authentication type {} is not allowed, check the setting allow_{} in the server configuration",
+ toString(auth_type), AuthenticationTypeInfo::get(auth_type).name);
}
void IAccessStorage::throwInvalidCredentials()
{
diff --git a/src/Access/IAccessStorage.h b/src/Access/IAccessStorage.h
index 3069e41b285..428a0e8f052 100644
--- a/src/Access/IAccessStorage.h
+++ b/src/Access/IAccessStorage.h
@@ -18,6 +18,7 @@ namespace DB
struct User;
class Credentials;
class ExternalAuthenticators;
+enum class AuthenticationType;
/// Contains entities, i.e. instances of classes derived from IAccessEntity.
/// The implementations of this class MUST be thread-safe.
@@ -148,7 +149,7 @@ public:
/// Finds a user, check the provided credentials and returns the ID of the user if they are valid.
/// Throws an exception if no such user or credentials are invalid.
- UUID authenticate(const Credentials & credentials, const Poco::Net::IPAddress & address, const ExternalAuthenticators & external_authenticators, bool allow_no_password=true, bool allow_plaintext_password=true) const;
+ UUID authenticate(const Credentials & credentials, const Poco::Net::IPAddress & address, const ExternalAuthenticators & external_authenticators, bool allow_no_password, bool allow_plaintext_password) const;
std::optional authenticate(const Credentials & credentials, const Poco::Net::IPAddress & address, const ExternalAuthenticators & external_authenticators, bool throw_if_user_not_exists, bool allow_no_password, bool allow_plaintext_password) const;
protected:
@@ -164,8 +165,6 @@ protected:
virtual std::optional authenticateImpl(const Credentials & credentials, const Poco::Net::IPAddress & address, const ExternalAuthenticators & external_authenticators, bool throw_if_user_not_exists, bool allow_no_password, bool allow_plaintext_password) const;
virtual bool areCredentialsValid(const User & user, const Credentials & credentials, const ExternalAuthenticators & external_authenticators) const;
virtual bool isAddressAllowed(const User & user, const Poco::Net::IPAddress & address) const;
- static bool isPlaintextPasswordAllowed(const User & user, bool allow_plaintext_password) ;
- static bool isNoPasswordAllowed(const User & user, bool allow_no_password);
static UUID generateRandomID();
Poco::Logger * getLogger() const;
static String formatEntityTypeWithName(AccessEntityType type, const String & name) { return AccessEntityTypeInfo::get(type).formatEntityNameWithType(name); }
@@ -181,7 +180,7 @@ protected:
[[noreturn]] void throwReadonlyCannotRemove(AccessEntityType type, const String & name) const;
[[noreturn]] static void throwAddressNotAllowed(const Poco::Net::IPAddress & address);
[[noreturn]] static void throwInvalidCredentials();
- [[noreturn]] static void throwPasswordTypeNotAllowed();
+ [[noreturn]] static void throwAuthenticationTypeNotAllowed(AuthenticationType auth_type);
using Notification = std::tuple;
using Notifications = std::vector;
static void notify(const Notifications & notifications);
diff --git a/src/Access/LDAPAccessStorage.cpp b/src/Access/LDAPAccessStorage.cpp
index dd1c50343f2..4cf42a5017c 100644
--- a/src/Access/LDAPAccessStorage.cpp
+++ b/src/Access/LDAPAccessStorage.cpp
@@ -481,7 +481,9 @@ std::optional LDAPAccessStorage::authenticateImpl(
const Credentials & credentials,
const Poco::Net::IPAddress & address,
const ExternalAuthenticators & external_authenticators,
- bool throw_if_user_not_exists,bool allow_no_password __attribute__((unused)), bool allow_plaintext_password __attribute__((unused))) const
+ bool throw_if_user_not_exists,
+ bool /* allow_no_password */,
+ bool /* allow_plaintext_password */) const
{
std::scoped_lock lock(mutex);
auto id = memory_storage.find(credentials.getUserName());
diff --git a/src/Access/MultipleAccessStorage.cpp b/src/Access/MultipleAccessStorage.cpp
index c988a4d374a..359214eac9f 100644
--- a/src/Access/MultipleAccessStorage.cpp
+++ b/src/Access/MultipleAccessStorage.cpp
@@ -449,14 +449,20 @@ void MultipleAccessStorage::updateSubscriptionsToNestedStorages(std::unique_lock
}
-std::optional MultipleAccessStorage::authenticateImpl(const Credentials & credentials, const Poco::Net::IPAddress & address, const ExternalAuthenticators & external_authenticators, bool throw_if_user_not_exists,bool allow_no_password, bool allow_plaintext_password) const
+std::optional
+MultipleAccessStorage::authenticateImpl(const Credentials & credentials, const Poco::Net::IPAddress & address,
+ const ExternalAuthenticators & external_authenticators,
+ bool throw_if_user_not_exists,
+ bool allow_no_password, bool allow_plaintext_password) const
{
auto storages = getStoragesInternal();
for (size_t i = 0; i != storages->size(); ++i)
{
const auto & storage = (*storages)[i];
bool is_last_storage = (i == storages->size() - 1);
- auto id = storage->authenticate(credentials, address, external_authenticators, (throw_if_user_not_exists && is_last_storage), allow_no_password, allow_plaintext_password);
+ auto id = storage->authenticate(credentials, address, external_authenticators,
+ (throw_if_user_not_exists && is_last_storage),
+ allow_no_password, allow_plaintext_password);
if (id)
{
std::lock_guard lock{mutex};
diff --git a/src/Access/UsersConfigAccessStorage.cpp b/src/Access/UsersConfigAccessStorage.cpp
index b2bdebfcf6c..fe8e6d1d6c0 100644
--- a/src/Access/UsersConfigAccessStorage.cpp
+++ b/src/Access/UsersConfigAccessStorage.cpp
@@ -28,8 +28,6 @@ namespace ErrorCodes
extern const int BAD_ARGUMENTS;
extern const int UNKNOWN_ADDRESS_PATTERN_TYPE;
extern const int NOT_IMPLEMENTED;
- extern const int ILLEGAL_TYPE_OF_ARGUMENT;
-
}
namespace
@@ -50,7 +48,7 @@ namespace
UUID generateID(const IAccessEntity & entity) { return generateID(entity.getType(), entity.getName()); }
- UserPtr parseUser(const Poco::Util::AbstractConfiguration & config, const String & user_name)
+ UserPtr parseUser(const Poco::Util::AbstractConfiguration & config, const String & user_name, bool allow_no_password, bool allow_plaintext_password)
{
auto user = std::make_shared();
user->setName(user_name);
@@ -130,6 +128,15 @@ namespace
user->auth_data.setSSLCertificateCommonNames(std::move(common_names));
}
+ auto auth_type = user->auth_data.getType();
+ if (((auth_type == AuthenticationType::NO_PASSWORD) && !allow_no_password) ||
+ ((auth_type == AuthenticationType::PLAINTEXT_PASSWORD) && !allow_plaintext_password))
+ {
+ throw Exception(ErrorCodes::BAD_ARGUMENTS,
+ "Authentication type {} is not allowed, check the setting allow_{} in the server configuration",
+ toString(auth_type), AuthenticationTypeInfo::get(auth_type).name);
+ }
+
const auto profile_name_config = user_config + ".profile";
if (config.has(profile_name_config))
{
@@ -225,24 +232,18 @@ namespace
}
- std::vector parseUsers(const Poco::Util::AbstractConfiguration & config, Fn auto && is_no_password_allowed_function, Fn auto && is_plaintext_password_allowed_function)
+ std::vector parseUsers(const Poco::Util::AbstractConfiguration & config, bool allow_no_password, bool allow_plaintext_password)
{
Poco::Util::AbstractConfiguration::Keys user_names;
config.keys("users", user_names);
std::vector users;
users.reserve(user_names.size());
- bool allow_plaintext_password = is_plaintext_password_allowed_function();
- bool allow_no_password = is_no_password_allowed_function();
for (const auto & user_name : user_names)
{
try
{
- String user_config = "users." + user_name;
- if ((config.has(user_config + ".password") && !allow_plaintext_password) || (config.has(user_config + ".no_password") && !allow_no_password))
- throw Exception("Incorrect User configuration. User is not allowed to configure PLAINTEXT_PASSWORD or NO_PASSWORD. Please configure User with authtype SHA256_PASSWORD_HASH, SHA256_PASSWORD, DOUBLE_SHA1_PASSWORD OR enable setting allow_plaintext_and_no_password in server configuration to configure user with plaintext and no password Auth_Type"
- " Though it is not recommended to use plaintext_password and No_password for user authentication.", ErrorCodes::ILLEGAL_TYPE_OF_ARGUMENT);
- users.push_back(parseUser(config, user_name));
+ users.push_back(parseUser(config, user_name, allow_no_password, allow_plaintext_password));
}
catch (Exception & e)
{
@@ -562,8 +563,10 @@ void UsersConfigAccessStorage::parseFromConfig(const Poco::Util::AbstractConfigu
{
try
{
+ bool no_password_allowed = is_no_password_allowed_function();
+ bool plaintext_password_allowed = is_plaintext_password_allowed_function();
std::vector> all_entities;
- for (const auto & entity : parseUsers(config,is_no_password_allowed_function, is_plaintext_password_allowed_function))
+ for (const auto & entity : parseUsers(config, no_password_allowed, plaintext_password_allowed))
all_entities.emplace_back(generateID(*entity), entity);
for (const auto & entity : parseQuotas(config))
all_entities.emplace_back(generateID(*entity), entity);
diff --git a/src/AggregateFunctions/AggregateFunctionFactory.h b/src/AggregateFunctions/AggregateFunctionFactory.h
index ef5740733df..e5263a54d79 100644
--- a/src/AggregateFunctions/AggregateFunctionFactory.h
+++ b/src/AggregateFunctions/AggregateFunctionFactory.h
@@ -38,7 +38,8 @@ struct AggregateFunctionWithProperties
AggregateFunctionWithProperties(const AggregateFunctionWithProperties &) = default;
AggregateFunctionWithProperties & operator = (const AggregateFunctionWithProperties &) = default;
- template > * = nullptr>
+ template
+ requires (!std::is_same_v)
AggregateFunctionWithProperties(Creator creator_, AggregateFunctionProperties properties_ = {}) /// NOLINT
: creator(std::forward(creator_)), properties(std::move(properties_))
{
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index 22fe1f2ffff..b24181625d3 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -569,6 +569,14 @@ if (ENABLE_TESTS)
clickhouse_common_zookeeper
string_utils)
+ if (TARGET ch_contrib::simdjson)
+ target_link_libraries(unit_tests_dbms PRIVATE ch_contrib::simdjson)
+ endif()
+
+ if(TARGET ch_contrib::rapidjson)
+ target_include_directories(unit_tests_dbms PRIVATE ch_contrib::rapidjson)
+ endif()
+
if (TARGET ch_contrib::yaml_cpp)
target_link_libraries(unit_tests_dbms PRIVATE ch_contrib::yaml_cpp)
endif()
diff --git a/src/Client/ClientBase.cpp b/src/Client/ClientBase.cpp
index c575cd37a5f..4f1c1f4539e 100644
--- a/src/Client/ClientBase.cpp
+++ b/src/Client/ClientBase.cpp
@@ -1092,10 +1092,11 @@ void ClientBase::sendData(Block & sample, const ColumnsDescription & columns_des
try
{
+ auto metadata = storage->getInMemoryMetadataPtr();
sendDataFromPipe(
storage->read(
sample.getNames(),
- storage->getInMemoryMetadataPtr(),
+ storage->getStorageSnapshot(metadata),
query_info,
global_context,
{},
diff --git a/src/Columns/ColumnAggregateFunction.cpp b/src/Columns/ColumnAggregateFunction.cpp
index f27e103b304..59d56c6e437 100644
--- a/src/Columns/ColumnAggregateFunction.cpp
+++ b/src/Columns/ColumnAggregateFunction.cpp
@@ -297,7 +297,7 @@ ColumnPtr ColumnAggregateFunction::filter(const Filter & filter, ssize_t result_
{
size_t size = data.size();
if (size != filter.size())
- throw Exception("Size of filter doesn't match size of column.", ErrorCodes::SIZES_OF_COLUMNS_DOESNT_MATCH);
+ throw Exception(ErrorCodes::SIZES_OF_COLUMNS_DOESNT_MATCH, "Size of filter ({}) doesn't match size of column ({})", filter.size(), size);
if (size == 0)
return cloneEmpty();
diff --git a/src/Columns/ColumnArray.cpp b/src/Columns/ColumnArray.cpp
index be5d9065281..24da9644335 100644
--- a/src/Columns/ColumnArray.cpp
+++ b/src/Columns/ColumnArray.cpp
@@ -608,7 +608,7 @@ ColumnPtr ColumnArray::filterString(const Filter & filt, ssize_t result_size_hin
{
size_t col_size = getOffsets().size();
if (col_size != filt.size())
- throw Exception("Size of filter doesn't match size of column.", ErrorCodes::SIZES_OF_COLUMNS_DOESNT_MATCH);
+ throw Exception(ErrorCodes::SIZES_OF_COLUMNS_DOESNT_MATCH, "Size of filter ({}) doesn't match size of column ({})", filt.size(), col_size);
if (0 == col_size)
return ColumnArray::create(data);
@@ -676,7 +676,7 @@ ColumnPtr ColumnArray::filterGeneric(const Filter & filt, ssize_t result_size_hi
{
size_t size = getOffsets().size();
if (size != filt.size())
- throw Exception("Size of filter doesn't match size of column.", ErrorCodes::SIZES_OF_COLUMNS_DOESNT_MATCH);
+ throw Exception(ErrorCodes::SIZES_OF_COLUMNS_DOESNT_MATCH, "Size of filter ({}) doesn't match size of column ({})", filt.size(), size);
if (size == 0)
return ColumnArray::create(data);
@@ -1189,4 +1189,12 @@ void ColumnArray::gather(ColumnGathererStream & gatherer)
gatherer.gather(*this);
}
+size_t ColumnArray::getNumberOfDimensions() const
+{
+ const auto * nested_array = checkAndGetColumn(*data);
+ if (!nested_array)
+ return 1;
+ return 1 + nested_array->getNumberOfDimensions(); /// Every modern C++ compiler optimizes tail recursion.
+}
+
}
diff --git a/src/Columns/ColumnArray.h b/src/Columns/ColumnArray.h
index cc80d1300ce..3f41ae9cd8a 100644
--- a/src/Columns/ColumnArray.h
+++ b/src/Columns/ColumnArray.h
@@ -169,6 +169,8 @@ public:
bool isCollationSupported() const override { return getData().isCollationSupported(); }
+ size_t getNumberOfDimensions() const;
+
private:
WrappedPtr data;
WrappedPtr offsets;
diff --git a/src/Columns/ColumnDecimal.cpp b/src/Columns/ColumnDecimal.cpp
index f9feb8f10b9..4290a7a4cb1 100644
--- a/src/Columns/ColumnDecimal.cpp
+++ b/src/Columns/ColumnDecimal.cpp
@@ -266,7 +266,7 @@ ColumnPtr ColumnDecimal::filter(const IColumn::Filter & filt, ssize_t result_
{
size_t size = data.size();
if (size != filt.size())
- throw Exception("Size of filter doesn't match size of column.", ErrorCodes::SIZES_OF_COLUMNS_DOESNT_MATCH);
+ throw Exception(ErrorCodes::SIZES_OF_COLUMNS_DOESNT_MATCH, "Size of filter ({}) doesn't match size of column ({})", filt.size(), size);
auto res = this->create(0, scale);
Container & res_data = res->getData();
diff --git a/src/Columns/ColumnFixedString.cpp b/src/Columns/ColumnFixedString.cpp
index d0a735a5580..de6324ca7ce 100644
--- a/src/Columns/ColumnFixedString.cpp
+++ b/src/Columns/ColumnFixedString.cpp
@@ -207,7 +207,7 @@ ColumnPtr ColumnFixedString::filter(const IColumn::Filter & filt, ssize_t result
{
size_t col_size = size();
if (col_size != filt.size())
- throw Exception("Size of filter doesn't match size of column.", ErrorCodes::SIZES_OF_COLUMNS_DOESNT_MATCH);
+ throw Exception(ErrorCodes::SIZES_OF_COLUMNS_DOESNT_MATCH, "Size of filter ({}) doesn't match size of column ({})", filt.size(), col_size);
auto res = ColumnFixedString::create(n);
diff --git a/src/Columns/ColumnNullable.h b/src/Columns/ColumnNullable.h
index 41ad099818e..52e5e43fa48 100644
--- a/src/Columns/ColumnNullable.h
+++ b/src/Columns/ColumnNullable.h
@@ -144,15 +144,15 @@ public:
double getRatioOfDefaultRows(double sample_ratio) const override
{
- return null_map->getRatioOfDefaultRows(sample_ratio);
+ return getRatioOfDefaultRowsImpl(sample_ratio);
}
void getIndicesOfNonDefaultRows(Offsets & indices, size_t from, size_t limit) const override
{
- null_map->getIndicesOfNonDefaultRows(indices, from, limit);
+ getIndicesOfNonDefaultRowsImpl(indices, from, limit);
}
- ColumnPtr createWithOffsets(const IColumn::Offsets & offsets, const Field & default_field, size_t total_rows, size_t shift) const override;
+ ColumnPtr createWithOffsets(const Offsets & offsets, const Field & default_field, size_t total_rows, size_t shift) const override;
bool isNullable() const override { return true; }
bool isFixedAndContiguous() const override { return false; }
diff --git a/src/Columns/ColumnObject.cpp b/src/Columns/ColumnObject.cpp
new file mode 100644
index 00000000000..bfa8ffe6358
--- /dev/null
+++ b/src/Columns/ColumnObject.cpp
@@ -0,0 +1,780 @@
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+namespace DB
+{
+
+namespace ErrorCodes
+{
+ extern const int LOGICAL_ERROR;
+ extern const int ILLEGAL_COLUMN;
+ extern const int DUPLICATE_COLUMN;
+ extern const int NUMBER_OF_DIMENSIONS_MISMATHED;
+ extern const int NOT_IMPLEMENTED;
+ extern const int SIZES_OF_COLUMNS_DOESNT_MATCH;
+}
+
+namespace
+{
+
+/// Recreates column with default scalar values and keeps sizes of arrays.
+ColumnPtr recreateColumnWithDefaultValues(
+ const ColumnPtr & column, const DataTypePtr & scalar_type, size_t num_dimensions)
+{
+ const auto * column_array = checkAndGetColumn(column.get());
+ if (column_array && num_dimensions)
+ {
+ return ColumnArray::create(
+ recreateColumnWithDefaultValues(
+ column_array->getDataPtr(), scalar_type, num_dimensions - 1),
+ IColumn::mutate(column_array->getOffsetsPtr()));
+ }
+
+ return createArrayOfType(scalar_type, num_dimensions)->createColumn()->cloneResized(column->size());
+}
+
+/// Replaces NULL fields to given field or empty array.
+class FieldVisitorReplaceNull : public StaticVisitor
+{
+public:
+ explicit FieldVisitorReplaceNull(
+ const Field & replacement_, size_t num_dimensions_)
+ : replacement(replacement_)
+ , num_dimensions(num_dimensions_)
+ {
+ }
+
+ Field operator()(const Null &) const
+ {
+ return num_dimensions
+ ? createEmptyArrayField(num_dimensions)
+ : replacement;
+ }
+
+ Field operator()(const Array & x) const
+ {
+ assert(num_dimensions > 0);
+ const size_t size = x.size();
+ Array res(size);
+ for (size_t i = 0; i < size; ++i)
+ res[i] = applyVisitor(FieldVisitorReplaceNull(replacement, num_dimensions - 1), x[i]);
+ return res;
+ }
+
+ template
+ Field operator()(const T & x) const { return x; }
+
+private:
+ const Field & replacement;
+ size_t num_dimensions;
+};
+
+/// Calculates number of dimensions in array field.
+/// Returns 0 for scalar fields.
+class FieldVisitorToNumberOfDimensions : public StaticVisitor
+{
+public:
+ size_t operator()(const Array & x) const
+ {
+ const size_t size = x.size();
+ std::optional dimensions;
+
+ for (size_t i = 0; i < size; ++i)
+ {
+ /// Do not count Nulls, because they will be replaced by default
+ /// values with proper number of dimensions.
+ if (x[i].isNull())
+ continue;
+
+ size_t current_dimensions = applyVisitor(*this, x[i]);
+ if (!dimensions)
+ dimensions = current_dimensions;
+ else if (current_dimensions != *dimensions)
+ throw Exception(ErrorCodes::NUMBER_OF_DIMENSIONS_MISMATHED,
+ "Number of dimensions mismatched among array elements");
+ }
+
+ return 1 + dimensions.value_or(0);
+ }
+
+ template
+ size_t operator()(const T &) const { return 0; }
+};
+
+/// Visitor that allows to get type of scalar field
+/// or least common type of scalars in array.
+/// More optimized version of FieldToDataType.
+class FieldVisitorToScalarType : public StaticVisitor<>
+{
+public:
+ using FieldType = Field::Types::Which;
+
+ void operator()(const Array & x)
+ {
+ size_t size = x.size();
+ for (size_t i = 0; i < size; ++i)
+ applyVisitor(*this, x[i]);
+ }
+
+ void operator()(const UInt64 & x)
+ {
+ field_types.insert(FieldType::UInt64);
+ if (x <= std::numeric_limits::max())
+ type_indexes.insert(TypeIndex::UInt8);
+ else if (x <= std::numeric_limits::max())
+ type_indexes.insert(TypeIndex::UInt16);
+ else if (x <= std::numeric_limits::max())
+ type_indexes.insert(TypeIndex::UInt32);
+ else
+ type_indexes.insert(TypeIndex::UInt64);
+ }
+
+ void operator()(const Int64 & x)
+ {
+ field_types.insert(FieldType::Int64);
+ if (x <= std::numeric_limits::max() && x >= std::numeric_limits::min())
+ type_indexes.insert(TypeIndex::Int8);
+ else if (x <= std::numeric_limits::max() && x >= std::numeric_limits::min())
+ type_indexes.insert(TypeIndex::Int16);
+ else if (x <= std::numeric_limits::max() && x >= std::numeric_limits::min())
+ type_indexes.insert(TypeIndex::Int32);
+ else
+ type_indexes.insert(TypeIndex::Int64);
+ }
+
+ void operator()(const Null &)
+ {
+ have_nulls = true;
+ }
+
+ template
+ void operator()(const T &)
+ {
+ field_types.insert(Field::TypeToEnum>::value);
+ type_indexes.insert(TypeToTypeIndex>);
+ }
+
+ DataTypePtr getScalarType() const { return getLeastSupertype(type_indexes, true); }
+ bool haveNulls() const { return have_nulls; }
+ bool needConvertField() const { return field_types.size() > 1; }
+
+private:
+ TypeIndexSet type_indexes;
+ std::unordered_set field_types;
+ bool have_nulls = false;
+};
+
+}
+
+FieldInfo getFieldInfo(const Field & field)
+{
+ FieldVisitorToScalarType to_scalar_type_visitor;
+ applyVisitor(to_scalar_type_visitor, field);
+
+ return
+ {
+ to_scalar_type_visitor.getScalarType(),
+ to_scalar_type_visitor.haveNulls(),
+ to_scalar_type_visitor.needConvertField(),
+ applyVisitor(FieldVisitorToNumberOfDimensions(), field),
+ };
+}
+
+ColumnObject::Subcolumn::Subcolumn(MutableColumnPtr && data_, bool is_nullable_)
+ : least_common_type(getDataTypeByColumn(*data_))
+ , is_nullable(is_nullable_)
+{
+ data.push_back(std::move(data_));
+}
+
+ColumnObject::Subcolumn::Subcolumn(
+ size_t size_, bool is_nullable_)
+ : least_common_type(std::make_shared())
+ , is_nullable(is_nullable_)
+ , num_of_defaults_in_prefix(size_)
+{
+}
+
+size_t ColumnObject::Subcolumn::Subcolumn::size() const
+{
+ size_t res = num_of_defaults_in_prefix;
+ for (const auto & part : data)
+ res += part->size();
+ return res;
+}
+
+size_t ColumnObject::Subcolumn::Subcolumn::byteSize() const
+{
+ size_t res = 0;
+ for (const auto & part : data)
+ res += part->byteSize();
+ return res;
+}
+
+size_t ColumnObject::Subcolumn::Subcolumn::allocatedBytes() const
+{
+ size_t res = 0;
+ for (const auto & part : data)
+ res += part->allocatedBytes();
+ return res;
+}
+
+void ColumnObject::Subcolumn::checkTypes() const
+{
+ DataTypes prefix_types;
+ prefix_types.reserve(data.size());
+ for (size_t i = 0; i < data.size(); ++i)
+ {
+ auto current_type = getDataTypeByColumn(*data[i]);
+ prefix_types.push_back(current_type);
+ auto prefix_common_type = getLeastSupertype(prefix_types);
+ if (!prefix_common_type->equals(*current_type))
+ throw Exception(ErrorCodes::LOGICAL_ERROR,
+ "Data type {} of column at position {} cannot represent all columns from i-th prefix",
+ current_type->getName(), i);
+ }
+}
+
+void ColumnObject::Subcolumn::insert(Field field)
+{
+ auto info = getFieldInfo(field);
+ insert(std::move(field), std::move(info));
+}
+
+void ColumnObject::Subcolumn::insert(Field field, FieldInfo info)
+{
+ auto base_type = info.scalar_type;
+
+ if (isNothing(base_type) && info.num_dimensions == 0)
+ {
+ insertDefault();
+ return;
+ }
+
+ auto column_dim = getNumberOfDimensions(*least_common_type);
+ auto value_dim = info.num_dimensions;
+
+ if (isNothing(least_common_type))
+ column_dim = value_dim;
+
+ if (field.isNull())
+ value_dim = column_dim;
+
+ if (value_dim != column_dim)
+ throw Exception(ErrorCodes::NUMBER_OF_DIMENSIONS_MISMATHED,
+ "Dimension of types mismatched between inserted value and column. "
+ "Dimension of value: {}. Dimension of column: {}",
+ value_dim, column_dim);
+
+ if (is_nullable)
+ base_type = makeNullable(base_type);
+
+ if (!is_nullable && info.have_nulls)
+ field = applyVisitor(FieldVisitorReplaceNull(base_type->getDefault(), value_dim), std::move(field));
+
+ auto value_type = createArrayOfType(base_type, value_dim);
+ bool type_changed = false;
+
+ if (data.empty())
+ {
+ data.push_back(value_type->createColumn());
+ least_common_type = value_type;
+ }
+ else if (!least_common_type->equals(*value_type))
+ {
+ value_type = getLeastSupertype(DataTypes{value_type, least_common_type}, true);
+ type_changed = true;
+ if (!least_common_type->equals(*value_type))
+ {
+ data.push_back(value_type->createColumn());
+ least_common_type = value_type;
+ }
+ }
+
+ if (type_changed || info.need_convert)
+ field = convertFieldToTypeOrThrow(field, *value_type);
+
+ data.back()->insert(field);
+}
+
+void ColumnObject::Subcolumn::insertRangeFrom(const Subcolumn & src, size_t start, size_t length)
+{
+ assert(src.isFinalized());
+
+ const auto & src_column = src.data.back();
+ const auto & src_type = src.least_common_type;
+
+ if (data.empty())
+ {
+ least_common_type = src_type;
+ data.push_back(src_type->createColumn());
+ data.back()->insertRangeFrom(*src_column, start, length);
+ }
+ else if (least_common_type->equals(*src_type))
+ {
+ data.back()->insertRangeFrom(*src_column, start, length);
+ }
+ else
+ {
+ auto new_least_common_type = getLeastSupertype(DataTypes{least_common_type, src_type}, true);
+ auto casted_column = castColumn({src_column, src_type, ""}, new_least_common_type);
+
+ if (!least_common_type->equals(*new_least_common_type))
+ {
+ least_common_type = new_least_common_type;
+ data.push_back(least_common_type->createColumn());
+ }
+
+ data.back()->insertRangeFrom(*casted_column, start, length);
+ }
+}
+
+void ColumnObject::Subcolumn::finalize()
+{
+ if (isFinalized() || data.empty())
+ return;
+
+ const auto & to_type = least_common_type;
+ auto result_column = to_type->createColumn();
+
+ if (num_of_defaults_in_prefix)
+ result_column->insertManyDefaults(num_of_defaults_in_prefix);
+
+ for (auto & part : data)
+ {
+ auto from_type = getDataTypeByColumn(*part);
+ size_t part_size = part->size();
+
+ if (!from_type->equals(*to_type))
+ {
+ auto offsets = ColumnUInt64::create();
+ auto & offsets_data = offsets->getData();
+
+ /// We need to convert only non-default values and then recreate column
+ /// with default value of new type, because default values (which represents misses in data)
+ /// may be inconsistent between types (e.g "0" in UInt64 and empty string in String).
+
+ part->getIndicesOfNonDefaultRows(offsets_data, 0, part_size);
+
+ if (offsets->size() == part_size)
+ {
+ part = castColumn({part, from_type, ""}, to_type);
+ }
+ else
+ {
+ auto values = part->index(*offsets, offsets->size());
+ values = castColumn({values, from_type, ""}, to_type);
+ part = values->createWithOffsets(offsets_data, to_type->getDefault(), part_size, /*shift=*/ 0);
+ }
+ }
+
+ result_column->insertRangeFrom(*part, 0, part_size);
+ }
+
+ data = { std::move(result_column) };
+ num_of_defaults_in_prefix = 0;
+}
+
+void ColumnObject::Subcolumn::insertDefault()
+{
+ if (data.empty())
+ ++num_of_defaults_in_prefix;
+ else
+ data.back()->insertDefault();
+}
+
+void ColumnObject::Subcolumn::insertManyDefaults(size_t length)
+{
+ if (data.empty())
+ num_of_defaults_in_prefix += length;
+ else
+ data.back()->insertManyDefaults(length);
+}
+
+void ColumnObject::Subcolumn::popBack(size_t n)
+{
+ assert(n <= size());
+
+ size_t num_removed = 0;
+ for (auto it = data.rbegin(); it != data.rend(); ++it)
+ {
+ if (n == 0)
+ break;
+
+ auto & column = *it;
+ if (n < column->size())
+ {
+ column->popBack(n);
+ n = 0;
+ }
+ else
+ {
+ ++num_removed;
+ n -= column->size();
+ }
+ }
+
+ data.resize(data.size() - num_removed);
+ num_of_defaults_in_prefix -= n;
+}
+
+Field ColumnObject::Subcolumn::getLastField() const
+{
+ if (data.empty())
+ return Field();
+
+ const auto & last_part = data.back();
+ assert(!last_part->empty());
+ return (*last_part)[last_part->size() - 1];
+}
+
+ColumnObject::Subcolumn ColumnObject::Subcolumn::recreateWithDefaultValues(const FieldInfo & field_info) const
+{
+ auto scalar_type = field_info.scalar_type;
+ if (is_nullable)
+ scalar_type = makeNullable(scalar_type);
+
+ Subcolumn new_subcolumn;
+ new_subcolumn.least_common_type = createArrayOfType(scalar_type, field_info.num_dimensions);
+ new_subcolumn.is_nullable = is_nullable;
+ new_subcolumn.num_of_defaults_in_prefix = num_of_defaults_in_prefix;
+ new_subcolumn.data.reserve(data.size());
+
+ for (const auto & part : data)
+ new_subcolumn.data.push_back(recreateColumnWithDefaultValues(
+ part, scalar_type, field_info.num_dimensions));
+
+ return new_subcolumn;
+}
+
+IColumn & ColumnObject::Subcolumn::getFinalizedColumn()
+{
+ assert(isFinalized());
+ return *data[0];
+}
+
+const IColumn & ColumnObject::Subcolumn::getFinalizedColumn() const
+{
+ assert(isFinalized());
+ return *data[0];
+}
+
+const ColumnPtr & ColumnObject::Subcolumn::getFinalizedColumnPtr() const
+{
+ assert(isFinalized());
+ return data[0];
+}
+
+ColumnObject::ColumnObject(bool is_nullable_)
+ : is_nullable(is_nullable_)
+ , num_rows(0)
+{
+}
+
+ColumnObject::ColumnObject(SubcolumnsTree && subcolumns_, bool is_nullable_)
+ : is_nullable(is_nullable_)
+ , subcolumns(std::move(subcolumns_))
+ , num_rows(subcolumns.empty() ? 0 : (*subcolumns.begin())->data.size())
+
+{
+ checkConsistency();
+}
+
+void ColumnObject::checkConsistency() const
+{
+ if (subcolumns.empty())
+ return;
+
+ for (const auto & leaf : subcolumns)
+ {
+ if (num_rows != leaf->data.size())
+ {
+ throw Exception(ErrorCodes::LOGICAL_ERROR, "Sizes of subcolumns are inconsistent in ColumnObject."
+ " Subcolumn '{}' has {} rows, but expected size is {}",
+ leaf->path.getPath(), leaf->data.size(), num_rows);
+ }
+ }
+}
+
+size_t ColumnObject::size() const
+{
+#ifndef NDEBUG
+ checkConsistency();
+#endif
+ return num_rows;
+}
+
+MutableColumnPtr ColumnObject::cloneResized(size_t new_size) const
+{
+ /// cloneResized with new_size == 0 is used for cloneEmpty().
+ if (new_size != 0)
+ throw Exception(ErrorCodes::NOT_IMPLEMENTED,
+ "ColumnObject doesn't support resize to non-zero length");
+
+ return ColumnObject::create(is_nullable);
+}
+
+size_t ColumnObject::byteSize() const
+{
+ size_t res = 0;
+ for (const auto & entry : subcolumns)
+ res += entry->data.byteSize();
+ return res;
+}
+
+size_t ColumnObject::allocatedBytes() const
+{
+ size_t res = 0;
+ for (const auto & entry : subcolumns)
+ res += entry->data.allocatedBytes();
+ return res;
+}
+
+void ColumnObject::forEachSubcolumn(ColumnCallback callback)
+{
+ if (!isFinalized())
+ throw Exception(ErrorCodes::LOGICAL_ERROR, "Cannot iterate over non-finalized ColumnObject");
+
+ for (auto & entry : subcolumns)
+ callback(entry->data.data.back());
+}
+
+void ColumnObject::insert(const Field & field)
+{
+ const auto & object = field.get();
+
+ HashSet inserted;
+ size_t old_size = size();
+ for (const auto & [key_str, value] : object)
+ {
+ PathInData key(key_str);
+ inserted.insert(key_str);
+ if (!hasSubcolumn(key))
+ addSubcolumn(key, old_size);
+
+ auto & subcolumn = getSubcolumn(key);
+ subcolumn.insert(value);
+ }
+
+ for (auto & entry : subcolumns)
+ if (!inserted.has(entry->path.getPath()))
+ entry->data.insertDefault();
+
+ ++num_rows;
+}
+
+void ColumnObject::insertDefault()
+{
+ for (auto & entry : subcolumns)
+ entry->data.insertDefault();
+
+ ++num_rows;
+}
+
+Field ColumnObject::operator[](size_t n) const
+{
+ if (!isFinalized())
+ throw Exception(ErrorCodes::LOGICAL_ERROR, "Cannot get Field from non-finalized ColumnObject");
+
+ Object object;
+ for (const auto & entry : subcolumns)
+ object[entry->path.getPath()] = (*entry->data.data.back())[n];
+
+ return object;
+}
+
+void ColumnObject::get(size_t n, Field & res) const
+{
+ if (!isFinalized())
+ throw Exception(ErrorCodes::LOGICAL_ERROR, "Cannot get Field from non-finalized ColumnObject");
+
+ auto & object = res.get