Preserve last-wins semantics for duplicate parameter names on the wire

readQueryParameters used NameToNameMap::emplace, which keeps the first
occurrence of a duplicate key. The Settings-based path it replaced kept
the last occurrence (BaseSettings::read overwrites the same custom
setting entry on each occurrence of its name), so a driver relying on
appending an override without deduplicating first would see different
behavior. Use insert_or_assign to keep the old semantics, and add gtest
coverage for the round trip and the duplicate-name case.
This commit is contained in:
Raúl Marín
2026-07-16 18:25:08 +02:00
parent ee423ea5bf
commit ee7b3e933b
2 changed files with 36 additions and 1 deletions
+1 -1
View File
@@ -9158,7 +9158,7 @@ NameToNameMap readQueryParameters(ReadBuffer & in)
String value;
ReadBufferFromOwnString buf(BaseSettingsHelpers::readString(in));
readQuoted(value, buf);
parameters.emplace(std::move(name), std::move(value));
parameters.insert_or_assign(std::move(name), std::move(value));
}
return parameters;
}
+35
View File
@@ -1,9 +1,13 @@
#include <gtest/gtest.h>
#include <Common/Exception.h>
#include <Core/BaseSettings.h>
#include <Core/Settings.h>
#include <Core/SettingsFields.h>
#include <Core/SettingsEnums.h>
#include <Core/Field.h>
#include <IO/ReadBufferFromString.h>
#include <IO/WriteBufferFromString.h>
namespace
{
@@ -141,3 +145,34 @@ GTEST_TEST(SettingMySQLDataTypesSupport, SetInvalidString)
ASSERT_TRUE(setting.changed);
ASSERT_EQ(std::vector<MySQLDataTypesSupport>{}, setting.value);
}
GTEST_TEST(QueryParameters, RoundTrip)
{
NameToNameMap parameters{{"max_threads", "0"}, {"name", "John's \\ Doe"}, {"empty", ""}};
WriteBufferFromOwnString out;
writeQueryParameters(parameters, out);
ReadBufferFromString in(out.str());
ASSERT_EQ(readQueryParameters(in), parameters);
}
GTEST_TEST(QueryParameters, DuplicateNameOnTheWireLastOccurrenceWins)
{
/// A driver that appends an override without deduplicating first relies on the last
/// occurrence of a repeated Parameter[] key winning, matching the pre-existing behavior
/// of routing parameters through a Settings object (BaseSettings::read overwrites the
/// same custom setting entry on each occurrence of its name).
WriteBufferFromOwnString out;
BaseSettingsHelpers::writeString("x", out);
BaseSettingsHelpers::writeFlags(BaseSettingsHelpers::Flags::CUSTOM, out);
BaseSettingsHelpers::writeString(SettingFieldCustom(Field(String("1"))).toString(), out);
BaseSettingsHelpers::writeString("x", out);
BaseSettingsHelpers::writeFlags(BaseSettingsHelpers::Flags::CUSTOM, out);
BaseSettingsHelpers::writeString(SettingFieldCustom(Field(String("2"))).toString(), out);
BaseSettingsHelpers::writeString(std::string_view{}, out);
ReadBufferFromString in(out.str());
NameToNameMap parameters = readQueryParameters(in);
ASSERT_EQ(parameters.at("x"), "2");
}