NamedCollections: prevent fields overriding in functions call

Add syntax in SQL and XML to mark specific fields to allow
override or not.
Also add a new setting to control the default behaviour when
overriding support is not specified.
This commit is contained in:
Salvatore Mesoraca 2023-10-17 15:32:34 +02:00
parent bc25839be8
commit 7d206cbc3c
No known key found for this signature in database
GPG Key ID: 0567E50A25403074
19 changed files with 295 additions and 36 deletions

View File

@ -18,7 +18,15 @@ function, table engine, database, etc. In the examples below the parameter list
linked to for each type.
Parameters set in a named collection can be overridden in SQL, this is shown in the examples
below.
below. This ability can be limited using `[NOT] OVERRIDABLE` keywords and XML attributes
and/or the configuration option `allow_named_collection_override_by_default`.
:::warning
If override is allowed, it may be possible for users without administrative access to
figure out the credentials that you are trying to hide.
If you are using named collections with that purpose, you should disable
`allow_named_collection_override_by_default` (which is enabled by default).
:::
## Storing named collections in the system database
@ -26,11 +34,17 @@ below.
```sql
CREATE NAMED COLLECTION name AS
key_1 = 'value',
key_2 = 'value2',
key_1 = 'value' OVERRIDABLE,
key_2 = 'value2' NOT OVERRIDABLE,
url = 'https://connection.url/'
```
In the above example:
* `key_1` can always be overridden.
* `key_2` can never be overridden.
* `url` can be overridden or not depending on the value of `allow_named_collection_override_by_default`.
### Permissions to create named collections with DDL
To manage named collections with DDL a user must have the `named_control_collection` privilege. This can be assigned by adding a file to `/etc/clickhouse-server/users.d/`. The example gives the user `default` both the `access_management` and `named_collection_control` privileges:
@ -61,25 +75,37 @@ In the above example the `password_sha256_hex` value is the hexadecimal represen
<clickhouse>
<named_collections>
<name>
<key_1>value</key_1>
<key_2>value_2</key_2>
<key_1 overridable="true">value</key_1>
<key_2 overridable="false">value_2</key_2>
<url>https://connection.url/</url>
</name>
</named_collections>
</clickhouse>
```
In the above example:
* `key_1` can always be overridden.
* `key_2` can never be overridden.
* `url` can be overridden or not depending on the value of `allow_named_collection_override_by_default`.
## Modifying named collections
Named collections that are created with DDL queries can be altered or dropped with DDL. Named collections created with XML files can be managed by editing or deleting the corresponding XML.
### Alter a DDL named collection
Change or add the keys `key1` and `key3` of the collection `collection2`:
Change or add the keys `key1` and `key3` of the collection `collection2`
(this will not change the value of the `overridable` flag for those keys):
```sql
ALTER NAMED COLLECTION collection2 SET key1=4, key3='value3'
```
Change or add the key `key1` and allow it to be always overridden:
```sql
ALTER NAMED COLLECTION collection2 SET key1=4 OVERRIDABLE
```
Remove the key `key2` from `collection2`:
```sql
ALTER NAMED COLLECTION collection2 DELETE key2
@ -90,6 +116,13 @@ Change or add the key `key1` and delete the key `key3` of the collection `collec
ALTER NAMED COLLECTION collection2 SET key1=4, DELETE key3
```
To force a key to use the default settings for the `overridable` flag, you have to
remove and re-add the key.
```sql
ALTER NAMED COLLECTION collection2 DELETE key1;
ALTER NAMED COLLECTION collection2 SET key1=4;
```
### Drop the DDL named collection `collection2`:
```sql
DROP NAMED COLLECTION collection2

View File

@ -12,9 +12,9 @@ This query intends to modify already existing named collections.
```sql
ALTER NAMED COLLECTION [IF EXISTS] name [ON CLUSTER cluster]
[ SET
key_name1 = 'some value',
key_name2 = 'some value',
key_name3 = 'some value',
key_name1 = 'some value' [[NOT] OVERRIDABLE],
key_name2 = 'some value' [[NOT] OVERRIDABLE],
key_name3 = 'some value' [[NOT] OVERRIDABLE],
... ] |
[ DELETE key_name4, key_name5, ... ]
```
@ -22,9 +22,9 @@ key_name3 = 'some value',
**Example**
```sql
CREATE NAMED COLLECTION foobar AS a = '1', b = '2';
CREATE NAMED COLLECTION foobar AS a = '1' NOT OVERRIDABLE, b = '2';
ALTER NAMED COLLECTION foobar SET a = '2', c = '3';
ALTER NAMED COLLECTION foobar SET a = '2' OVERRIDABLE, c = '3';
ALTER NAMED COLLECTION foobar DELETE b;
```

View File

@ -11,16 +11,16 @@ Creates a new named collection.
```sql
CREATE NAMED COLLECTION [IF NOT EXISTS] name [ON CLUSTER cluster] AS
key_name1 = 'some value',
key_name2 = 'some value',
key_name3 = 'some value',
key_name1 = 'some value' [[NOT] OVERRIDABLE],
key_name2 = 'some value' [[NOT] OVERRIDABLE],
key_name3 = 'some value' [[NOT] OVERRIDABLE],
...
```
**Example**
```sql
CREATE NAMED COLLECTION foobar AS a = '1', b = '2';
CREATE NAMED COLLECTION foobar AS a = '1', b = '2' OVERRIDABLE;
```
**Related statements**

View File

@ -123,6 +123,9 @@ template <typename T> void copyConfigValue(
ErrorCodes::NOT_IMPLEMENTED,
"Unsupported type in copyConfigValue(). "
"Supported types are String, UInt64, Int64, Float64");
const auto overridable = getOverridable(from_config, from_path);
if (overridable)
setOverridable(to_config, to_path, *overridable);
}
void removeConfigValue(
@ -147,13 +150,16 @@ ConfigurationPtr createEmptyConfiguration(const std::string & root_name)
return config;
}
ConfigurationPtr createConfiguration(const std::string & root_name, const SettingsChanges & settings)
ConfigurationPtr
createConfiguration(const std::string & root_name, const SettingsChanges & settings, const SettingsChanges & overridability)
{
namespace Configuration = NamedCollectionConfiguration;
auto config = Configuration::createEmptyConfiguration(root_name);
for (const auto & [name, value] : settings)
Configuration::setConfigValue<String>(*config, name, convertFieldToString(value));
for (const auto & [name, value] : overridability)
Configuration::setOverridable(*config, name, value.get<bool>());
return config;
}
@ -204,6 +210,20 @@ void listKeys(
listKeys(config, enumerate_paths, result, depth);
}
std::optional<bool> getOverridable(const Poco::Util::AbstractConfiguration & config, const std::string & path)
{
std::string overridable_path = path + "[@overridable]";
if (config.has(overridable_path))
return config.getBool(overridable_path);
return {};
}
void setOverridable(Poco::Util::AbstractConfiguration & config, const std::string & path, const bool value)
{
std::string overridable_path = path + "[@overridable]";
config.setBool(overridable_path, value);
}
template String getConfigValue<String>(const Poco::Util::AbstractConfiguration & config,
const std::string & path);
template UInt64 getConfigValue<UInt64>(const Poco::Util::AbstractConfiguration & config,

View File

@ -43,7 +43,8 @@ void removeConfigValue(
Poco::Util::AbstractConfiguration & config,
const std::string & path);
ConfigurationPtr createConfiguration(const std::string & root_name, const SettingsChanges & settings);
ConfigurationPtr
createConfiguration(const std::string & root_name, const SettingsChanges & settings, const SettingsChanges & overridability);
/// Enumerate keys paths of the config recursively.
/// E.g. if `enumerate_paths` = {"root.key1"} and config like
@ -67,6 +68,10 @@ void listKeys(
std::queue<std::string> enumerate_paths,
std::set<std::string, std::less<>> & result,
ssize_t depth);
std::optional<bool> getOverridable(const Poco::Util::AbstractConfiguration & config, const std::string & path);
void setOverridable(Poco::Util::AbstractConfiguration & config, const std::string & path, bool value);
}
}

View File

@ -199,6 +199,12 @@ public:
for (const auto & [name, value] : create_query.changes)
result_changes_map.emplace(name, value);
std::unordered_map<std::string, Field> result_overridability_map;
for (const auto & [name, value] : query.overridability)
result_overridability_map.emplace(name, value);
for (const auto & [name, value] : create_query.overridability)
result_overridability_map.emplace(name, value);
for (const auto & delete_key : query.delete_keys)
{
auto it = result_changes_map.find(delete_key);
@ -210,12 +216,20 @@ public:
delete_key);
}
else
{
result_changes_map.erase(it);
auto it_override = result_overridability_map.find(delete_key);
if (it_override != result_overridability_map.end())
result_overridability_map.erase(it_override);
}
}
create_query.changes.clear();
for (const auto & [name, value] : result_changes_map)
create_query.changes.emplace_back(name, value);
create_query.overridability.clear();
for (const auto & [name, value] : result_overridability_map)
create_query.overridability.emplace_back(name, value);
writeCreateQueryToMetadata(
create_query,
@ -244,8 +258,7 @@ private:
const ASTCreateNamedCollectionQuery & query)
{
const auto & collection_name = query.collection_name;
const auto config = NamedCollectionConfiguration::createConfiguration(
collection_name, query.changes);
const auto config = NamedCollectionConfiguration::createConfiguration(collection_name, query.changes, query.overridability);
std::set<std::string, std::less<>> keys;
for (const auto & [name, _] : query.changes)
@ -448,6 +461,9 @@ void updateFromSQL(const ASTAlterNamedCollectionQuery & query, ContextPtr contex
for (const auto & [name, value] : query.changes)
collection->setOrUpdate<String, true>(name, convertFieldToString(value));
for (const auto & [name, value] : query.overridability)
collection->setOverridable<true>(name, value.get<bool>());
for (const auto & key : query.delete_keys)
collection->remove<true>(key);
}

View File

@ -223,6 +223,16 @@ public:
keys.insert(key);
}
bool getOverridable(const Key & key, const bool default_value)
{
const auto overridable = Configuration::getOverridable(*config, key);
if (overridable)
return *overridable;
return default_value;
}
void setOverridable(const Key & key, const bool value) { Configuration::setOverridable(*config, key, value); }
ImplPtr createCopy(const std::string & collection_name_) const
{
return create(*config, collection_name_, "", keys);
@ -414,6 +424,22 @@ template <typename T, bool Locked> void NamedCollection::setOrUpdate(const Key &
pimpl->set<T>(key, value, true);
}
bool NamedCollection::getOverridable(const Key & key, bool default_value) const
{
std::lock_guard lock(mutex);
return pimpl->getOverridable(key, default_value);
}
template <bool Locked>
void NamedCollection::setOverridable(const Key & key, bool value)
{
assertMutable();
std::unique_lock lock(mutex, std::defer_lock);
if constexpr (!Locked)
lock.lock();
return pimpl->setOverridable(key, value);
}
template <bool Locked> void NamedCollection::remove(const Key & key)
{
assertMutable();
@ -519,6 +545,9 @@ template void NamedCollection::setOrUpdate<Float64, true>(const NamedCollection:
template void NamedCollection::setOrUpdate<Float64, false>(const NamedCollection::Key & key, const Float64 & value);
template void NamedCollection::setOrUpdate<bool, false>(const NamedCollection::Key & key, const bool & value);
template void NamedCollection::setOverridable<false>(const NamedCollection::Key & key, const bool value);
template void NamedCollection::setOverridable<true>(const NamedCollection::Key & key, const bool value);
template void NamedCollection::remove<true>(const Key & key);
template void NamedCollection::remove<false>(const Key & key);

View File

@ -51,6 +51,11 @@ public:
template <typename T, bool locked = false> void setOrUpdate(const Key & key, const T & value);
bool getOverridable(const Key & key, bool default_value) const;
template <bool locked = false>
void setOverridable(const Key & key, bool value);
template <bool locked = false> void remove(const Key & key);
MutableNamedCollectionPtr duplicate() const;

View File

@ -817,6 +817,7 @@ class IColumn;
M(Bool, create_index_ignore_unique, false, "Ignore UNIQUE keyword in CREATE UNIQUE INDEX. Made for SQL compatibility tests.", 0) \
M(Bool, print_pretty_type_names, false, "Print pretty type names in DESCRIBE query and toTypeName() function", 0) \
M(Bool, create_table_empty_primary_key_by_default, false, "Allow to create *MergeTree tables with empty primary key when ORDER BY and PRIMARY KEY not specified", 0) \
M(Bool, allow_named_collection_override_by_default, true, "Allow named collections' fields override by default.", 0)\
// End of COMMON_SETTINGS
// Please add settings related to formats into the FORMAT_FACTORY_SETTINGS, move obsolete settings to OBSOLETE_SETTINGS and obsolete format settings to OBSOLETE_FORMAT_SETTINGS.

View File

@ -35,6 +35,9 @@ void ASTAlterNamedCollectionQuery::formatImpl(const IAST::FormatSettings & setti
settings.ostr << " = " << applyVisitor(FieldVisitorToString(), change.value);
else
settings.ostr << " = '[HIDDEN]'";
Field override_value;
if (overridability.tryGet(change.name, override_value))
settings.ostr << " " << (override_value.get<bool>() ? "" : "NOT ") << "OVERRIDABLE";
}
}
if (!delete_keys.empty())

View File

@ -15,6 +15,7 @@ public:
SettingsChanges changes;
std::vector<std::string> delete_keys;
bool if_exists = false;
SettingsChanges overridability;
String getID(char) const override { return "AlterNamedCollectionQuery"; }

View File

@ -39,6 +39,9 @@ void ASTCreateNamedCollectionQuery::formatImpl(const IAST::FormatSettings & sett
settings.ostr << " = " << applyVisitor(FieldVisitorToString(), change.value);
else
settings.ostr << " = '[HIDDEN]'";
Field override_value;
if (overridability.tryGet(change.name, override_value))
settings.ostr << " " << (override_value.get<bool>() ? "" : "NOT ") << "OVERRIDABLE";
}
}

View File

@ -14,6 +14,7 @@ public:
std::string collection_name;
SettingsChanges changes;
bool if_not_exists = false;
SettingsChanges overridability;
String getID(char) const override { return "CreateNamedCollectionQuery"; }

View File

@ -17,14 +17,16 @@ bool ParserAlterNamedCollectionQuery::parseImpl(IParser::Pos & pos, ASTPtr & nod
ParserKeyword s_on("ON");
ParserKeyword s_delete("DELETE");
ParserIdentifier name_p;
ParserSetQuery set_p;
ParserKeyword s_set("SET");
ParserKeyword s_overridable("OVERRIDABLE");
ParserKeyword s_not_overridable("NOT OVERRIDABLE");
ParserToken s_comma(TokenType::Comma);
String cluster_str;
bool if_exists = false;
ASTPtr collection_name;
ASTPtr set;
std::vector<std::string> delete_keys;
if (!s_alter.ignore(pos, expected))
@ -45,20 +47,27 @@ bool ParserAlterNamedCollectionQuery::parseImpl(IParser::Pos & pos, ASTPtr & nod
return false;
}
bool parsed_delete = false;
if (!set_p.parse(pos, set, expected))
SettingsChanges changes;
SettingsChanges overridability;
if (s_set.ignore(pos, expected))
{
if (!s_delete.ignore(pos, expected))
return false;
while (true)
{
if (!changes.empty() && !s_comma.ignore(pos))
break;
parsed_delete = true;
}
else if (s_delete.ignore(pos, expected))
{
parsed_delete = true;
changes.push_back(SettingChange{});
if (!ParserSetQuery::parseNameValuePair(changes.back(), pos, expected))
return false;
if (s_not_overridable.ignore(pos, expected))
overridability.push_back(SettingChange{changes.back().name, false});
else if (s_overridable.ignore(pos, expected))
overridability.push_back(SettingChange{changes.back().name, true});
}
}
if (parsed_delete)
if (s_delete.ignore(pos, expected))
{
while (true)
{
@ -78,8 +87,8 @@ bool ParserAlterNamedCollectionQuery::parseImpl(IParser::Pos & pos, ASTPtr & nod
query->collection_name = getIdentifierName(collection_name);
query->if_exists = if_exists;
query->cluster = std::move(cluster_str);
if (set)
query->changes = set->as<ASTSetQuery>()->changes;
query->changes = changes;
query->overridability = overridability;
query->delete_keys = delete_keys;
node = query;

View File

@ -1517,6 +1517,8 @@ bool ParserCreateNamedCollectionQuery::parseImpl(Pos & pos, ASTPtr & node, Expec
ParserKeyword s_if_not_exists("IF NOT EXISTS");
ParserKeyword s_on("ON");
ParserKeyword s_as("AS");
ParserKeyword s_not_overridable("NOT OVERRIDABLE");
ParserKeyword s_overridable("OVERRIDABLE");
ParserIdentifier name_p;
ParserToken s_comma(TokenType::Comma);
@ -1547,6 +1549,7 @@ bool ParserCreateNamedCollectionQuery::parseImpl(Pos & pos, ASTPtr & node, Expec
return false;
SettingsChanges changes;
SettingsChanges overridability;
while (true)
{
@ -1557,6 +1560,10 @@ bool ParserCreateNamedCollectionQuery::parseImpl(Pos & pos, ASTPtr & node, Expec
if (!ParserSetQuery::parseNameValuePair(changes.back(), pos, expected))
return false;
if (s_not_overridable.ignore(pos, expected))
overridability.push_back(SettingChange{changes.back().name, false});
else if (s_overridable.ignore(pos, expected))
overridability.push_back(SettingChange{changes.back().name, true});
}
auto query = std::make_shared<ASTCreateNamedCollectionQuery>();
@ -1565,6 +1572,7 @@ bool ParserCreateNamedCollectionQuery::parseImpl(Pos & pos, ASTPtr & node, Expec
query->if_not_exists = if_not_exists;
query->changes = changes;
query->cluster = std::move(cluster_str);
query->overridability = overridability;
node = query;
return true;

View File

@ -92,14 +92,20 @@ MutableNamedCollectionPtr tryGetNamedCollectionWithOverrides(
if (asts.size() == 1)
return collection_copy;
const auto allow_override_by_default = context->getSettings().allow_named_collection_override_by_default;
for (auto * it = std::next(asts.begin()); it != asts.end(); ++it)
{
auto value_override = getKeyValueFromAST(*it, /* fallback_to_ast_value */complex_args != nullptr, context);
if (!value_override && !(*it)->as<ASTFunction>())
throw Exception(ErrorCodes::BAD_ARGUMENTS, "Expected key-value argument or function");
if (!value_override)
if (!value_override && allow_override_by_default)
continue;
else if (!value_override)
throw Exception(ErrorCodes::BAD_ARGUMENTS, "Override not allowed");
else if (!collection_copy->getOverridable(value_override->first, allow_override_by_default))
throw Exception(ErrorCodes::BAD_ARGUMENTS, "Override not allowed for '{}'", value_override->first);
if (const ASTPtr * value = std::get_if<ASTPtr>(&value_override->second))
{
@ -108,7 +114,7 @@ MutableNamedCollectionPtr tryGetNamedCollectionWithOverrides(
}
const auto & [key, value] = *value_override;
collection_copy->setOrUpdate<String>(key, toString(std::get<Field>(value_override->second)));
collection_copy->setOrUpdate<String>(key, toString(std::get<Field>(value)));
}
return collection_copy;
@ -128,8 +134,14 @@ MutableNamedCollectionPtr tryGetNamedCollectionWithOverrides(
Poco::Util::AbstractConfiguration::Keys keys;
config.keys(config_prefix, keys);
const auto allow_override_by_default = context->getSettings().allow_named_collection_override_by_default;
for (const auto & key : keys)
collection_copy->setOrUpdate<String>(key, config.getString(config_prefix + '.' + key));
{
if (collection_copy->getOverridable(key, allow_override_by_default))
collection_copy->setOrUpdate<String>(key, config.getString(config_prefix + '.' + key));
else
throw Exception(ErrorCodes::BAD_ARGUMENTS, "Override not allowed for '{}'", key);
}
return collection_copy;
}

View File

@ -37,5 +37,13 @@
<access_key_id>test</access_key_id>
<secret_access_key>testtest</secret_access_key>
</s3_conn_db>
<url_override1>
<url overridable="0">http://127.0.0.1:8123?query=select+1</url>
<format overridable="1">RawBLOB</format>
</url_override1>
<url_override2>
<url>http://127.0.0.1:8123?query=select+1</url>
<format>RawBLOB</format>
</url_override2>
</named_collections>
</clickhouse>

View File

@ -0,0 +1,32 @@
allow_named_collection_override_by_default=1 u1
1\n
1\n
1
allow_named_collection_override_by_default=1 u2
1\n
1\n
2\n
1
allow_named_collection_override_by_default=0 u1
1\n
1
allow_named_collection_override_by_default=0 u2
1\n
Test ALTER
2\n
1\n
Test XML collections
allow_named_collection_override_by_default=1 url_override1
1\n
1\n
1
allow_named_collection_override_by_default=1 url_override2
1\n
1\n
2\n
1
allow_named_collection_override_by_default=0 url_override1
1\n
1
allow_named_collection_override_by_default=0 url_override2
1\n

View File

@ -0,0 +1,73 @@
DROP NAMED COLLECTION IF EXISTS u1;
DROP NAMED COLLECTION IF EXISTS u2;
CREATE NAMED COLLECTION u1 AS
url = 'http://127.0.0.1:8123?query=select+1' NOT OVERRIDABLE,
format = 'RawBLOB' OVERRIDABLE;
CREATE NAMED COLLECTION u2 AS
url = 'http://127.0.0.1:8123?query=select+1',
format = 'RawBLOB';
SET allow_named_collection_override_by_default=1;
SELECT 'allow_named_collection_override_by_default=1 u1';
SELECT * FROM url(u1);
SELECT * FROM url(u1, headers('Accept'='text/csv; charset=utf-8'));
SELECT * FROM url(u1, url='http://127.0.0.1:8123?query=select+2'); -- { serverError BAD_ARGUMENTS }
SELECT * FROM url(u1, format='CSV');
SELECT 'allow_named_collection_override_by_default=1 u2';
SELECT * FROM url(u2);
SELECT * FROM url(u2, headers('Accept'='text/csv; charset=utf-8'));
SELECT * FROM url(u2, url='http://127.0.0.1:8123?query=select+2');
SELECT * FROM url(u2, format='CSV');
SET allow_named_collection_override_by_default=0;
SELECT 'allow_named_collection_override_by_default=0 u1';
SELECT * FROM url(u1);
SELECT * FROM url(u1, headers('Accept'='text/csv; charset=utf-8')); -- { serverError BAD_ARGUMENTS }
SELECT * FROM url(u1, url='http://127.0.0.1:8123?query=select+2'); -- { serverError BAD_ARGUMENTS }
SELECT * FROM url(u1, format='CSV');
SELECT 'allow_named_collection_override_by_default=0 u2';
SELECT * FROM url(u2);
SELECT * FROM url(u2, headers('Accept'='text/csv; charset=utf-8')); -- { serverError BAD_ARGUMENTS }
SELECT * FROM url(u2, url='http://127.0.0.1:8123?query=select+2'); -- { serverError BAD_ARGUMENTS }
SELECT * FROM url(u2, format='CSV'); -- { serverError BAD_ARGUMENTS }
SELECT 'Test ALTER';
ALTER NAMED COLLECTION u1 SET
url = 'http://127.0.0.1:8123?query=select+2' OVERRIDABLE,
format = 'RawBLOB' NOT OVERRIDABLE;
SELECT * FROM url(u1);
SELECT * FROM url(u1, url='http://127.0.0.1:8123?query=select+1');
SELECT * FROM url(u1, format='CSV'); -- { serverError BAD_ARGUMENTS }
DROP NAMED COLLECTION IF EXISTS u1;
DROP NAMED COLLECTION IF EXISTS u2;
SELECT 'Test XML collections';
SET allow_named_collection_override_by_default=1;
SELECT 'allow_named_collection_override_by_default=1 url_override1';
SELECT * FROM url(url_override1);
SELECT * FROM url(url_override1, headers('Accept'='text/csv; charset=utf-8'));
SELECT * FROM url(url_override1, url='http://127.0.0.1:8123?query=select+2'); -- { serverError BAD_ARGUMENTS }
SELECT * FROM url(url_override1, format='CSV');
SELECT 'allow_named_collection_override_by_default=1 url_override2';
SELECT * FROM url(url_override2);
SELECT * FROM url(url_override2, headers('Accept'='text/csv; charset=utf-8'));
SELECT * FROM url(url_override2, url='http://127.0.0.1:8123?query=select+2');
SELECT * FROM url(url_override2, format='CSV');
SET allow_named_collection_override_by_default=0;
SELECT 'allow_named_collection_override_by_default=0 url_override1';
SELECT * FROM url(url_override1);
SELECT * FROM url(url_override1, headers('Accept'='text/csv; charset=utf-8')); -- { serverError BAD_ARGUMENTS }
SELECT * FROM url(url_override1, url='http://127.0.0.1:8123?query=select+2'); -- { serverError BAD_ARGUMENTS }
SELECT * FROM url(url_override1, format='CSV');
SELECT 'allow_named_collection_override_by_default=0 url_override2';
SELECT * FROM url(url_override2);
SELECT * FROM url(url_override2, headers('Accept'='text/csv; charset=utf-8')); -- { serverError BAD_ARGUMENTS }
SELECT * FROM url(url_override2, url='http://127.0.0.1:8123?query=select+2'); -- { serverError BAD_ARGUMENTS }
SELECT * FROM url(url_override2, format='CSV'); -- { serverError BAD_ARGUMENTS }