From dd45a809c5a31f3734297953f506df3976ebecf7 Mon Sep 17 00:00:00 2001 From: robot-clickhouse Date: Mon, 18 May 2026 13:34:35 +0000 Subject: [PATCH 001/146] =?UTF-8?q?Backport=20#100939=20to=2026.3:=20Fix?= =?UTF-8?q?=20FunctionVariantAdaptor=20silently=20returning=200=20rows=20w?= =?UTF-8?q?hen=20all=20Variant=E2=80=A6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/Functions/FunctionVariantAdaptor.cpp | 34 ++++++++++++++-- ...variant_adaptor_all_incompatible.reference | 5 +++ ...04202_variant_adaptor_all_incompatible.sql | 40 +++++++++++++++++++ 3 files changed, 75 insertions(+), 4 deletions(-) create mode 100644 tests/queries/0_stateless/04202_variant_adaptor_all_incompatible.reference create mode 100644 tests/queries/0_stateless/04202_variant_adaptor_all_incompatible.sql diff --git a/src/Functions/FunctionVariantAdaptor.cpp b/src/Functions/FunctionVariantAdaptor.cpp index 88476476e169..d92737ce23d5 100644 --- a/src/Functions/FunctionVariantAdaptor.cpp +++ b/src/Functions/FunctionVariantAdaptor.cpp @@ -648,12 +648,38 @@ FunctionBaseVariantAdaptor::FunctionBaseVariantAdaptor( } } - /// If no valid result types were found, all alternatives are incompatible - /// Return Nullable(Nothing) to indicate NULL result for all rows + /// If no valid result types were found, all Variant alternatives are incompatible with the function. + /// When variant_throw_on_type_mismatch is enabled (the default), throw a clear error rather than + /// silently returning Nullable(Nothing), which would cause WHERE clauses to return 0 rows with no + /// diagnostic. When the setting is disabled, fall back to Nullable(Nothing) so that executeImpl + /// returns NULL rows (consistent with the per-row mismatch behaviour). if (result_types.empty()) { - return_type = std::make_shared(std::make_shared()); - return; + bool throw_on_mismatch = true; + if (CurrentThread::isInitialized()) + { + if (auto query_context = CurrentThread::tryGetQueryContext()) + throw_on_mismatch = query_context->getSettingsRef()[Setting::variant_throw_on_type_mismatch]; + } + + if (!throw_on_mismatch) + { + return_type = makeNullable(std::make_shared()); + return; + } + + String alt_names; + for (const auto & alt : variant_alternatives) + { + if (!alt_names.empty()) + alt_names += ", "; + alt_names += alt->getName(); + } + throw Exception( + ErrorCodes::ILLEGAL_TYPE_OF_ARGUMENT, + "None of the Variant alternatives ({}) are compatible with function '{}'", + alt_names, + function_overload_resolver->getName()); } /// If all result types are the same (ignoring Nullable), return Nullable(common). diff --git a/tests/queries/0_stateless/04202_variant_adaptor_all_incompatible.reference b/tests/queries/0_stateless/04202_variant_adaptor_all_incompatible.reference new file mode 100644 index 000000000000..6febcf3dc66e --- /dev/null +++ b/tests/queries/0_stateless/04202_variant_adaptor_all_incompatible.reference @@ -0,0 +1,5 @@ +1 +1 +1 +1 +0 diff --git a/tests/queries/0_stateless/04202_variant_adaptor_all_incompatible.sql b/tests/queries/0_stateless/04202_variant_adaptor_all_incompatible.sql new file mode 100644 index 000000000000..b07f61def522 --- /dev/null +++ b/tests/queries/0_stateless/04202_variant_adaptor_all_incompatible.sql @@ -0,0 +1,40 @@ +-- Regression test: FunctionVariantAdaptor must throw ILLEGAL_TYPE_OF_ARGUMENT +-- consistently when ALL Variant alternatives are incompatible with a function, +-- instead of silently returning Nullable(Nothing) which caused WHERE clauses +-- to return 0 rows with no diagnostic. +-- +-- The fix: when result_types is empty after trying all alternatives, +-- throw instead of falling back to Nullable(Nothing). + +SET enable_analyzer = 1; + +-- Variant(UInt32, Date): neither alternative is compatible with base58Encode(String). + +-- WHERE context: must throw, not silently return 0 rows. +SELECT count() FROM ( + SELECT CAST(number::UInt32 AS Variant(UInt32, Date)) AS v FROM numbers(5) +) +WHERE base58Encode(v) != ''; -- {serverError ILLEGAL_TYPE_OF_ARGUMENT} + +-- SELECT context: must throw the same error (was already consistent). +SELECT base58Encode(v) FROM ( + SELECT CAST(number::UInt32 AS Variant(UInt32, Date)) AS v FROM numbers(3) +); -- {serverError ILLEGAL_TYPE_OF_ARGUMENT} + +-- Sanity check: a Variant where one alternative IS compatible works correctly. +-- base58Encode accepts String, so Variant(UInt32, String) rows with String value +-- produce a result; UInt32 rows produce NULL. +SELECT base58Encode(v) IS NOT NULL FROM ( + SELECT CAST('hello'::String AS Variant(UInt32, String)) AS v +); + +-- With variant_throw_on_type_mismatch = 0, all-incompatible case must return NULL rows +-- instead of throwing, consistent with the per-row mismatch behaviour. +SET variant_throw_on_type_mismatch = 0; +SELECT base58Encode(v) IS NULL FROM ( + SELECT CAST(number::UInt32 AS Variant(UInt32, Date)) AS v FROM numbers(3) +); +SELECT count() FROM ( + SELECT CAST(number::UInt32 AS Variant(UInt32, Date)) AS v FROM numbers(3) +) +WHERE base58Encode(v) != ''; From 82e6ff5e528f4e897ff8590f2b02b655e09e326a Mon Sep 17 00:00:00 2001 From: robot-clickhouse Date: Tue, 2 Jun 2026 15:41:05 +0000 Subject: [PATCH 002/146] Backport #106077 to 26.3: Bump `curl` from 8.19 to 8.20 --- contrib/curl | 2 +- contrib/curl-cmake/CMakeLists.txt | 6 +++++- contrib/curl-cmake/curl_config.h | 4 +++- 3 files changed, 9 insertions(+), 3 deletions(-) diff --git a/contrib/curl b/contrib/curl index 8c908d2d0a6d..a05f34973e6c 160000 --- a/contrib/curl +++ b/contrib/curl @@ -1 +1 @@ -Subproject commit 8c908d2d0a6d32abdedda2c52e90bd56ec76c24d +Subproject commit a05f34973e6c4bb629d018f7cb51487be1c904d8 diff --git a/contrib/curl-cmake/CMakeLists.txt b/contrib/curl-cmake/CMakeLists.txt index 90d1659d3d9d..b9c8f955a3d2 100644 --- a/contrib/curl-cmake/CMakeLists.txt +++ b/contrib/curl-cmake/CMakeLists.txt @@ -15,6 +15,7 @@ set (SRCS "${LIBRARY_DIR}/lib/asyn-thrdd.c" "${LIBRARY_DIR}/lib/bufq.c" "${LIBRARY_DIR}/lib/bufref.c" + "${LIBRARY_DIR}/lib/cf-dns.c" "${LIBRARY_DIR}/lib/cf-h1-proxy.c" "${LIBRARY_DIR}/lib/cf-h2-proxy.c" "${LIBRARY_DIR}/lib/cf-haproxy.c" @@ -37,7 +38,6 @@ set (SRCS "${LIBRARY_DIR}/lib/curl_memrchr.c" "${LIBRARY_DIR}/lib/curl_ntlm_core.c" "${LIBRARY_DIR}/lib/curl_range.c" - "${LIBRARY_DIR}/lib/curl_rtmp.c" "${LIBRARY_DIR}/lib/curl_sasl.c" "${LIBRARY_DIR}/lib/curl_sha512_256.c" "${LIBRARY_DIR}/lib/curl_share.c" @@ -47,6 +47,7 @@ set (SRCS "${LIBRARY_DIR}/lib/cw-out.c" "${LIBRARY_DIR}/lib/cw-pause.c" "${LIBRARY_DIR}/lib/dict.c" + "${LIBRARY_DIR}/lib/dnscache.c" "${LIBRARY_DIR}/lib/doh.c" "${LIBRARY_DIR}/lib/dynhds.c" "${LIBRARY_DIR}/lib/easy.c" @@ -100,6 +101,7 @@ set (SRCS "${LIBRARY_DIR}/lib/pingpong.c" "${LIBRARY_DIR}/lib/pop3.c" "${LIBRARY_DIR}/lib/progress.c" + "${LIBRARY_DIR}/lib/protocol.c" "${LIBRARY_DIR}/lib/psl.c" "${LIBRARY_DIR}/lib/rand.c" "${LIBRARY_DIR}/lib/ratelimit.c" @@ -123,6 +125,8 @@ set (SRCS "${LIBRARY_DIR}/lib/system_win32.c" "${LIBRARY_DIR}/lib/telnet.c" "${LIBRARY_DIR}/lib/tftp.c" + "${LIBRARY_DIR}/lib/thrdpool.c" + "${LIBRARY_DIR}/lib/thrdqueue.c" "${LIBRARY_DIR}/lib/transfer.c" "${LIBRARY_DIR}/lib/uint-bset.c" "${LIBRARY_DIR}/lib/uint-hash.c" diff --git a/contrib/curl-cmake/curl_config.h b/contrib/curl-cmake/curl_config.h index 243af6b0a3ae..d36bb558eced 100644 --- a/contrib/curl-cmake/curl_config.h +++ b/contrib/curl-cmake/curl_config.h @@ -57,8 +57,10 @@ #define ENABLE_IPV6 #define USE_OPENSSL -#define USE_THREADS_POSIX +#define HAVE_THREADS_POSIX #define USE_ARES +#define USE_RESOLV_ARES +#define CURL_ENABLE_NTLM #ifdef __illumos__ #define HAVE_POSIX_STRERROR_R 1 From 54e4e5cb5392db2dc743be847b47528f6047d4b3 Mon Sep 17 00:00:00 2001 From: robot-clickhouse Date: Tue, 2 Jun 2026 15:43:38 +0000 Subject: [PATCH 003/146] Backport #106076 to 26.3: Bump `krb5` to 1.22.2 --- contrib/krb5 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contrib/krb5 b/contrib/krb5 index 1279d8ae5acc..42d204a24a57 160000 --- a/contrib/krb5 +++ b/contrib/krb5 @@ -1 +1 @@ -Subproject commit 1279d8ae5accd8a7b79ed7f603770fecb90db759 +Subproject commit 42d204a24a577ef93081cb7b70cb78ff6f7130ee From 21970367f8028ecad8257837370ccf0d9e04e540 Mon Sep 17 00:00:00 2001 From: robot-clickhouse Date: Tue, 2 Jun 2026 15:46:23 +0000 Subject: [PATCH 004/146] Backport #105994 to 26.3: Use `postgres` REL_18_4 --- contrib/postgres | 2 +- contrib/postgres-cmake/CMakeLists.txt | 1 + contrib/postgres-cmake/pg_config.h | 17 +++++++++++------ 3 files changed, 13 insertions(+), 7 deletions(-) diff --git a/contrib/postgres b/contrib/postgres index c37596dd61c5..ab6952af3ac8 160000 --- a/contrib/postgres +++ b/contrib/postgres @@ -1 +1 @@ -Subproject commit c37596dd61c5f2b8b7521fdbcdabc651bd9412c4 +Subproject commit ab6952af3ac8cdede654be4dc68f990b0352c213 diff --git a/contrib/postgres-cmake/CMakeLists.txt b/contrib/postgres-cmake/CMakeLists.txt index 88c5eceb1a33..c843d857ba26 100644 --- a/contrib/postgres-cmake/CMakeLists.txt +++ b/contrib/postgres-cmake/CMakeLists.txt @@ -56,6 +56,7 @@ set(SRCS "${POSTGRES_SOURCE_DIR}/src/port/pgstrcasecmp.c" "${POSTGRES_SOURCE_DIR}/src/port/pg_bitutils.c" "${POSTGRES_SOURCE_DIR}/src/port/path.c" + "${POSTGRES_SOURCE_DIR}/src/port/timingsafe_bcmp.c" ) if(NOT OS_DARWIN) diff --git a/contrib/postgres-cmake/pg_config.h b/contrib/postgres-cmake/pg_config.h index 12767588b94d..84e435838d41 100644 --- a/contrib/postgres-cmake/pg_config.h +++ b/contrib/postgres-cmake/pg_config.h @@ -618,19 +618,24 @@ #define PG_MAJORVERSION_NUM 18 /* PostgreSQL minor version number */ -#define PG_MINORVERSION_NUM 3 +#define PG_MINORVERSION_NUM 4 -/* Define to best printf format archetype, usually gnu_printf if available. */ -#define PG_PRINTF_ATTRIBUTE gnu_printf +/* Define to best C++ printf format archetype, usually gnu_printf if + available. */ +#define PG_CXX_PRINTF_ATTRIBUTE gnu_printf + +/* Define to best C printf format archetype, usually gnu_printf if available. + */ +#define PG_C_PRINTF_ATTRIBUTE gnu_printf /* PostgreSQL version as a string */ -#define PG_VERSION "18.3" +#define PG_VERSION "18.4" /* PostgreSQL version as a number */ -#define PG_VERSION_NUM 180003 +#define PG_VERSION_NUM 180004 /* A string containing the version number, platform, and C compiler */ -#define PG_VERSION_STR "PostgreSQL 18.3 on x86_64-pc-linux-gnu, compiled by gcc (GCC) 15.2.1 20250813, 64-bit" +#define PG_VERSION_STR "PostgreSQL 18.4 on x86_64-pc-linux-gnu, compiled by gcc (GCC) 15.2.1 20250813, 64-bit" /* Define to 1 to allow profiling output to be saved separately for each process. */ From d24f67fd0982c4dd7de96b7ce63bdb301c942827 Mon Sep 17 00:00:00 2001 From: robot-clickhouse Date: Tue, 2 Jun 2026 15:49:05 +0000 Subject: [PATCH 005/146] Backport #105985 to 26.3: Bump libxml2 from 2.15.1 to 2.15.3 --- contrib/libxml2 | 2 +- contrib/libxml2-cmake/linux_x86_64/include/config.h | 6 +++--- .../linux_x86_64/include/libxml/xmlversion.h | 8 ++++---- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/contrib/libxml2 b/contrib/libxml2 index b7fa62cbe8ef..c94eb0210183 160000 --- a/contrib/libxml2 +++ b/contrib/libxml2 @@ -1 +1 @@ -Subproject commit b7fa62cbe8ef0df5869e000d5b690bdedd07f33e +Subproject commit c94eb0210183b9d7cb43f8e7fddc6be55843ef49 diff --git a/contrib/libxml2-cmake/linux_x86_64/include/config.h b/contrib/libxml2-cmake/linux_x86_64/include/config.h index 600e7570e990..8abacf912b82 100644 --- a/contrib/libxml2-cmake/linux_x86_64/include/config.h +++ b/contrib/libxml2-cmake/linux_x86_64/include/config.h @@ -77,7 +77,7 @@ #define PACKAGE_NAME "libxml2" /* Define to the full name and version of this package. */ -#define PACKAGE_STRING "libxml2 2.15.1" +#define PACKAGE_STRING "libxml2 2.15.3" /* Define to the one symbol short name of this package. */ #define PACKAGE_TARNAME "libxml2" @@ -86,7 +86,7 @@ #define PACKAGE_URL "" /* Define to the version of this package. */ -#define PACKAGE_VERSION "2.15.1" +#define PACKAGE_VERSION "2.15.3" /* Define to 1 if all of the C90 standard headers exist (not just the ones required in a freestanding environment). This macro is provided for @@ -94,7 +94,7 @@ #define STDC_HEADERS 1 /* Version number of package */ -#define VERSION "2.15.1" +#define VERSION "2.15.3" /* System configuration directory (/etc) */ #define XML_SYSCONFDIR "/usr/local/etc" diff --git a/contrib/libxml2-cmake/linux_x86_64/include/libxml/xmlversion.h b/contrib/libxml2-cmake/linux_x86_64/include/libxml/xmlversion.h index ff8b0ed8011b..e63ad461ce92 100644 --- a/contrib/libxml2-cmake/linux_x86_64/include/libxml/xmlversion.h +++ b/contrib/libxml2-cmake/linux_x86_64/include/libxml/xmlversion.h @@ -16,17 +16,17 @@ /** * the version string like "1.2.3" */ -#define LIBXML_DOTTED_VERSION "2.15.1" +#define LIBXML_DOTTED_VERSION "2.15.3" /** * the version number: 1.2.3 value is 10203 */ -#define LIBXML_VERSION 21501 +#define LIBXML_VERSION 21503 /** * the version number string, 1.2.3 value is "10203" */ -#define LIBXML_VERSION_STRING "21501" +#define LIBXML_VERSION_STRING "21503" /** * extra version information, used to show a git commit description @@ -37,7 +37,7 @@ * Macro to check that the libxml version in use is compatible with * the version the software has been compiled against */ -#define LIBXML_TEST_VERSION xmlCheckVersion(21501); +#define LIBXML_TEST_VERSION xmlCheckVersion(21503); #if 1 /** From 5131f51ca484752b5a77ed684ed94594be2c891f Mon Sep 17 00:00:00 2001 From: robot-clickhouse Date: Thu, 4 Jun 2026 09:39:15 +0000 Subject: [PATCH 006/146] Backport #99531 to 26.3: validate column structure before applying patches --- .../MergeTree/PatchParts/applyPatches.cpp | 97 ++++++++-- .../gtest_apply_patches_type_mismatch.cpp | 178 ++++++++++++++++++ 2 files changed, 255 insertions(+), 20 deletions(-) create mode 100644 src/Storages/MergeTree/PatchParts/tests/gtest_apply_patches_type_mismatch.cpp diff --git a/src/Storages/MergeTree/PatchParts/applyPatches.cpp b/src/Storages/MergeTree/PatchParts/applyPatches.cpp index ce43998a5458..e1de3183383b 100644 --- a/src/Storages/MergeTree/PatchParts/applyPatches.cpp +++ b/src/Storages/MergeTree/PatchParts/applyPatches.cpp @@ -8,6 +8,7 @@ #include #include #include +#include #include namespace ProfileEvents @@ -69,7 +70,10 @@ struct CombinedPatchBuilder build(); } - IColumn::Patch createPatchForColumn(const String & column_name, IColumn::Versions & dst_versions) const; + /// When @p result_type is provided, sources with a different type are + /// filtered out per-source so compatible updates survive schema transitions. + IColumn::Patch createPatchForColumn(const String & column_name, IColumn::Versions & dst_versions, + const DataTypePtr & result_type = nullptr) const; private: void build(); @@ -98,6 +102,11 @@ struct CombinedPatchBuilder IColumn::Offsets src_row_indices; /// Index of row in the result block. IColumn::Offsets dst_row_indices; + + /// Scratch space for type-filtering path in createPatchForColumn. + mutable IColumn::Offsets filtered_src_col_indices; + mutable IColumn::Offsets filtered_src_row_indices; + mutable IColumn::Offsets filtered_dst_row_indices; }; void CombinedPatchBuilder::build() @@ -224,33 +233,58 @@ void CombinedPatchBuilder::build() } } -IColumn::Patch CombinedPatchBuilder::createPatchForColumn(const String & column_name, IColumn::Versions & dst_versions) const +IColumn::Patch CombinedPatchBuilder::createPatchForColumn( + const String & column_name, IColumn::Versions & dst_versions, const DataTypePtr & result_type) const { std::vector sources; + std::vector source_ok(all_patch_blocks.size(), true); + std::vector remap(all_patch_blocks.size()); + bool need_filter = false; - for (const auto & patch_block : all_patch_blocks) + for (size_t i = 0; i < all_patch_blocks.size(); ++i) { - const auto & patch_column = patch_block.getByName(column_name).column; - if (!patch_column) + const auto & pcol = all_patch_blocks[i].getByName(column_name); + if (!pcol.column) throw Exception(ErrorCodes::LOGICAL_ERROR, "Column {} has null data in patch block", column_name); - IColumn::Patch::Source source = + if (result_type && !result_type->equals(*pcol.type)) { - .column = *patch_column, - .versions = getColumnUInt64Data(patch_block, PartDataVersionColumn::name), - }; + source_ok[i] = false; + need_filter = true; + continue; + } - sources.push_back(std::move(source)); + remap[i] = sources.size(); + sources.push_back({.column = *pcol.column, + .versions = getColumnUInt64Data(all_patch_blocks[i], PartDataVersionColumn::name)}); } - return IColumn::Patch + if (!need_filter) + return IColumn::Patch{ + .sources = std::move(sources), .src_col_indices = &src_block_indices, + .src_row_indices = src_row_indices, .dst_row_indices = dst_row_indices, + .dst_versions = dst_versions}; + + filtered_src_col_indices.clear(); + filtered_src_row_indices.clear(); + filtered_dst_row_indices.clear(); + + for (size_t j = 0; j < dst_row_indices.size(); ++j) { + if (!source_ok[src_block_indices[j]]) + continue; + filtered_src_col_indices.push_back(remap[src_block_indices[j]]); + filtered_src_row_indices.push_back(src_row_indices[j]); + filtered_dst_row_indices.push_back(dst_row_indices[j]); + } + + bool single_source = sources.size() == 1; + return IColumn::Patch{ .sources = std::move(sources), - .src_col_indices = &src_block_indices, - .src_row_indices = src_row_indices, - .dst_row_indices = dst_row_indices, - .dst_versions = dst_versions, - }; + .src_col_indices = single_source ? nullptr : &filtered_src_col_indices, + .src_row_indices = filtered_src_row_indices, + .dst_row_indices = filtered_dst_row_indices, + .dst_versions = dst_versions}; } Block getUpdatedHeader(const PatchesToApply & patches, const NameSet & updated_columns) @@ -284,10 +318,19 @@ Block getUpdatedHeader(const PatchesToApply & patches, const NameSet & updated_c headers.push_back(header.sortColumns()); } + if (headers.empty()) + return {}; + + /// Schema evolution may cause type mismatches across patch headers. + /// Skip assertion in that case — createPatchForColumn handles filtering. + for (size_t i = 1; i < headers.size(); ++i) + if (!isCompatibleHeader(headers[i], headers[0])) + return headers.front(); + for (size_t i = 1; i < headers.size(); ++i) assertCompatibleHeader(headers[i], headers[0], "patch parts"); - return headers.empty() ? Block{} : headers.front(); + return headers.front(); } bool canApplyPatchesRaw(const PatchesToApply & patches) @@ -338,10 +381,24 @@ void applyPatchesToBlockRaw( if (!patch_block.has(result_column.name)) continue; - const auto & patch_column = patch_block.getByName(result_column.name).column; - if (!patch_column) + const auto & patch_col_with_type = patch_block.getByName(result_column.name); + if (!patch_col_with_type.column) continue; + /// If the column type in the patch part doesn't match the result column + /// (e.g., due to schema evolution changing a column's type), skip the patch + /// for this column. Applying a patch with mismatched types would cause + /// undefined behavior in insertFrom (SIGSEGV in release builds). + if (!result_column.type->equals(*patch_col_with_type.type)) + { + LOG_WARNING(getLogger("applyPatches"), + "Skipping patch for column {} because column type has changed ({} -> {})", + result_column.name, patch_col_with_type.type->getName(), result_column.type->getName()); + continue; + } + + const auto & patch_column = patch_col_with_type.column; + IColumn::Patch::Source source = { .column = *patch_column, @@ -383,7 +440,7 @@ void applyPatchesToBlockCombined( continue; auto & result_versions = addDataVersionForColumn(versions_block, result_column.name, result_block.rows(), source_data_version); - auto multi_patch = builder.createPatchForColumn(result_column.name, result_versions); + auto multi_patch = builder.createPatchForColumn(result_column.name, result_versions, result_column.type); result_column.column = removeSpecialRepresentations(result_column.column); if (canApplyPatchInplace(*result_column.column)) diff --git a/src/Storages/MergeTree/PatchParts/tests/gtest_apply_patches_type_mismatch.cpp b/src/Storages/MergeTree/PatchParts/tests/gtest_apply_patches_type_mismatch.cpp new file mode 100644 index 000000000000..867f2d38448d --- /dev/null +++ b/src/Storages/MergeTree/PatchParts/tests/gtest_apply_patches_type_mismatch.cpp @@ -0,0 +1,178 @@ +#include + +#include +#include +#include +#include +#include +#include + +using namespace DB; + +/// Regression test for a SIGSEGV that occurred in production when +/// applyPatchesToBlock was called with a patch part whose column type +/// had diverged from the result block type due to schema evolution. +/// +/// Without the type->equals() guard the patch was applied blindly, +/// causing insertFrom to reinterpret ColumnString memory as +/// ColumnVector (or vice-versa) → SIGSEGV at a garbage pointer. +/// +/// With the guard the mismatched patch is skipped and the result +/// block retains its original data for that column. +TEST(ApplyPatches, TypeMismatchSkipsPatch) +{ + /// ---------- result block (current schema: String column) ---------- + auto result_col = ColumnString::create(); + result_col->insert("aaa"); + result_col->insert("bbb"); + result_col->insert("ccc"); + + auto version_col = ColumnUInt64::create(); + version_col->insert(1u); + version_col->insert(1u); + version_col->insert(1u); + + Block result_block; + result_block.insert({result_col->getPtr(), std::make_shared(), "value"}); + result_block.insert({version_col->getPtr(), std::make_shared(), PartDataVersionColumn::name}); + + /// ---------- patch block (old schema: UInt64 column) ---------- + auto patch_value_col = ColumnUInt64::create(); + patch_value_col->insert(42u); + + auto patch_version_col = ColumnUInt64::create(); + patch_version_col->insert(2u); /// higher version → patch wins if applied + + Block patch_block; + patch_block.insert({patch_value_col->getPtr(), std::make_shared(), "value"}); + patch_block.insert({patch_version_col->getPtr(), std::make_shared(), PartDataVersionColumn::name}); + + /// ---------- build PatchToApply ---------- + auto patch = std::make_shared(); + patch->patch_blocks.push_back(std::move(patch_block)); + patch->result_row_indices.push_back(1); /// update row 1 + patch->patch_row_indices.push_back(0); /// from patch row 0 + + PatchesToApply patches{std::move(patch)}; + Block versions_block; + + /// ---------- apply ---------- + applyPatchesToBlock(result_block, versions_block, patches, {"value"}, /*source_data_version=*/ 1); + + /// ---------- verify ---------- + /// The patch has UInt64 type while the result has String type. + /// The guard must skip the patch, so row 1 keeps its original value. + const auto & col = result_block.getByName("value").column; + ASSERT_EQ(col->size(), 3u); + EXPECT_EQ((*col)[0].safeGet(), "aaa"); + EXPECT_EQ((*col)[1].safeGet(), "bbb"); /// NOT "42" or garbled + EXPECT_EQ((*col)[2].safeGet(), "ccc"); +} + +/// Sanity check: when types match the patch IS applied normally. +TEST(ApplyPatches, SameTypeAppliesPatch) +{ + auto result_col = ColumnUInt64::create(); + result_col->insert(100u); + result_col->insert(200u); + result_col->insert(300u); + + auto version_col = ColumnUInt64::create(); + version_col->insert(1u); + version_col->insert(1u); + version_col->insert(1u); + + Block result_block; + result_block.insert({result_col->getPtr(), std::make_shared(), "value"}); + result_block.insert({version_col->getPtr(), std::make_shared(), PartDataVersionColumn::name}); + + auto patch_value_col = ColumnUInt64::create(); + patch_value_col->insert(999u); + + auto patch_version_col = ColumnUInt64::create(); + patch_version_col->insert(2u); + + Block patch_block; + patch_block.insert({patch_value_col->getPtr(), std::make_shared(), "value"}); + patch_block.insert({patch_version_col->getPtr(), std::make_shared(), PartDataVersionColumn::name}); + + auto patch = std::make_shared(); + patch->patch_blocks.push_back(std::move(patch_block)); + patch->result_row_indices.push_back(1); + patch->patch_row_indices.push_back(0); + + PatchesToApply patches{std::move(patch)}; + Block versions_block; + + applyPatchesToBlock(result_block, versions_block, patches, {"value"}, /*source_data_version=*/ 1); + + const auto & col = result_block.getByName("value").column; + ASSERT_EQ(col->size(), 3u); + EXPECT_EQ((*col)[0].safeGet(), 100u); + EXPECT_EQ((*col)[1].safeGet(), 999u); /// patch applied + EXPECT_EQ((*col)[2].safeGet(), 300u); +} + +/// When multiple patch sources exist and only some have mismatched types, +/// the compatible sources must still be applied (per-source filtering). +TEST(ApplyPatches, MixedTypeSourcesAppliesCompatibleOnes) +{ + /// Result block: String column with 4 rows. + auto result_col = ColumnString::create(); + result_col->insert("a"); + result_col->insert("b"); + result_col->insert("c"); + result_col->insert("d"); + + auto version_col = ColumnUInt64::create(); + version_col->insert(1u); + version_col->insert(1u); + version_col->insert(1u); + version_col->insert(1u); + + Block result_block; + result_block.insert({result_col->getPtr(), std::make_shared(), "value"}); + result_block.insert({version_col->getPtr(), std::make_shared(), PartDataVersionColumn::name}); + + /// Patch 1: INCOMPATIBLE — UInt64 type, wants to update row 1. + auto p1_value = ColumnUInt64::create(); + p1_value->insert(42u); + auto p1_version = ColumnUInt64::create(); + p1_version->insert(2u); + + Block p1_block; + p1_block.insert({p1_value->getPtr(), std::make_shared(), "value"}); + p1_block.insert({p1_version->getPtr(), std::make_shared(), PartDataVersionColumn::name}); + + auto patch1 = std::make_shared(); + patch1->patch_blocks.push_back(std::move(p1_block)); + patch1->result_row_indices.push_back(1); + patch1->patch_row_indices.push_back(0); + + /// Patch 2: COMPATIBLE — String type, wants to update row 2. + auto p2_value = ColumnString::create(); + p2_value->insert("updated"); + auto p2_version = ColumnUInt64::create(); + p2_version->insert(3u); + + Block p2_block; + p2_block.insert({p2_value->getPtr(), std::make_shared(), "value"}); + p2_block.insert({p2_version->getPtr(), std::make_shared(), PartDataVersionColumn::name}); + + auto patch2 = std::make_shared(); + patch2->patch_blocks.push_back(std::move(p2_block)); + patch2->result_row_indices.push_back(2); + patch2->patch_row_indices.push_back(0); + + PatchesToApply patches{std::move(patch1), std::move(patch2)}; + Block versions_block; + + applyPatchesToBlock(result_block, versions_block, patches, {"value"}, /*source_data_version=*/ 1); + + const auto & col = result_block.getByName("value").column; + ASSERT_EQ(col->size(), 4u); + EXPECT_EQ((*col)[0].safeGet(), "a"); + EXPECT_EQ((*col)[1].safeGet(), "b"); /// incompatible patch skipped + EXPECT_EQ((*col)[2].safeGet(), "updated"); /// compatible patch applied + EXPECT_EQ((*col)[3].safeGet(), "d"); +} From 7307e2f4a36bfce18d0c33f3401fbe4f9999add7 Mon Sep 17 00:00:00 2001 From: robot-clickhouse Date: Thu, 4 Jun 2026 11:19:27 +0000 Subject: [PATCH 007/146] Backport #106287 to 26.3: Bump mariadb-connector-c to 3.1.29 --- contrib/mariadb-connector-c | 2 +- contrib/mariadb-connector-c-cmake/CMakeLists.txt | 10 +++++++++- tests/integration/helpers/port_forward.py | 4 ++++ 3 files changed, 14 insertions(+), 2 deletions(-) diff --git a/contrib/mariadb-connector-c b/contrib/mariadb-connector-c index d0a788c5b9fc..95d6264bb43d 160000 --- a/contrib/mariadb-connector-c +++ b/contrib/mariadb-connector-c @@ -1 +1 @@ -Subproject commit d0a788c5b9fcaca2368d9233770d3ca91ea79f88 +Subproject commit 95d6264bb43d31f825cf2f85ce1928557457b62d diff --git a/contrib/mariadb-connector-c-cmake/CMakeLists.txt b/contrib/mariadb-connector-c-cmake/CMakeLists.txt index 58f8a7614cff..93c8469b41b5 100644 --- a/contrib/mariadb-connector-c-cmake/CMakeLists.txt +++ b/contrib/mariadb-connector-c-cmake/CMakeLists.txt @@ -43,6 +43,14 @@ MATH(EXPR MARIADB_VERSION_ID "${MARIADB_CLIENT_VERSION_MAJOR} * 10000 + ${MARIADB_CLIENT_VERSION_MINOR} * 100 + ${MARIADB_CLIENT_VERSION_PATCH}") +set(CPACK_PACKAGE_VERSION_MAJOR 3) +set(CPACK_PACKAGE_VERSION_MINOR 1) +set(CPACK_PACKAGE_VERSION_PATCH 29) +set(CPACK_PACKAGE_VERSION "${CPACK_PACKAGE_VERSION_MAJOR}.${CPACK_PACKAGE_VERSION_MINOR}.${CPACK_PACKAGE_VERSION_PATCH}") +MATH(EXPR MARIADB_PACKAGE_VERSION_ID "${CPACK_PACKAGE_VERSION_MAJOR} * 10000 + + ${CPACK_PACKAGE_VERSION_MINOR} * 100 + + ${CPACK_PACKAGE_VERSION_PATCH}") + IF (NOT MARIADB_PORT) set(MARIADB_PORT 3306) ENDIF () @@ -207,7 +215,7 @@ set(LIBMARIADB_SOURCES ${LIBMARIADB_SOURCES} ${CC_SOURCE_DIR}/plugins/auth/my_auth.c ${CC_SOURCE_DIR}/libmariadb/ma_array.c ${CC_SOURCE_DIR}/libmariadb/ma_charset.c -${CC_SOURCE_DIR}/libmariadb/ma_hash.c +${CC_SOURCE_DIR}/libmariadb/ma_hashtbl.c ${CC_SOURCE_DIR}/libmariadb/ma_net.c ${CC_SOURCE_DIR}/libmariadb/mariadb_charset.c ${CC_SOURCE_DIR}/libmariadb/ma_time.c diff --git a/tests/integration/helpers/port_forward.py b/tests/integration/helpers/port_forward.py index 230f434e04b2..9f29688d4fd4 100644 --- a/tests/integration/helpers/port_forward.py +++ b/tests/integration/helpers/port_forward.py @@ -70,6 +70,10 @@ def termination(connection: socket.socket, idx: int): def start(self, address, listen_port=0): self._address = address self._sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + # Allow rebinding to the same fixed port immediately after stop(): the + # previously accepted connection lingers in TIME_WAIT on this port, so + # without SO_REUSEADDR bind() fails with "Address already in use". + self._sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) self._sock.bind(("", listen_port)) self._sock.listen() self._sock.settimeout(1) From b8015dabc74e304876740760793c6029f86fb908 Mon Sep 17 00:00:00 2001 From: robot-clickhouse Date: Thu, 4 Jun 2026 15:22:57 +0000 Subject: [PATCH 008/146] Backport #105464 to 26.3: Fix SIGSEGV when reading Dynamic subcolumns from compressed Memory table after ALTER --- src/Interpreters/getColumnFromBlock.cpp | 2 +- .../04260_dynamic_subcolumn_compressed_memory.reference | 5 +++++ .../04260_dynamic_subcolumn_compressed_memory.sql | 9 +++++++++ 3 files changed, 15 insertions(+), 1 deletion(-) create mode 100644 tests/queries/0_stateless/04260_dynamic_subcolumn_compressed_memory.reference create mode 100644 tests/queries/0_stateless/04260_dynamic_subcolumn_compressed_memory.sql diff --git a/src/Interpreters/getColumnFromBlock.cpp b/src/Interpreters/getColumnFromBlock.cpp index 9b8cc218aba0..4f301329b559 100644 --- a/src/Interpreters/getColumnFromBlock.cpp +++ b/src/Interpreters/getColumnFromBlock.cpp @@ -50,7 +50,7 @@ ColumnPtr tryGetSubcolumnFromBlock(const Block & block, const DataTypePtr & requ /// extract the subcolumn, because the data of dynamic subcolumn can change after cast. if ((elem->type->hasDynamicStructure() || requested_column_type->hasDynamicStructure()) && !elem->type->equals(*requested_column_type)) { - auto cast_column = castColumn({elem->column, elem->type, ""}, requested_column_type); + auto cast_column = castColumn({elem->column->decompress(), elem->type, ""}, requested_column_type); auto elem_column = requested_column_type->tryGetSubcolumn(subcolumn_name, cast_column); auto elem_type = requested_column_type->tryGetSubcolumnType(subcolumn_name); diff --git a/tests/queries/0_stateless/04260_dynamic_subcolumn_compressed_memory.reference b/tests/queries/0_stateless/04260_dynamic_subcolumn_compressed_memory.reference new file mode 100644 index 000000000000..9dfcf39f5a78 --- /dev/null +++ b/tests/queries/0_stateless/04260_dynamic_subcolumn_compressed_memory.reference @@ -0,0 +1,5 @@ +0 +1 +2 +3 +4 diff --git a/tests/queries/0_stateless/04260_dynamic_subcolumn_compressed_memory.sql b/tests/queries/0_stateless/04260_dynamic_subcolumn_compressed_memory.sql new file mode 100644 index 000000000000..6010c771dc12 --- /dev/null +++ b/tests/queries/0_stateless/04260_dynamic_subcolumn_compressed_memory.sql @@ -0,0 +1,9 @@ +-- Tags: memory-engine +SET allow_experimental_dynamic_type=1; + +DROP TABLE IF EXISTS test_dynamic_compressed; +CREATE TABLE test_dynamic_compressed (d Dynamic) ENGINE=Memory SETTINGS compress=1; +INSERT INTO test_dynamic_compressed SELECT number FROM numbers(5); +ALTER TABLE test_dynamic_compressed MODIFY COLUMN d Dynamic(max_types=0); +SELECT d.UInt64 FROM test_dynamic_compressed ORDER BY d.UInt64; +DROP TABLE test_dynamic_compressed; From 3a44737dc1aee7061aa32a0835550b16181a577e Mon Sep 17 00:00:00 2001 From: robot-clickhouse Date: Thu, 4 Jun 2026 22:45:32 +0000 Subject: [PATCH 009/146] Backport #106476 to 26.3: Fix crash due to OOB when deserializing corrupted Avro data --- .../Formats/Impl/AvroRowInputFormat.cpp | 8 ++++++++ .../04036_avro_invalid_enum_value.reference | 2 ++ .../0_stateless/04036_avro_invalid_enum_value.sh | 6 ++++++ .../data_avro/enum_index_out_of_range.avro | Bin 0 -> 157 bytes 4 files changed, 16 insertions(+) create mode 100644 tests/queries/0_stateless/data_avro/enum_index_out_of_range.avro diff --git a/src/Processors/Formats/Impl/AvroRowInputFormat.cpp b/src/Processors/Formats/Impl/AvroRowInputFormat.cpp index d545b15c9742..a0e7b1a23b26 100644 --- a/src/Processors/Formats/Impl/AvroRowInputFormat.cpp +++ b/src/Processors/Formats/Impl/AvroRowInputFormat.cpp @@ -569,6 +569,10 @@ AvroDeserializer::DeserializeFn AvroDeserializer::createDeserializeFn(const avro return [symbols](IColumn & column, avro::Decoder & decoder) { size_t enum_index = decoder.decodeEnum(); + if (enum_index >= symbols.size()) + { + throw Exception(ErrorCodes::INCORRECT_DATA, "Avro enum index {} is out of range, the schema has {} symbols", enum_index, symbols.size()); + } const auto & enum_symbol = symbols[enum_index]; column.insertData(enum_symbol.c_str(), enum_symbol.length()); return true; @@ -585,6 +589,10 @@ AvroDeserializer::DeserializeFn AvroDeserializer::createDeserializeFn(const avro return [symbol_mapping](IColumn & column, avro::Decoder & decoder) { size_t enum_index = decoder.decodeEnum(); + if (enum_index >= symbol_mapping.size()) + { + throw Exception(ErrorCodes::INCORRECT_DATA, "Avro enum index {} is out of range, the schema has {} symbols", enum_index, symbol_mapping.size()); + } column.insert(symbol_mapping[enum_index]); return true; }; diff --git a/tests/queries/0_stateless/04036_avro_invalid_enum_value.reference b/tests/queries/0_stateless/04036_avro_invalid_enum_value.reference index f060a3bdfeef..8c3404ff50dd 100644 --- a/tests/queries/0_stateless/04036_avro_invalid_enum_value.reference +++ b/tests/queries/0_stateless/04036_avro_invalid_enum_value.reference @@ -1,2 +1,4 @@ BAD_ARGUMENTS BAD_ARGUMENTS +INCORRECT_DATA +INCORRECT_DATA diff --git a/tests/queries/0_stateless/04036_avro_invalid_enum_value.sh b/tests/queries/0_stateless/04036_avro_invalid_enum_value.sh index cb93f4be8c48..81485553e20f 100755 --- a/tests/queries/0_stateless/04036_avro_invalid_enum_value.sh +++ b/tests/queries/0_stateless/04036_avro_invalid_enum_value.sh @@ -4,6 +4,7 @@ # Regression test for https://github.com/ClickHouse/ClickHouse/issues/99326 # Avro output should throw BAD_ARGUMENTS instead of logical error # when an Enum column contains a value not in the enum definition. +# Avro input should likewise reject an out-of-range enum index with INCORRECT_DATA. CURDIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd) # shellcheck source=../shell_config.sh @@ -39,3 +40,8 @@ grep -q 'BAD_ARGUMENTS' "${CLICKHOUSE_TMP}/avro_enum16_err.txt" && echo "BAD_ARG ${CLICKHOUSE_CLIENT} -q "DROP TABLE enum16_wide" ${CLICKHOUSE_CLIENT} -q "DROP TABLE enum16_narrow" + +# enum_index_out_of_range.avro declares a single enum symbol but encodes index 1. +DATA_DIR=$CURDIR/data_avro +cat "$DATA_DIR"/enum_index_out_of_range.avro | ${CLICKHOUSE_LOCAL} --input-format Avro -S "e Enum8('x' = 0)" -q 'select * from table' 2>&1 | grep -o 'INCORRECT_DATA' | head -n 1 +cat "$DATA_DIR"/enum_index_out_of_range.avro | ${CLICKHOUSE_LOCAL} --input-format Avro -S 'e String' -q 'select * from table' 2>&1 | grep -o 'INCORRECT_DATA' | head -n 1 diff --git a/tests/queries/0_stateless/data_avro/enum_index_out_of_range.avro b/tests/queries/0_stateless/data_avro/enum_index_out_of_range.avro new file mode 100644 index 0000000000000000000000000000000000000000..658b45c635c7d0c338878e8dee890e28d8405181 GIT binary patch literal 157 zcmeZI%3@>^ODrqO*DFrWNX<<=!&t3UQdy9yWTjM;nw(#hqNJmgmzWFUmjIb*nW;G` y#Y$Gu)iBXipeRHGTw`ipX)Z#WGmu$anVXcK1Jb8dp%hzN8(YhO4w#sj(1ie=EhdZr literal 0 HcmV?d00001 From fa597e4938bba73ea704022327d73710c841ee09 Mon Sep 17 00:00:00 2001 From: robot-clickhouse Date: Fri, 5 Jun 2026 11:24:27 +0000 Subject: [PATCH 010/146] Backport #105949 to 26.3: Fix server abort when cancelling postgresql table function queries --- src/Processors/Sources/PostgreSQLSource.cpp | 10 +- .../test_postgresql_kill_query/test.py | 111 ++++++++++++++++++ 2 files changed, 120 insertions(+), 1 deletion(-) diff --git a/src/Processors/Sources/PostgreSQLSource.cpp b/src/Processors/Sources/PostgreSQLSource.cpp index 4e100b1fc212..0cb16c8e6a14 100644 --- a/src/Processors/Sources/PostgreSQLSource.cpp +++ b/src/Processors/Sources/PostgreSQLSource.cpp @@ -200,7 +200,15 @@ void PostgreSQLSource::onCancel() noexcept /// The code is executed only if onStart() was not finished mainly due to freezing on pqxx::from_query if (!started.load() && tx && tx->conn().is_open()) { - tx->conn().cancel_query(); + try + { + tx->conn().cancel_query(); + } + catch (...) + { + tryLogCurrentException(__PRETTY_FUNCTION__); + } + if (connection_holder) connection_holder->setBroken(); } diff --git a/tests/integration/test_postgresql_kill_query/test.py b/tests/integration/test_postgresql_kill_query/test.py index c7a875d2551a..a590408d352b 100644 --- a/tests/integration/test_postgresql_kill_query/test.py +++ b/tests/integration/test_postgresql_kill_query/test.py @@ -1,11 +1,14 @@ import psycopg2 import pytest +import socket import uuid import threading import time from helpers.cluster import ClickHouseCluster +from helpers.port_forward import PortForward from helpers.postgres_utility import get_postgres_conn +from helpers.test_tools import assert_eq_with_retry cluster = ClickHouseCluster(__file__) node1 = cluster.add_instance( @@ -65,6 +68,33 @@ def setup_infinite_query(started_cluster): conn.close() +@pytest.fixture(scope="module") +def setup_sleepy_view(started_cluster): + conn = get_postgres_conn( + started_cluster.postgres_ip, started_cluster.postgres_port, database=True + ) + cursor = conn.cursor() + + cursor.execute( + """CREATE OR REPLACE FUNCTION sleepy_start() +RETURNS SETOF integer AS $$ +BEGIN + PERFORM pg_sleep(600); + RETURN NEXT 1; +END; +$$ LANGUAGE plpgsql;""" + ) + cursor.execute( + """CREATE OR REPLACE VIEW sleepy_view AS +SELECT sleepy_start() AS id;""" + ) + + yield + + cursor.close() + conn.close() + + @pytest.fixture(scope="module") def setup_big_data_table(started_cluster): # Connect to postgres_database database @@ -98,6 +128,87 @@ def setup_big_data_table(started_cluster): conn.close() +def wait_for_port_forward_connection(port_forward): + for _ in range(50): + with port_forward._clients_lock: + if port_forward._clients: + return + time.sleep(0.1) + + raise AssertionError("No active PostgreSQL proxy connection") + + +def wait_for_proxy_listener_closed(host, port): + for _ in range(50): + try: + with socket.create_connection((host, port), timeout=0.1): + pass + except OSError: + return + time.sleep(0.1) + + raise AssertionError("PostgreSQL proxy listener is still accepting connections") + + +def test_kill_query_when_postgresql_cancel_connection_fails( + started_cluster, setup_sleepy_view +): + port_forward = PortForward() + port = port_forward.start( + (started_cluster.postgres_ip, started_cluster.postgres_port) + ) + proxy_host = socket.gethostbyname(socket.gethostname()) + query_id = str(uuid.uuid4()) + query_errors = [] + query_exceptions = [] + + def execute_query(): + try: + _, error = node1.query_and_get_answer_with_error( + f"""SELECT count() FROM postgresql( + '{proxy_host}:{port}', + 'postgres_database', + 'sleepy_view', + 'postgres', + 'ClickHouse_PostgreSQL_P@ssw0rd')""", + query_id=query_id, + timeout=60, + ) + query_errors.append(error) + except Exception as ex: + query_exceptions.append(ex) + + query_thread = threading.Thread(target=execute_query) + query_thread.start() + + try: + assert_eq_with_retry( + node1, + f"SELECT count() FROM system.processes WHERE query_id='{query_id}'", + "1", + retry_count=60, + sleep_time=0.5, + ) + wait_for_port_forward_connection(port_forward) + + # Keep the active `postgresql` data connection open, but refuse the separate + # `pqxx::connection::cancel_query` connection opened by `KILL QUERY`. + port_forward.stop() + wait_for_proxy_listener_closed(proxy_host, port) + + node1.query(f"KILL QUERY WHERE query_id='{query_id}'") + node1.wait_for_log_line("PQcancel\\(\\) -- connect\\(\\) failed", timeout=30) + + assert node1.query("SELECT 1").strip() == "1" + finally: + port_forward.stop(force=True) + + query_thread.join(timeout=30) + assert not query_thread.is_alive() + assert not query_exceptions + assert query_errors + + def test_kill_infinite_query(setup_infinite_query): cursor, postgres_host_with__port = setup_infinite_query query_id = str(uuid.uuid4()) From 98f97584d43e4be4c97f6dc2ff562d0cd278448e Mon Sep 17 00:00:00 2001 From: robot-clickhouse Date: Mon, 8 Jun 2026 14:23:44 +0000 Subject: [PATCH 011/146] Update autogenerated version to 26.3.13.31 and contributors --- cmake/autogenerated_versions.txt | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/cmake/autogenerated_versions.txt b/cmake/autogenerated_versions.txt index a201b26ccff4..87263c2b3641 100644 --- a/cmake/autogenerated_versions.txt +++ b/cmake/autogenerated_versions.txt @@ -2,11 +2,11 @@ # NOTE: VERSION_REVISION has nothing common with DBMS_TCP_PROTOCOL_VERSION, # only DBMS_TCP_PROTOCOL_VERSION should be incremented on protocol changes. -SET(VERSION_REVISION 54520) +SET(VERSION_REVISION 54521) SET(VERSION_MAJOR 26) SET(VERSION_MINOR 3) -SET(VERSION_PATCH 13) -SET(VERSION_GITHASH d23c7536b980c34b39c850b08ef23c509f06aaaa) -SET(VERSION_DESCRIBE v26.3.13.1-lts) -SET(VERSION_STRING 26.3.13.1) +SET(VERSION_PATCH 14) +SET(VERSION_GITHASH 27ae4e9fe0e3f97f012b3be293caf42a60d08747) +SET(VERSION_DESCRIBE v26.3.14.1-lts) +SET(VERSION_STRING 26.3.14.1) # end of autochange From 2cf5fd4edd390d5fbcd67545c8342dc3762ba29e Mon Sep 17 00:00:00 2001 From: robot-clickhouse Date: Mon, 8 Jun 2026 19:12:22 +0000 Subject: [PATCH 012/146] Backport #105666 to 26.3: Fix OOB read in flattened Dynamic column Native deserialization --- .../Serializations/SerializationDynamic.cpp | 7 +++++++ ...c_flattened_underfilled_type_data.reference | 1 + ..._dynamic_flattened_underfilled_type_data.sh | 17 +++++++++++++++++ .../dynamic_flattened_underfilled.native | Bin 0 -> 51 bytes 4 files changed, 25 insertions(+) create mode 100644 tests/queries/0_stateless/04267_dynamic_flattened_underfilled_type_data.reference create mode 100755 tests/queries/0_stateless/04267_dynamic_flattened_underfilled_type_data.sh create mode 100644 tests/queries/0_stateless/data_native/dynamic_flattened_underfilled.native diff --git a/src/DataTypes/Serializations/SerializationDynamic.cpp b/src/DataTypes/Serializations/SerializationDynamic.cpp index 10da12f245b8..1c1a3117a1a4 100644 --- a/src/DataTypes/Serializations/SerializationDynamic.cpp +++ b/src/DataTypes/Serializations/SerializationDynamic.cpp @@ -603,6 +603,13 @@ void SerializationDynamic::deserializeBinaryBulkWithMultipleStreams( { ColumnPtr type_column = flattened_column.types[i]->createColumn(); flattened_column.types[i]->getDefaultSerialization()->deserializeBinaryBulkWithMultipleStreams(type_column, 0, flattened_limits[i], settings, dynamic_state->flattened_states[i], cache); + if (type_column->size() != flattened_limits[i]) + throw Exception( + ErrorCodes::INCORRECT_DATA, + "Mismatch in flattened Dynamic column: indexes declare {} rows for type {}, but only {} rows were deserialized", + flattened_limits[i], + flattened_column.types[i]->getName(), + type_column->size()); flattened_column.columns.emplace_back(std::move(type_column)); } diff --git a/tests/queries/0_stateless/04267_dynamic_flattened_underfilled_type_data.reference b/tests/queries/0_stateless/04267_dynamic_flattened_underfilled_type_data.reference new file mode 100644 index 000000000000..1e6d4f95a01a --- /dev/null +++ b/tests/queries/0_stateless/04267_dynamic_flattened_underfilled_type_data.reference @@ -0,0 +1 @@ +Mismatch in flattened Dynamic column: indexes declare 2 rows for type FixedString(8), but only 0 rows were deserialized diff --git a/tests/queries/0_stateless/04267_dynamic_flattened_underfilled_type_data.sh b/tests/queries/0_stateless/04267_dynamic_flattened_underfilled_type_data.sh new file mode 100755 index 000000000000..e09d325ce352 --- /dev/null +++ b/tests/queries/0_stateless/04267_dynamic_flattened_underfilled_type_data.sh @@ -0,0 +1,17 @@ +#!/usr/bin/env bash +# Tags: no-fasttest + +CUR_DIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd) +# shellcheck source=../shell_config.sh +. "$CUR_DIR"/../shell_config.sh + +# Test that reading a Native file with a flattened Dynamic column where the +# type data stream has fewer rows than declared by the indexes column produces +# an error instead of reading uninitialized memory. +# +# The file data_native/dynamic_flattened_underfilled.native is a truncated +# Native file with flattened Dynamic serialization for Dynamic(max_types=1) +# declaring 2 rows of FixedString(8) via the indexes column, but providing +# zero bytes of actual FixedString data. + +$CLICKHOUSE_LOCAL --table test --input-format Native -q "SELECT * FROM test" < "${CUR_DIR}/data_native/dynamic_flattened_underfilled.native" 2>&1 | grep -o 'Mismatch in flattened Dynamic column[^.]*' diff --git a/tests/queries/0_stateless/data_native/dynamic_flattened_underfilled.native b/tests/queries/0_stateless/data_native/dynamic_flattened_underfilled.native new file mode 100644 index 0000000000000000000000000000000000000000..20586739707c6e4282c75ed22d33115e6c36f33f GIT binary patch literal 51 zcmZQ%VoVWnsmx2v%}mzFO{|D7sVqn>wl&mbW`F=jKDW$@)Rf?oqRhN>4GT>M1^`;` B4BY?# literal 0 HcmV?d00001 From ca4ba0eb978552437fd5ec2bfff3003ceab0ceb9 Mon Sep 17 00:00:00 2001 From: robot-clickhouse Date: Mon, 8 Jun 2026 19:15:14 +0000 Subject: [PATCH 013/146] Backport #105582 to 26.3: Fix crash in Values format when tuples have different sizes --- .../Formats/Impl/ConstantExpressionTemplate.cpp | 3 +++ ...04267_values_template_tuple_size_mismatch.reference | 4 ++++ .../04267_values_template_tuple_size_mismatch.sql | 10 ++++++++++ 3 files changed, 17 insertions(+) create mode 100644 tests/queries/0_stateless/04267_values_template_tuple_size_mismatch.reference create mode 100644 tests/queries/0_stateless/04267_values_template_tuple_size_mismatch.sql diff --git a/src/Processors/Formats/Impl/ConstantExpressionTemplate.cpp b/src/Processors/Formats/Impl/ConstantExpressionTemplate.cpp index 4777b8e68a5a..38a2dd78c94c 100644 --- a/src/Processors/Formats/Impl/ConstantExpressionTemplate.cpp +++ b/src/Processors/Formats/Impl/ConstantExpressionTemplate.cpp @@ -667,6 +667,9 @@ bool ConstantExpressionTemplate::parseLiteralAndAssertType( nested_types = map_type->getKeyValueTypes(); } + if (nested_types.size() != type_info.nested_types.size()) + return false; + for (size_t i = 0; i < nested_types.size(); ++i) { const auto & [nested_field_type, is_nullable] = type_info.nested_types[i]; diff --git a/tests/queries/0_stateless/04267_values_template_tuple_size_mismatch.reference b/tests/queries/0_stateless/04267_values_template_tuple_size_mismatch.reference new file mode 100644 index 000000000000..a28d0ccb88ae --- /dev/null +++ b/tests/queries/0_stateless/04267_values_template_tuple_size_mismatch.reference @@ -0,0 +1,4 @@ +(1,2) +(1,2) +(1,2,3) +(1,2,3) diff --git a/tests/queries/0_stateless/04267_values_template_tuple_size_mismatch.sql b/tests/queries/0_stateless/04267_values_template_tuple_size_mismatch.sql new file mode 100644 index 000000000000..5fee3b4c104f --- /dev/null +++ b/tests/queries/0_stateless/04267_values_template_tuple_size_mismatch.sql @@ -0,0 +1,10 @@ +-- Regression test for https://github.com/ClickHouse/ClickHouse/issues/101727 +-- Tuples of different sizes in VALUES should not cause OOB access in template parser. +DROP TABLE IF EXISTS t_tuple_size_mismatch; +CREATE TABLE t_tuple_size_mismatch (c0 String) ENGINE = Memory; + +INSERT INTO TABLE t_tuple_size_mismatch (c0) VALUES ((1, 2)), ((1, 2, 3)); +INSERT INTO TABLE t_tuple_size_mismatch (c0) VALUES ((1, 2, 3)), ((1, 2)); + +SELECT * FROM t_tuple_size_mismatch ORDER BY c0; +DROP TABLE t_tuple_size_mismatch; From f29906fa92c6928fab0c8e49fa2b767da1cf9a92 Mon Sep 17 00:00:00 2001 From: robot-clickhouse Date: Mon, 8 Jun 2026 19:18:06 +0000 Subject: [PATCH 014/146] Backport #105573 to 26.3: Materialize subcolumns in `executePartitionByExpression` before executing expression --- src/Storages/MergeTree/MergeTreePartition.cpp | 8 ++++++ ...rtition_by_function_of_subcolumn.reference | 8 ++++++ ...266_partition_by_function_of_subcolumn.sql | 27 +++++++++++++++++++ 3 files changed, 43 insertions(+) create mode 100644 tests/queries/0_stateless/04266_partition_by_function_of_subcolumn.reference create mode 100644 tests/queries/0_stateless/04266_partition_by_function_of_subcolumn.sql diff --git a/src/Storages/MergeTree/MergeTreePartition.cpp b/src/Storages/MergeTree/MergeTreePartition.cpp index ea16a5ff6602..dd984373b98a 100644 --- a/src/Storages/MergeTree/MergeTreePartition.cpp +++ b/src/Storages/MergeTree/MergeTreePartition.cpp @@ -470,6 +470,14 @@ void MergeTreePartition::create(const StorageMetadataPtr & metadata_snapshot, Bl NamesAndTypesList MergeTreePartition::executePartitionByExpression(const StorageMetadataPtr & metadata_snapshot, Block & block, ContextPtr context) { auto adjusted_partition_key = adjustPartitionKey(metadata_snapshot, context); + /// Materialize subcolumns that the partition key expression needs. + /// The block may contain only parent columns (e.g. a Tuple or JSON column), + /// while the expression requires individual subcolumns as separate inputs. + for (const auto & required_column : adjusted_partition_key.expression->getRequiredColumns()) + { + if (!block.has(required_column)) + block.insert(block.getSubcolumnByName(required_column)); + } adjusted_partition_key.expression->execute(block); return adjusted_partition_key.sample_block.getNamesAndTypesList(); } diff --git a/tests/queries/0_stateless/04266_partition_by_function_of_subcolumn.reference b/tests/queries/0_stateless/04266_partition_by_function_of_subcolumn.reference new file mode 100644 index 000000000000..a2f4e6e1439b --- /dev/null +++ b/tests/queries/0_stateless/04266_partition_by_function_of_subcolumn.reference @@ -0,0 +1,8 @@ +1 2 3 10 +3 4 7 20 +1 2 3 30 +2020-03-15 2020 +2021-07-20 2021 +[] 0 1 +[1] 1 2 +[NULL,1] 2 3 diff --git a/tests/queries/0_stateless/04266_partition_by_function_of_subcolumn.sql b/tests/queries/0_stateless/04266_partition_by_function_of_subcolumn.sql new file mode 100644 index 000000000000..f2a45a089f65 --- /dev/null +++ b/tests/queries/0_stateless/04266_partition_by_function_of_subcolumn.sql @@ -0,0 +1,27 @@ +-- Tags: no-random-merge-tree-settings + +-- Test that PARTITION BY with a function applied to a subcolumn works correctly. +-- Previously, executePartitionByExpression did not materialize subcolumns +-- before executing the expression, causing NOT_FOUND_COLUMN_IN_BLOCK. + +-- Case 1: Tuple subcolumn with arithmetic function in PARTITION BY +DROP TABLE IF EXISTS t_subcol_func_part; +CREATE TABLE t_subcol_func_part (t Tuple(a UInt32, b UInt32), val Int32) ENGINE = MergeTree ORDER BY val PARTITION BY (t.a + t.b); +INSERT INTO t_subcol_func_part VALUES ((1, 2), 10), ((3, 4), 20), ((1, 2), 30); +SELECT t.a, t.b, t.a + t.b AS part_key, val FROM t_subcol_func_part ORDER BY val; +DROP TABLE t_subcol_func_part; + +-- Case 2: JSON subcolumn with toYear function in PARTITION BY +DROP TABLE IF EXISTS t_json_func_part; +CREATE TABLE t_json_func_part (json JSON(d Date)) ENGINE = MergeTree ORDER BY tuple() PARTITION BY toYear(json.d); +INSERT INTO t_json_func_part SELECT '{"d" : "2020-03-15"}'; +INSERT INTO t_json_func_part SELECT '{"d" : "2021-07-20"}'; +SELECT json.d, toYear(json.d) AS yr FROM t_json_func_part ORDER BY json.d; +DROP TABLE t_json_func_part; + +-- Case 3: Nullable subcolumn with function in PARTITION BY +DROP TABLE IF EXISTS t_nullable_func_part; +CREATE TABLE t_nullable_func_part (c0 Array(Nullable(Int32)), c1 Int32) ENGINE = MergeTree() PARTITION BY length(c0.null) ORDER BY c1; +INSERT INTO t_nullable_func_part VALUES ([], 1), ([1], 2), ([NULL, 1], 3); +SELECT c0, length(c0.null) AS null_len, c1 FROM t_nullable_func_part ORDER BY c1; +DROP TABLE t_nullable_func_part; From cbdc46020cfa21ce6dce3cd666aab855ec8e6330 Mon Sep 17 00:00:00 2001 From: robot-clickhouse Date: Mon, 8 Jun 2026 23:40:59 +0000 Subject: [PATCH 015/146] Backport #106741 to 26.3: Fix heap buffer overflow in decodeHTMLComponent with expanding HTML entities --- src/Functions/CMakeLists.txt | 2 + .../HTMLCharacterReference.generated.cpp | 35156 ++++++++-------- src/Functions/HTMLCharacterReference.gperf | 24 + src/Functions/HTMLCharacterReference.sh | 32 + src/Functions/decodeHTMLComponent.cpp | 11 +- ...tml_component_expanding_entities.reference | 5 + ...code_html_component_expanding_entities.sql | 14 + 7 files changed, 17668 insertions(+), 17576 deletions(-) create mode 100644 tests/queries/0_stateless/04325_decode_html_component_expanding_entities.reference create mode 100644 tests/queries/0_stateless/04325_decode_html_component_expanding_entities.sql diff --git a/src/Functions/CMakeLists.txt b/src/Functions/CMakeLists.txt index 27fd32750472..c7e51a79e04f 100644 --- a/src/Functions/CMakeLists.txt +++ b/src/Functions/CMakeLists.txt @@ -194,6 +194,8 @@ if (USE_GPERF) && clang-format -i HTMLCharacterReference.generated.cpp # for clang-tidy, since string.h is deprecated && sed -i 's/\#include /\#include /g' HTMLCharacterReference.generated.cpp + # the table must be constexpr for the compile-time entity-expansion static_assert in the .gperf + && sed -i 's/static const struct NameAndGlyph wordlist/static constexpr struct NameAndGlyph wordlist/g' HTMLCharacterReference.generated.cpp SOURCES HTMLCharacterReference.gperf WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} ) diff --git a/src/Functions/HTMLCharacterReference.generated.cpp b/src/Functions/HTMLCharacterReference.generated.cpp index dec2f2695de4..4efbf3bfd82d 100644 --- a/src/Functions/HTMLCharacterReference.generated.cpp +++ b/src/Functions/HTMLCharacterReference.generated.cpp @@ -1,5 +1,5 @@ /* C++ code produced by gperf version 3.1 */ -/* Command-line: /usr/bin/gperf -t --output-file=HTMLCharacterReference.generated.cpp HTMLCharacterReference.gperf */ +/* Command-line: gperf -t --output-file=HTMLCharacterReference.generated.cpp HTMLCharacterReference.gperf */ /* Computed positions: -k'1-8,12,14' */ #if !( \ @@ -15,10 +15,10 @@ && ('q' == 113) && ('r' == 114) && ('s' == 115) && ('t' == 116) && ('u' == 117) && ('v' == 118) && ('w' == 119) && ('x' == 120) \ && ('y' == 121) && ('z' == 122) && ('{' == 123) && ('|' == 124) && ('}' == 125) && ('~' == 126)) /* The character set is not based on ISO-646. */ -# error "gperf generated tables don't work with this execution character set. Please report a bug to ." +#error "gperf generated tables don't work with this execution character set. Please report a bug to ." #endif -#line 7 "HTMLCharacterReference.gperf" +#line 8 "HTMLCharacterReference.gperf" #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wimplicit-fallthrough" @@ -27,7 +27,7 @@ #pragma GCC diagnostic ignored "-Wmissing-field-initializers" #pragma GCC diagnostic ignored "-Wshorten-64-to-32" // NOLINTBEGIN(google-runtime-int,hicpp-use-nullptr,modernize-use-nullptr,modernize-macro-to-enum) -#line 16 "HTMLCharacterReference.gperf" +#line 17 "HTMLCharacterReference.gperf" struct NameAndGlyph { const char * name; @@ -73,17791 +73,17780 @@ inline unsigned int HTMLCharacterHash::hash(const char * str, size_t len) switch (hval) { - default: - hval += asso_values[static_cast(str[13])]; + default: hval += asso_values[static_cast(str[13])]; /*FALLTHROUGH*/ case 13: - case 12: - hval += asso_values[static_cast(str[11])]; + case 12: hval += asso_values[static_cast(str[11])]; /*FALLTHROUGH*/ case 11: case 10: case 9: - case 8: - hval += asso_values[static_cast(str[7])]; + case 8: hval += asso_values[static_cast(str[7])]; /*FALLTHROUGH*/ - case 7: - hval += asso_values[static_cast(str[6] + 1)]; + case 7: hval += asso_values[static_cast(str[6] + 1)]; /*FALLTHROUGH*/ - case 6: - hval += asso_values[static_cast(str[5] + 2)]; + case 6: hval += asso_values[static_cast(str[5] + 2)]; /*FALLTHROUGH*/ - case 5: - hval += asso_values[static_cast(str[4] + 3)]; + case 5: hval += asso_values[static_cast(str[4] + 3)]; /*FALLTHROUGH*/ - case 4: - hval += asso_values[static_cast(str[3] + 5)]; + case 4: hval += asso_values[static_cast(str[3] + 5)]; /*FALLTHROUGH*/ - case 3: - hval += asso_values[static_cast(str[2] + 1)]; + case 3: hval += asso_values[static_cast(str[2] + 1)]; /*FALLTHROUGH*/ - case 2: - hval += asso_values[static_cast(str[1])]; + case 2: hval += asso_values[static_cast(str[1])]; /*FALLTHROUGH*/ - case 1: - hval += asso_values[static_cast(str[0] + 13)]; - break; + case 1: hval += asso_values[static_cast(str[0] + 13)]; break; } return hval; } -const struct NameAndGlyph * HTMLCharacterHash::Lookup(const char * str, size_t len) -{ - static const struct NameAndGlyph wordlist[] - = {{""}, - {""}, -#line 1155 "HTMLCharacterReference.gperf" - {"gt", ">"}, +static constexpr struct NameAndGlyph wordlist[] + = {{""}, + {""}, #line 1156 "HTMLCharacterReference.gperf" - {"gt;", ">"}, - {""}, - {""}, - {""}, -#line 1410 "HTMLCharacterReference.gperf" - {"lt", "<"}, + {"gt", ">"}, +#line 1157 "HTMLCharacterReference.gperf" + {"gt;", ">"}, + {""}, + {""}, + {""}, #line 1411 "HTMLCharacterReference.gperf" - {"lt;", "<"}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 704 "HTMLCharacterReference.gperf" - {"ap;", "≈"}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 1398 "HTMLCharacterReference.gperf" - {"lrm;", "‎"}, - {""}, - {""}, - {""}, - {""}, -#line 1062 "HTMLCharacterReference.gperf" - {"eta;", "η"}, -#line 1044 "HTMLCharacterReference.gperf" - {"epsi;", "ε"}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 1046 "HTMLCharacterReference.gperf" - {"epsiv;", "ϵ"}, - {""}, - {""}, - {""}, - {""}, -#line 1148 "HTMLCharacterReference.gperf" - {"gnsim;", "⋧"}, - {""}, - {""}, - {""}, - {""}, -#line 1373 "HTMLCharacterReference.gperf" - {"lnsim;", "⋦"}, - {""}, - {""}, - {""}, -#line 601 "HTMLCharacterReference.gperf" - {"Upsi;", "ϒ"}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 1392 "HTMLCharacterReference.gperf" - {"lpar;", "("}, - {""}, - {""}, - {""}, - {""}, -#line 1041 "HTMLCharacterReference.gperf" - {"epar;", "⋕"}, - {""}, - {""}, - {""}, - {""}, -#line 1038 "HTMLCharacterReference.gperf" - {"ensp;", " "}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 1049 "HTMLCharacterReference.gperf" - {"eqsim;", "≂"}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 1143 "HTMLCharacterReference.gperf" - {"gnap;", "⪊"}, - {""}, - {""}, - {""}, - {""}, -#line 1368 "HTMLCharacterReference.gperf" - {"lnap;", "⪉"}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, + {"lt", "<"}, +#line 1412 "HTMLCharacterReference.gperf" + {"lt;", "<"}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 705 "HTMLCharacterReference.gperf" + {"ap;", "≈"}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 1399 "HTMLCharacterReference.gperf" + {"lrm;", "‎"}, + {""}, + {""}, + {""}, + {""}, +#line 1063 "HTMLCharacterReference.gperf" + {"eta;", "η"}, +#line 1045 "HTMLCharacterReference.gperf" + {"epsi;", "ε"}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 1047 "HTMLCharacterReference.gperf" + {"epsiv;", "ϵ"}, + {""}, + {""}, + {""}, + {""}, +#line 1149 "HTMLCharacterReference.gperf" + {"gnsim;", "⋧"}, + {""}, + {""}, + {""}, + {""}, +#line 1374 "HTMLCharacterReference.gperf" + {"lnsim;", "⋦"}, + {""}, + {""}, + {""}, +#line 602 "HTMLCharacterReference.gperf" + {"Upsi;", "ϒ"}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 1393 "HTMLCharacterReference.gperf" + {"lpar;", "("}, + {""}, + {""}, + {""}, + {""}, +#line 1042 "HTMLCharacterReference.gperf" + {"epar;", "⋕"}, + {""}, + {""}, + {""}, + {""}, +#line 1039 "HTMLCharacterReference.gperf" + {"ensp;", " "}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 1050 "HTMLCharacterReference.gperf" + {"eqsim;", "≂"}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 1144 "HTMLCharacterReference.gperf" + {"gnap;", "⪊"}, + {""}, + {""}, + {""}, + {""}, +#line 1369 "HTMLCharacterReference.gperf" + {"lnap;", "⪉"}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 2199 "HTMLCharacterReference.gperf" + {"wr;", "≀"}, + {""}, + {""}, + {""}, + {""}, #line 2198 "HTMLCharacterReference.gperf" - {"wr;", "≀"}, - {""}, - {""}, - {""}, - {""}, -#line 2197 "HTMLCharacterReference.gperf" - {"wp;", "℘"}, -#line 917 "HTMLCharacterReference.gperf" - {"cup;", "∪"}, -#line 1420 "HTMLCharacterReference.gperf" - {"ltri;", "◃"}, -#line 1394 "HTMLCharacterReference.gperf" - {"lrarr;", "⇆"}, - {""}, - {""}, - {""}, - {""}, -#line 1058 "HTMLCharacterReference.gperf" - {"erarr;", "⥱"}, - {""}, - {""}, -#line 1065 "HTMLCharacterReference.gperf" - {"euml", "ë"}, + {"wp;", "℘"}, +#line 918 "HTMLCharacterReference.gperf" + {"cup;", "∪"}, +#line 1421 "HTMLCharacterReference.gperf" + {"ltri;", "◃"}, +#line 1395 "HTMLCharacterReference.gperf" + {"lrarr;", "⇆"}, + {""}, + {""}, + {""}, + {""}, +#line 1059 "HTMLCharacterReference.gperf" + {"erarr;", "⥱"}, + {""}, + {""}, #line 1066 "HTMLCharacterReference.gperf" - {"euml;", "ë"}, -#line 903 "HTMLCharacterReference.gperf" - {"crarr;", "↵"}, - {""}, - {""}, - {""}, -#line 1179 "HTMLCharacterReference.gperf" - {"hbar;", "ℏ"}, - {""}, - {""}, - {""}, -#line 720 "HTMLCharacterReference.gperf" - {"auml", "ä"}, + {"euml", "ë"}, +#line 1067 "HTMLCharacterReference.gperf" + {"euml;", "ë"}, +#line 904 "HTMLCharacterReference.gperf" + {"crarr;", "↵"}, + {""}, + {""}, + {""}, +#line 1180 "HTMLCharacterReference.gperf" + {"hbar;", "ℏ"}, + {""}, + {""}, + {""}, #line 721 "HTMLCharacterReference.gperf" - {"auml;", "ä"}, -#line 1303 "HTMLCharacterReference.gperf" - {"lbarr;", "⤌"}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 606 "HTMLCharacterReference.gperf" - {"Uuml", "Ü"}, + {"auml", "ä"}, +#line 722 "HTMLCharacterReference.gperf" + {"auml;", "ä"}, +#line 1304 "HTMLCharacterReference.gperf" + {"lbarr;", "⤌"}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, #line 607 "HTMLCharacterReference.gperf" - {"Uuml;", "Ü"}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 1067 "HTMLCharacterReference.gperf" - {"euro;", "€"}, - {""}, - {""}, - {""}, - {""}, -#line 998 "HTMLCharacterReference.gperf" - {"dtri;", "▿"}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 922 "HTMLCharacterReference.gperf" - {"cupor;", "⩅"}, - {""}, - {""}, -#line 715 "HTMLCharacterReference.gperf" - {"ast;", "*"}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 774 "HTMLCharacterReference.gperf" - {"bnot;", "⌐"}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 995 "HTMLCharacterReference.gperf" - {"dsol;", "⧶"}, -#line 1000 "HTMLCharacterReference.gperf" - {"duarr;", "⇵"}, - {""}, -#line 1250 "HTMLCharacterReference.gperf" - {"it;", "⁢"}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 1037 "HTMLCharacterReference.gperf" - {"eng;", "ŋ"}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 684 "HTMLCharacterReference.gperf" - {"ang;", "∠"}, -#line 891 "HTMLCharacterReference.gperf" - {"comp;", "∁"}, - {""}, - {""}, -#line 1225 "HTMLCharacterReference.gperf" - {"in;", "∈"}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 979 "HTMLCharacterReference.gperf" - {"dot;", "˙"}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 1006 "HTMLCharacterReference.gperf" - {"eDot;", "≑"}, -#line 1375 "HTMLCharacterReference.gperf" - {"loarr;", "⇽"}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 668 "HTMLCharacterReference.gperf" - {"af;", "⁡"}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 1145 "HTMLCharacterReference.gperf" - {"gne;", "⪈"}, -#line 836 "HTMLCharacterReference.gperf" - {"bump;", "≎"}, - {""}, - {""}, - {""}, -#line 1370 "HTMLCharacterReference.gperf" - {"lne;", "⪇"}, - {""}, -#line 696 "HTMLCharacterReference.gperf" - {"angrt;", "∟"}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 325 "HTMLCharacterReference.gperf" - {"Lt;", "≪"}, -#line 707 "HTMLCharacterReference.gperf" - {"ape;", "≊"}, -#line 733 "HTMLCharacterReference.gperf" - {"bbrk;", "⎵"}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 1230 "HTMLCharacterReference.gperf" - {"int;", "∫"}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 1054 "HTMLCharacterReference.gperf" - {"equiv;", "≡"}, - {""}, - {""}, - {""}, -#line 831 "HTMLCharacterReference.gperf" - {"bsol;", "\\"}, -#line 1188 "HTMLCharacterReference.gperf" - {"hoarr;", "⇿"}, - {""}, - {""}, - {""}, - {""}, -#line 1421 "HTMLCharacterReference.gperf" - {"ltrie;", "⊴"}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 1042 "HTMLCharacterReference.gperf" - {"eparsl;", "⧣"}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 1090 "HTMLCharacterReference.gperf" - {"frac12", "½"}, + {"Uuml", "Ü"}, +#line 608 "HTMLCharacterReference.gperf" + {"Uuml;", "Ü"}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 1068 "HTMLCharacterReference.gperf" + {"euro;", "€"}, + {""}, + {""}, + {""}, + {""}, +#line 999 "HTMLCharacterReference.gperf" + {"dtri;", "▿"}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 923 "HTMLCharacterReference.gperf" + {"cupor;", "⩅"}, + {""}, + {""}, +#line 716 "HTMLCharacterReference.gperf" + {"ast;", "*"}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 775 "HTMLCharacterReference.gperf" + {"bnot;", "⌐"}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 996 "HTMLCharacterReference.gperf" + {"dsol;", "⧶"}, +#line 1001 "HTMLCharacterReference.gperf" + {"duarr;", "⇵"}, + {""}, +#line 1251 "HTMLCharacterReference.gperf" + {"it;", "⁢"}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 1038 "HTMLCharacterReference.gperf" + {"eng;", "ŋ"}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 685 "HTMLCharacterReference.gperf" + {"ang;", "∠"}, +#line 892 "HTMLCharacterReference.gperf" + {"comp;", "∁"}, + {""}, + {""}, +#line 1226 "HTMLCharacterReference.gperf" + {"in;", "∈"}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 980 "HTMLCharacterReference.gperf" + {"dot;", "˙"}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 1007 "HTMLCharacterReference.gperf" + {"eDot;", "≑"}, +#line 1376 "HTMLCharacterReference.gperf" + {"loarr;", "⇽"}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 669 "HTMLCharacterReference.gperf" + {"af;", "⁡"}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 1146 "HTMLCharacterReference.gperf" + {"gne;", "⪈"}, +#line 837 "HTMLCharacterReference.gperf" + {"bump;", "≎"}, + {""}, + {""}, + {""}, +#line 1371 "HTMLCharacterReference.gperf" + {"lne;", "⪇"}, + {""}, +#line 697 "HTMLCharacterReference.gperf" + {"angrt;", "∟"}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 326 "HTMLCharacterReference.gperf" + {"Lt;", "≪"}, +#line 708 "HTMLCharacterReference.gperf" + {"ape;", "≊"}, +#line 734 "HTMLCharacterReference.gperf" + {"bbrk;", "⎵"}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 1231 "HTMLCharacterReference.gperf" + {"int;", "∫"}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 1055 "HTMLCharacterReference.gperf" + {"equiv;", "≡"}, + {""}, + {""}, + {""}, +#line 832 "HTMLCharacterReference.gperf" + {"bsol;", "\\"}, +#line 1189 "HTMLCharacterReference.gperf" + {"hoarr;", "⇿"}, + {""}, + {""}, + {""}, + {""}, +#line 1422 "HTMLCharacterReference.gperf" + {"ltrie;", "⊴"}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 1043 "HTMLCharacterReference.gperf" + {"eparsl;", "⧣"}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, #line 1091 "HTMLCharacterReference.gperf" - {"frac12;", "½"}, - {""}, - {""}, - {""}, -#line 2182 "HTMLCharacterReference.gperf" - {"vprop;", "∝"}, - {""}, - {""}, - {""}, - {""}, -#line 1307 "HTMLCharacterReference.gperf" - {"lbrke;", "⦋"}, - {""}, - {""}, -#line 1253 "HTMLCharacterReference.gperf" - {"iuml", "ï"}, + {"frac12", "½"}, +#line 1092 "HTMLCharacterReference.gperf" + {"frac12;", "½"}, + {""}, + {""}, + {""}, +#line 2183 "HTMLCharacterReference.gperf" + {"vprop;", "∝"}, + {""}, + {""}, + {""}, + {""}, +#line 1308 "HTMLCharacterReference.gperf" + {"lbrke;", "⦋"}, + {""}, + {""}, #line 1254 "HTMLCharacterReference.gperf" - {"iuml;", "ï"}, -#line 1093 "HTMLCharacterReference.gperf" - {"frac14", "¼"}, + {"iuml", "ï"}, +#line 1255 "HTMLCharacterReference.gperf" + {"iuml;", "ï"}, #line 1094 "HTMLCharacterReference.gperf" - {"frac14;", "¼"}, - {""}, -#line 776 "HTMLCharacterReference.gperf" - {"bot;", "⊥"}, - {""}, -#line 961 "HTMLCharacterReference.gperf" - {"dharr;", "⇂"}, + {"frac14", "¼"}, #line 1095 "HTMLCharacterReference.gperf" - {"frac15;", "⅕"}, - {""}, -#line 1133 "HTMLCharacterReference.gperf" - {"gfr;", "𝔤"}, - {""}, - {""}, + {"frac14;", "¼"}, + {""}, +#line 777 "HTMLCharacterReference.gperf" + {"bot;", "⊥"}, + {""}, +#line 962 "HTMLCharacterReference.gperf" + {"dharr;", "⇂"}, #line 1096 "HTMLCharacterReference.gperf" - {"frac16;", "⅙"}, - {""}, -#line 1351 "HTMLCharacterReference.gperf" - {"lfr;", "𝔩"}, -#line 1087 "HTMLCharacterReference.gperf" - {"fork;", "⋔"}, -#line 1100 "HTMLCharacterReference.gperf" - {"frac34", "¾"}, -#line 1101 "HTMLCharacterReference.gperf" - {"frac34;", "¾"}, - {""}, -#line 1019 "HTMLCharacterReference.gperf" - {"efr;", "𝔢"}, - {""}, + {"frac15;", "⅕"}, + {""}, +#line 1134 "HTMLCharacterReference.gperf" + {"gfr;", "𝔤"}, + {""}, + {""}, +#line 1097 "HTMLCharacterReference.gperf" + {"frac16;", "⅙"}, + {""}, +#line 1352 "HTMLCharacterReference.gperf" + {"lfr;", "𝔩"}, #line 1088 "HTMLCharacterReference.gperf" - {"forkv;", "⫙"}, + {"fork;", "⋔"}, +#line 1101 "HTMLCharacterReference.gperf" + {"frac34", "¾"}, #line 1102 "HTMLCharacterReference.gperf" - {"frac35;", "⅗"}, - {""}, -#line 864 "HTMLCharacterReference.gperf" - {"cfr;", "𝔠"}, - {""}, - {""}, -#line 1104 "HTMLCharacterReference.gperf" - {"frac45;", "⅘"}, - {""}, -#line 669 "HTMLCharacterReference.gperf" - {"afr;", "𝔞"}, -#line 644 "HTMLCharacterReference.gperf" - {"Yuml;", "Ÿ"}, - {""}, - {""}, - {""}, -#line 1257 "HTMLCharacterReference.gperf" - {"jfr;", "𝔧"}, -#line 1279 "HTMLCharacterReference.gperf" - {"lHar;", "⥢"}, - {""}, + {"frac34;", "¾"}, + {""}, +#line 1020 "HTMLCharacterReference.gperf" + {"efr;", "𝔢"}, + {""}, +#line 1089 "HTMLCharacterReference.gperf" + {"forkv;", "⫙"}, +#line 1103 "HTMLCharacterReference.gperf" + {"frac35;", "⅗"}, + {""}, +#line 865 "HTMLCharacterReference.gperf" + {"cfr;", "𝔠"}, + {""}, + {""}, #line 1105 "HTMLCharacterReference.gperf" - {"frac56;", "⅚"}, - {""}, -#line 578 "HTMLCharacterReference.gperf" - {"Ufr;", "𝔘"}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 908 "HTMLCharacterReference.gperf" - {"csup;", "⫐"}, - {""}, -#line 1092 "HTMLCharacterReference.gperf" - {"frac13;", "⅓"}, - {""}, -#line 1774 "HTMLCharacterReference.gperf" - {"quot", "\""}, + {"frac45;", "⅘"}, + {""}, +#line 670 "HTMLCharacterReference.gperf" + {"afr;", "𝔞"}, +#line 645 "HTMLCharacterReference.gperf" + {"Yuml;", "Ÿ"}, + {""}, + {""}, + {""}, +#line 1258 "HTMLCharacterReference.gperf" + {"jfr;", "𝔧"}, +#line 1280 "HTMLCharacterReference.gperf" + {"lHar;", "⥢"}, + {""}, +#line 1106 "HTMLCharacterReference.gperf" + {"frac56;", "⅚"}, + {""}, +#line 579 "HTMLCharacterReference.gperf" + {"Ufr;", "𝔘"}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 909 "HTMLCharacterReference.gperf" + {"csup;", "⫐"}, + {""}, +#line 1093 "HTMLCharacterReference.gperf" + {"frac13;", "⅓"}, + {""}, #line 1775 "HTMLCharacterReference.gperf" - {"quot;", "\""}, -#line 1039 "HTMLCharacterReference.gperf" - {"eogon;", "ę"}, - {""}, - {""}, - {""}, - {""}, -#line 930 "HTMLCharacterReference.gperf" - {"curren", "¤"}, + {"quot", "\""}, +#line 1776 "HTMLCharacterReference.gperf" + {"quot;", "\""}, +#line 1040 "HTMLCharacterReference.gperf" + {"eogon;", "ę"}, + {""}, + {""}, + {""}, + {""}, #line 931 "HTMLCharacterReference.gperf" - {"curren;", "¤"}, -#line 334 "HTMLCharacterReference.gperf" - {"Mu;", "Μ"}, -#line 959 "HTMLCharacterReference.gperf" - {"dfr;", "𝔡"}, - {""}, -#line 702 "HTMLCharacterReference.gperf" - {"aogon;", "ą"}, -#line 1163 "HTMLCharacterReference.gperf" - {"gtrarr;", "⥸"}, - {""}, -#line 1185 "HTMLCharacterReference.gperf" - {"hfr;", "𝔥"}, - {""}, - {""}, -#line 1099 "HTMLCharacterReference.gperf" - {"frac25;", "⅖"}, - {""}, - {""}, - {""}, -#line 588 "HTMLCharacterReference.gperf" - {"Uogon;", "Ų"}, - {""}, - {""}, -#line 772 "HTMLCharacterReference.gperf" - {"bne;", "=⃥"}, - {""}, - {""}, -#line 1097 "HTMLCharacterReference.gperf" - {"frac18;", "⅛"}, - {""}, - {""}, -#line 940 "HTMLCharacterReference.gperf" - {"dHar;", "⥥"}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 913 "HTMLCharacterReference.gperf" - {"cuepr;", "⋞"}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 1103 "HTMLCharacterReference.gperf" - {"frac38;", "⅜"}, - {""}, - {""}, - {""}, + {"curren", "¤"}, +#line 932 "HTMLCharacterReference.gperf" + {"curren;", "¤"}, +#line 335 "HTMLCharacterReference.gperf" + {"Mu;", "Μ"}, #line 960 "HTMLCharacterReference.gperf" - {"dharl;", "⇃"}, -#line 1393 "HTMLCharacterReference.gperf" - {"lparlt;", "⦓"}, - {""}, -#line 457 "HTMLCharacterReference.gperf" - {"Qfr;", "𝔔"}, - {""}, - {""}, -#line 1106 "HTMLCharacterReference.gperf" - {"frac58;", "⅝"}, - {""}, - {""}, - {""}, - {""}, + {"dfr;", "𝔡"}, + {""}, +#line 703 "HTMLCharacterReference.gperf" + {"aogon;", "ą"}, +#line 1164 "HTMLCharacterReference.gperf" + {"gtrarr;", "⥸"}, + {""}, +#line 1186 "HTMLCharacterReference.gperf" + {"hfr;", "𝔥"}, + {""}, + {""}, +#line 1100 "HTMLCharacterReference.gperf" + {"frac25;", "⅖"}, + {""}, + {""}, + {""}, +#line 589 "HTMLCharacterReference.gperf" + {"Uogon;", "Ų"}, + {""}, + {""}, +#line 773 "HTMLCharacterReference.gperf" + {"bne;", "=⃥"}, + {""}, + {""}, #line 1098 "HTMLCharacterReference.gperf" - {"frac23;", "⅔"}, - {""}, -#line 1078 "HTMLCharacterReference.gperf" - {"ffr;", "𝔣"}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 2195 "HTMLCharacterReference.gperf" - {"wfr;", "𝔴"}, - {""}, -#line 838 "HTMLCharacterReference.gperf" - {"bumpe;", "≏"}, - {""}, - {""}, - {""}, - {""}, -#line 686 "HTMLCharacterReference.gperf" - {"angle;", "∠"}, - {""}, - {""}, -#line 2177 "HTMLCharacterReference.gperf" - {"vfr;", "𝔳"}, - {""}, - {""}, -#line 924 "HTMLCharacterReference.gperf" - {"curarr;", "↷"}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 1146 "HTMLCharacterReference.gperf" - {"gneq;", "⪈"}, + {"frac18;", "⅛"}, + {""}, + {""}, +#line 941 "HTMLCharacterReference.gperf" + {"dHar;", "⥥"}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 914 "HTMLCharacterReference.gperf" + {"cuepr;", "⋞"}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 1104 "HTMLCharacterReference.gperf" + {"frac38;", "⅜"}, + {""}, + {""}, + {""}, +#line 961 "HTMLCharacterReference.gperf" + {"dharl;", "⇃"}, +#line 1394 "HTMLCharacterReference.gperf" + {"lparlt;", "⦓"}, + {""}, +#line 458 "HTMLCharacterReference.gperf" + {"Qfr;", "𝔔"}, + {""}, + {""}, +#line 1107 "HTMLCharacterReference.gperf" + {"frac58;", "⅝"}, + {""}, + {""}, + {""}, + {""}, +#line 1099 "HTMLCharacterReference.gperf" + {"frac23;", "⅔"}, + {""}, +#line 1079 "HTMLCharacterReference.gperf" + {"ffr;", "𝔣"}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 2196 "HTMLCharacterReference.gperf" + {"wfr;", "𝔴"}, + {""}, +#line 839 "HTMLCharacterReference.gperf" + {"bumpe;", "≏"}, + {""}, + {""}, + {""}, + {""}, +#line 687 "HTMLCharacterReference.gperf" + {"angle;", "∠"}, + {""}, + {""}, +#line 2178 "HTMLCharacterReference.gperf" + {"vfr;", "𝔳"}, + {""}, + {""}, +#line 925 "HTMLCharacterReference.gperf" + {"curarr;", "↷"}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, #line 1147 "HTMLCharacterReference.gperf" - {"gneqq;", "≩"}, - {""}, - {""}, - {""}, -#line 1371 "HTMLCharacterReference.gperf" - {"lneq;", "⪇"}, + {"gneq;", "⪈"}, +#line 1148 "HTMLCharacterReference.gperf" + {"gneqq;", "≩"}, + {""}, + {""}, + {""}, #line 1372 "HTMLCharacterReference.gperf" - {"lneqq;", "≨"}, -#line 899 "HTMLCharacterReference.gperf" - {"coprod;", "∐"}, -#line 1121 "HTMLCharacterReference.gperf" - {"ge;", "≥"}, -#line 746 "HTMLCharacterReference.gperf" - {"bfr;", "𝔟"}, - {""}, - {""}, - {""}, -#line 1321 "HTMLCharacterReference.gperf" - {"le;", "≤"}, -#line 1126 "HTMLCharacterReference.gperf" - {"ges;", "⩾"}, - {""}, -#line 1383 "HTMLCharacterReference.gperf" - {"lopar;", "⦅"}, -#line 777 "HTMLCharacterReference.gperf" - {"bottom;", "⊥"}, -#line 1017 "HTMLCharacterReference.gperf" - {"ee;", "ⅇ"}, -#line 1336 "HTMLCharacterReference.gperf" - {"les;", "⩽"}, - {""}, - {""}, -#line 1107 "HTMLCharacterReference.gperf" - {"frac78;", "⅞"}, - {""}, -#line 1123 "HTMLCharacterReference.gperf" - {"geq;", "≥"}, - {""}, - {""}, - {""}, - {""}, -#line 1333 "HTMLCharacterReference.gperf" - {"leq;", "≤"}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 1159 "HTMLCharacterReference.gperf" - {"gtdot;", "⋗"}, - {""}, - {""}, + {"lneq;", "⪇"}, +#line 1373 "HTMLCharacterReference.gperf" + {"lneqq;", "≨"}, #line 900 "HTMLCharacterReference.gperf" - {"copy", "©"}, + {"coprod;", "∐"}, +#line 1122 "HTMLCharacterReference.gperf" + {"ge;", "≥"}, +#line 747 "HTMLCharacterReference.gperf" + {"bfr;", "𝔟"}, + {""}, + {""}, + {""}, +#line 1322 "HTMLCharacterReference.gperf" + {"le;", "≤"}, +#line 1127 "HTMLCharacterReference.gperf" + {"ges;", "⩾"}, + {""}, +#line 1384 "HTMLCharacterReference.gperf" + {"lopar;", "⦅"}, +#line 778 "HTMLCharacterReference.gperf" + {"bottom;", "⊥"}, +#line 1018 "HTMLCharacterReference.gperf" + {"ee;", "ⅇ"}, +#line 1337 "HTMLCharacterReference.gperf" + {"les;", "⩽"}, + {""}, + {""}, +#line 1108 "HTMLCharacterReference.gperf" + {"frac78;", "⅞"}, + {""}, +#line 1124 "HTMLCharacterReference.gperf" + {"geq;", "≥"}, + {""}, + {""}, + {""}, + {""}, +#line 1334 "HTMLCharacterReference.gperf" + {"leq;", "≤"}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 1160 "HTMLCharacterReference.gperf" + {"gtdot;", "⋗"}, + {""}, + {""}, #line 901 "HTMLCharacterReference.gperf" - {"copy;", "©"}, -#line 1414 "HTMLCharacterReference.gperf" - {"ltdot;", "⋖"}, - {""}, - {""}, - {""}, - {""}, -#line 782 "HTMLCharacterReference.gperf" - {"boxDr;", "╓"}, - {""}, - {""}, - {""}, - {""}, + {"copy", "©"}, +#line 902 "HTMLCharacterReference.gperf" + {"copy;", "©"}, +#line 1415 "HTMLCharacterReference.gperf" + {"ltdot;", "⋖"}, + {""}, + {""}, + {""}, + {""}, +#line 783 "HTMLCharacterReference.gperf" + {"boxDr;", "╓"}, + {""}, + {""}, + {""}, + {""}, +#line 911 "HTMLCharacterReference.gperf" + {"ctdot;", "⋯"}, + {""}, + {""}, +#line 680 "HTMLCharacterReference.gperf" + {"and;", "∧"}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 1132 "HTMLCharacterReference.gperf" + {"gesl;", "⋛︀"}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 215 "HTMLCharacterReference.gperf" + {"Hfr;", "ℌ"}, + {""}, + {""}, + {""}, + {""}, +#line 182 "HTMLCharacterReference.gperf" + {"Ffr;", "𝔉"}, + {""}, + {""}, +#line 840 "HTMLCharacterReference.gperf" + {"bumpeq;", "≏"}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 1210 "HTMLCharacterReference.gperf" + {"ifr;", "𝔦"}, + {""}, +#line 998 "HTMLCharacterReference.gperf" + {"dtdot;", "⋱"}, + {""}, + {""}, + {""}, + {""}, #line 910 "HTMLCharacterReference.gperf" - {"ctdot;", "⋯"}, - {""}, - {""}, -#line 679 "HTMLCharacterReference.gperf" - {"and;", "∧"}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 1131 "HTMLCharacterReference.gperf" - {"gesl;", "⋛︀"}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 214 "HTMLCharacterReference.gperf" - {"Hfr;", "ℌ"}, - {""}, - {""}, - {""}, - {""}, -#line 181 "HTMLCharacterReference.gperf" - {"Ffr;", "𝔉"}, - {""}, - {""}, -#line 839 "HTMLCharacterReference.gperf" - {"bumpeq;", "≏"}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 1209 "HTMLCharacterReference.gperf" - {"ifr;", "𝔦"}, - {""}, -#line 997 "HTMLCharacterReference.gperf" - {"dtdot;", "⋱"}, - {""}, - {""}, - {""}, - {""}, -#line 909 "HTMLCharacterReference.gperf" - {"csupe;", "⫒"}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 837 "HTMLCharacterReference.gperf" - {"bumpE;", "⪮"}, -#line 897 "HTMLCharacterReference.gperf" - {"conint;", "∮"}, - {""}, - {""}, -#line 531 "HTMLCharacterReference.gperf" - {"Star;", "⋆"}, - {""}, - {""}, - {""}, -#line 641 "HTMLCharacterReference.gperf" - {"Yfr;", "𝔜"}, - {""}, -#line 1237 "HTMLCharacterReference.gperf" - {"iogon;", "į"}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 540 "HTMLCharacterReference.gperf" - {"Sum;", "∑"}, - {""}, -#line 781 "HTMLCharacterReference.gperf" - {"boxDl;", "╖"}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 117 "HTMLCharacterReference.gperf" - {"Dot;", "¨"}, - {""}, -#line 1060 "HTMLCharacterReference.gperf" - {"esdot;", "≐"}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 1134 "HTMLCharacterReference.gperf" - {"gg;", "≫"}, -#line 309 "HTMLCharacterReference.gperf" - {"Lfr;", "𝔏"}, - {""}, - {""}, - {""}, -#line 1352 "HTMLCharacterReference.gperf" - {"lg;", "≶"}, - {""}, - {""}, - {""}, - {""}, -#line 1020 "HTMLCharacterReference.gperf" - {"eg;", "⪚"}, + {"csupe;", "⫒"}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 838 "HTMLCharacterReference.gperf" + {"bumpE;", "⪮"}, +#line 898 "HTMLCharacterReference.gperf" + {"conint;", "∮"}, + {""}, + {""}, +#line 532 "HTMLCharacterReference.gperf" + {"Star;", "⋆"}, + {""}, + {""}, + {""}, +#line 642 "HTMLCharacterReference.gperf" + {"Yfr;", "𝔜"}, + {""}, +#line 1238 "HTMLCharacterReference.gperf" + {"iogon;", "į"}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, #line 541 "HTMLCharacterReference.gperf" - {"Sup;", "⋑"}, - {""}, - {""}, - {""}, -#line 96 "HTMLCharacterReference.gperf" - {"DD;", "ⅅ"}, -#line 1023 "HTMLCharacterReference.gperf" - {"egs;", "⪖"}, - {""}, - {""}, - {""}, -#line 1612 "HTMLCharacterReference.gperf" - {"nu;", "ν"}, -#line 861 "HTMLCharacterReference.gperf" - {"cent", "¢"}, -#line 862 "HTMLCharacterReference.gperf" - {"cent;", "¢"}, -#line 866 "HTMLCharacterReference.gperf" - {"check;", "✓"}, - {""}, -#line 1045 "HTMLCharacterReference.gperf" - {"epsilon;", "ε"}, -#line 238 "HTMLCharacterReference.gperf" - {"Int;", "∬"}, -#line 1239 "HTMLCharacterReference.gperf" - {"iota;", "ι"}, - {""}, - {""}, - {""}, -#line 1765 "HTMLCharacterReference.gperf" - {"qfr;", "𝔮"}, - {""}, - {""}, - {""}, -#line 1167 "HTMLCharacterReference.gperf" - {"gtrless;", "≷"}, - {""}, -#line 1560 "HTMLCharacterReference.gperf" - {"npar;", "∦"}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 780 "HTMLCharacterReference.gperf" - {"boxDR;", "╔"}, - {""}, -#line 602 "HTMLCharacterReference.gperf" - {"Upsilon;", "Υ"}, + {"Sum;", "∑"}, + {""}, +#line 782 "HTMLCharacterReference.gperf" + {"boxDl;", "╖"}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 118 "HTMLCharacterReference.gperf" + {"Dot;", "¨"}, + {""}, +#line 1061 "HTMLCharacterReference.gperf" + {"esdot;", "≐"}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 1135 "HTMLCharacterReference.gperf" + {"gg;", "≫"}, +#line 310 "HTMLCharacterReference.gperf" + {"Lfr;", "𝔏"}, + {""}, + {""}, + {""}, +#line 1353 "HTMLCharacterReference.gperf" + {"lg;", "≶"}, + {""}, + {""}, + {""}, + {""}, +#line 1021 "HTMLCharacterReference.gperf" + {"eg;", "⪚"}, +#line 542 "HTMLCharacterReference.gperf" + {"Sup;", "⋑"}, + {""}, + {""}, + {""}, +#line 97 "HTMLCharacterReference.gperf" + {"DD;", "ⅅ"}, +#line 1024 "HTMLCharacterReference.gperf" + {"egs;", "⪖"}, + {""}, + {""}, + {""}, #line 1613 "HTMLCharacterReference.gperf" - {"num;", "#"}, - {""}, - {""}, -#line 1181 "HTMLCharacterReference.gperf" - {"hearts;", "♥"}, - {""}, -#line 1489 "HTMLCharacterReference.gperf" - {"nbsp", " "}, + {"nu;", "ν"}, +#line 862 "HTMLCharacterReference.gperf" + {"cent", "¢"}, +#line 863 "HTMLCharacterReference.gperf" + {"cent;", "¢"}, +#line 867 "HTMLCharacterReference.gperf" + {"check;", "✓"}, + {""}, +#line 1046 "HTMLCharacterReference.gperf" + {"epsilon;", "ε"}, +#line 239 "HTMLCharacterReference.gperf" + {"Int;", "∬"}, +#line 1240 "HTMLCharacterReference.gperf" + {"iota;", "ι"}, + {""}, + {""}, + {""}, +#line 1766 "HTMLCharacterReference.gperf" + {"qfr;", "𝔮"}, + {""}, + {""}, + {""}, +#line 1168 "HTMLCharacterReference.gperf" + {"gtrless;", "≷"}, + {""}, +#line 1561 "HTMLCharacterReference.gperf" + {"npar;", "∦"}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 781 "HTMLCharacterReference.gperf" + {"boxDR;", "╔"}, + {""}, +#line 603 "HTMLCharacterReference.gperf" + {"Upsilon;", "Υ"}, +#line 1614 "HTMLCharacterReference.gperf" + {"num;", "#"}, + {""}, + {""}, +#line 1182 "HTMLCharacterReference.gperf" + {"hearts;", "♥"}, + {""}, #line 1490 "HTMLCharacterReference.gperf" - {"nbsp;", " "}, - {""}, - {""}, - {""}, -#line 1332 "HTMLCharacterReference.gperf" - {"leg;", "⋚"}, -#line 522 "HTMLCharacterReference.gperf" - {"Sqrt;", "√"}, -#line 791 "HTMLCharacterReference.gperf" - {"boxUr;", "╙"}, - {""}, - {""}, -#line 330 "HTMLCharacterReference.gperf" - {"Mfr;", "𝔐"}, - {""}, -#line 1563 "HTMLCharacterReference.gperf" - {"npart;", "∂̸"}, - {""}, - {""}, - {""}, -#line 1162 "HTMLCharacterReference.gperf" - {"gtrapprox;", "⪆"}, - {""}, -#line 687 "HTMLCharacterReference.gperf" - {"angmsd;", "∡"}, - {""}, -#line 249 "HTMLCharacterReference.gperf" - {"Iuml", "Ï"}, + {"nbsp", " "}, +#line 1491 "HTMLCharacterReference.gperf" + {"nbsp;", " "}, + {""}, + {""}, + {""}, +#line 1333 "HTMLCharacterReference.gperf" + {"leg;", "⋚"}, +#line 523 "HTMLCharacterReference.gperf" + {"Sqrt;", "√"}, +#line 792 "HTMLCharacterReference.gperf" + {"boxUr;", "╙"}, + {""}, + {""}, +#line 331 "HTMLCharacterReference.gperf" + {"Mfr;", "𝔐"}, + {""}, +#line 1564 "HTMLCharacterReference.gperf" + {"npart;", "∂̸"}, + {""}, + {""}, + {""}, +#line 1163 "HTMLCharacterReference.gperf" + {"gtrapprox;", "⪆"}, + {""}, +#line 688 "HTMLCharacterReference.gperf" + {"angmsd;", "∡"}, + {""}, #line 250 "HTMLCharacterReference.gperf" - {"Iuml;", "Ï"}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 2176 "HTMLCharacterReference.gperf" - {"vert;", "|"}, - {""}, - {""}, - {""}, -#line 1565 "HTMLCharacterReference.gperf" - {"npr;", "⊀"}, -#line 1124 "HTMLCharacterReference.gperf" - {"geqq;", "≧"}, -#line 1571 "HTMLCharacterReference.gperf" - {"nrarr;", "↛"}, -#line 1052 "HTMLCharacterReference.gperf" - {"equals;", "="}, - {""}, - {""}, -#line 1334 "HTMLCharacterReference.gperf" - {"leqq;", "≦"}, - {""}, -#line 1573 "HTMLCharacterReference.gperf" - {"nrarrw;", "↝̸"}, -#line 954 "HTMLCharacterReference.gperf" - {"deg", "°"}, - {""}, - {""}, - {""}, - {""}, - {""}, + {"Iuml", "Ï"}, +#line 251 "HTMLCharacterReference.gperf" + {"Iuml;", "Ï"}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 2177 "HTMLCharacterReference.gperf" + {"vert;", "|"}, + {""}, + {""}, + {""}, +#line 1566 "HTMLCharacterReference.gperf" + {"npr;", "⊀"}, +#line 1125 "HTMLCharacterReference.gperf" + {"geqq;", "≧"}, +#line 1572 "HTMLCharacterReference.gperf" + {"nrarr;", "↛"}, +#line 1053 "HTMLCharacterReference.gperf" + {"equals;", "="}, + {""}, + {""}, +#line 1335 "HTMLCharacterReference.gperf" + {"leqq;", "≦"}, + {""}, +#line 1574 "HTMLCharacterReference.gperf" + {"nrarrw;", "↝̸"}, #line 955 "HTMLCharacterReference.gperf" - {"deg;", "°"}, - {""}, -#line 1632 "HTMLCharacterReference.gperf" - {"nwarr;", "↖"}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 902 "HTMLCharacterReference.gperf" - {"copysr;", "℗"}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 983 "HTMLCharacterReference.gperf" - {"dotplus;", "∔"}, - {""}, -#line 1406 "HTMLCharacterReference.gperf" - {"lsqb;", "["}, - {""}, -#line 1086 "HTMLCharacterReference.gperf" - {"forall;", "∀"}, - {""}, -#line 1389 "HTMLCharacterReference.gperf" - {"loz;", "◊"}, - {""}, - {""}, - {""}, -#line 209 "HTMLCharacterReference.gperf" - {"Gt;", "≫"}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 689 "HTMLCharacterReference.gperf" - {"angmsdab;", "⦩"}, - {""}, - {""}, - {""}, -#line 925 "HTMLCharacterReference.gperf" - {"curarrm;", "⤼"}, -#line 175 "HTMLCharacterReference.gperf" - {"Eta;", "Η"}, - {""}, - {""}, - {""}, - {""}, -#line 108 "HTMLCharacterReference.gperf" - {"Dfr;", "𝔇"}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 790 "HTMLCharacterReference.gperf" - {"boxUl;", "╜"}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 1132 "HTMLCharacterReference.gperf" - {"gesles;", "⪔"}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 810 "HTMLCharacterReference.gperf" - {"boxplus;", "⊞"}, - {""}, - {""}, - {""}, - {""}, -#line 1548 "HTMLCharacterReference.gperf" - {"not", "¬"}, - {""}, - {""}, -#line 832 "HTMLCharacterReference.gperf" - {"bsolb;", "⧅"}, - {""}, - {""}, -#line 1549 "HTMLCharacterReference.gperf" - {"not;", "¬"}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 253 "HTMLCharacterReference.gperf" - {"Jfr;", "𝔍"}, - {""}, - {""}, - {""}, - {""}, -#line 1135 "HTMLCharacterReference.gperf" - {"ggg;", "⋙"}, -#line 1169 "HTMLCharacterReference.gperf" - {"gvertneqq;", "≩︀"}, -#line 1150 "HTMLCharacterReference.gperf" - {"grave;", "`"}, - {""}, - {""}, - {""}, -#line 1425 "HTMLCharacterReference.gperf" - {"lvertneqq;", "≨︀"}, - {""}, - {""}, - {""}, - {""}, -#line 1604 "HTMLCharacterReference.gperf" - {"ntgl;", "≹"}, -#line 789 "HTMLCharacterReference.gperf" - {"boxUR;", "╚"}, - {""}, - {""}, -#line 630 "HTMLCharacterReference.gperf" - {"Xfr;", "𝔛"}, -#line 867 "HTMLCharacterReference.gperf" - {"checkmark;", "✓"}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 1629 "HTMLCharacterReference.gperf" - {"nvsim;", "∼⃒"}, - {""}, - {""}, + {"deg", "°"}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 956 "HTMLCharacterReference.gperf" + {"deg;", "°"}, + {""}, +#line 1633 "HTMLCharacterReference.gperf" + {"nwarr;", "↖"}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 903 "HTMLCharacterReference.gperf" + {"copysr;", "℗"}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 984 "HTMLCharacterReference.gperf" + {"dotplus;", "∔"}, + {""}, +#line 1407 "HTMLCharacterReference.gperf" + {"lsqb;", "["}, + {""}, +#line 1087 "HTMLCharacterReference.gperf" + {"forall;", "∀"}, + {""}, +#line 1390 "HTMLCharacterReference.gperf" + {"loz;", "◊"}, + {""}, + {""}, + {""}, +#line 210 "HTMLCharacterReference.gperf" + {"Gt;", "≫"}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 690 "HTMLCharacterReference.gperf" + {"angmsdab;", "⦩"}, + {""}, + {""}, + {""}, +#line 926 "HTMLCharacterReference.gperf" + {"curarrm;", "⤼"}, #line 176 "HTMLCharacterReference.gperf" - {"Euml", "Ë"}, -#line 177 "HTMLCharacterReference.gperf" - {"Euml;", "Ë"}, - {""}, -#line 1184 "HTMLCharacterReference.gperf" - {"hercon;", "⊹"}, - {""}, -#line 2171 "HTMLCharacterReference.gperf" - {"vee;", "∨"}, - {""}, -#line 2218 "HTMLCharacterReference.gperf" - {"xrarr;", "⟶"}, - {""}, - {""}, - {""}, - {""}, + {"Eta;", "Η"}, + {""}, + {""}, + {""}, + {""}, +#line 109 "HTMLCharacterReference.gperf" + {"Dfr;", "𝔇"}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 791 "HTMLCharacterReference.gperf" + {"boxUl;", "╜"}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 1133 "HTMLCharacterReference.gperf" + {"gesles;", "⪔"}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 811 "HTMLCharacterReference.gperf" + {"boxplus;", "⊞"}, + {""}, + {""}, + {""}, + {""}, +#line 1549 "HTMLCharacterReference.gperf" + {"not", "¬"}, + {""}, + {""}, +#line 833 "HTMLCharacterReference.gperf" + {"bsolb;", "⧅"}, + {""}, + {""}, #line 1550 "HTMLCharacterReference.gperf" - {"notin;", "∉"}, -#line 742 "HTMLCharacterReference.gperf" - {"bernou;", "ℬ"}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 1309 "HTMLCharacterReference.gperf" - {"lbrkslu;", "⦍"}, - {""}, - {""}, -#line 1354 "HTMLCharacterReference.gperf" - {"lhard;", "↽"}, - {""}, - {""}, -#line 514 "HTMLCharacterReference.gperf" - {"Sfr;", "𝔖"}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 816 "HTMLCharacterReference.gperf" - {"boxv;", "│"}, + {"not;", "¬"}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 254 "HTMLCharacterReference.gperf" + {"Jfr;", "𝔍"}, + {""}, + {""}, + {""}, + {""}, +#line 1136 "HTMLCharacterReference.gperf" + {"ggg;", "⋙"}, +#line 1170 "HTMLCharacterReference.gperf" + {"gvertneqq;", "≩︀"}, +#line 1151 "HTMLCharacterReference.gperf" + {"grave;", "`"}, + {""}, + {""}, + {""}, +#line 1426 "HTMLCharacterReference.gperf" + {"lvertneqq;", "≨︀"}, + {""}, + {""}, + {""}, + {""}, +#line 1605 "HTMLCharacterReference.gperf" + {"ntgl;", "≹"}, +#line 790 "HTMLCharacterReference.gperf" + {"boxUR;", "╚"}, + {""}, + {""}, +#line 631 "HTMLCharacterReference.gperf" + {"Xfr;", "𝔛"}, +#line 868 "HTMLCharacterReference.gperf" + {"checkmark;", "✓"}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 1630 "HTMLCharacterReference.gperf" + {"nvsim;", "∼⃒"}, + {""}, + {""}, +#line 177 "HTMLCharacterReference.gperf" + {"Euml", "Ë"}, +#line 178 "HTMLCharacterReference.gperf" + {"Euml;", "Ë"}, + {""}, +#line 1185 "HTMLCharacterReference.gperf" + {"hercon;", "⊹"}, + {""}, +#line 2172 "HTMLCharacterReference.gperf" + {"vee;", "∨"}, + {""}, +#line 2219 "HTMLCharacterReference.gperf" + {"xrarr;", "⟶"}, + {""}, + {""}, + {""}, + {""}, +#line 1551 "HTMLCharacterReference.gperf" + {"notin;", "∉"}, +#line 743 "HTMLCharacterReference.gperf" + {"bernou;", "ℬ"}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 1310 "HTMLCharacterReference.gperf" + {"lbrkslu;", "⦍"}, + {""}, + {""}, +#line 1355 "HTMLCharacterReference.gperf" + {"lhard;", "↽"}, + {""}, + {""}, +#line 515 "HTMLCharacterReference.gperf" + {"Sfr;", "𝔖"}, + {""}, + {""}, + {""}, + {""}, + {""}, #line 817 "HTMLCharacterReference.gperf" - {"boxvH;", "╪"}, - {""}, - {""}, - {""}, - {""}, -#line 1523 "HTMLCharacterReference.gperf" - {"nharr;", "↮"}, - {""}, - {""}, - {""}, -#line 1618 "HTMLCharacterReference.gperf" - {"nvap;", "≍⃒"}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 586 "HTMLCharacterReference.gperf" - {"Union;", "⋃"}, -#line 1562 "HTMLCharacterReference.gperf" - {"nparsl;", "⫽⃥"}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 1152 "HTMLCharacterReference.gperf" - {"gsim;", "≳"}, - {""}, - {""}, - {""}, -#line 695 "HTMLCharacterReference.gperf" - {"angmsdah;", "⦯"}, -#line 1403 "HTMLCharacterReference.gperf" - {"lsim;", "≲"}, - {""}, - {""}, - {""}, -#line 475 "HTMLCharacterReference.gperf" - {"Rho;", "Ρ"}, -#line 1061 "HTMLCharacterReference.gperf" - {"esim;", "≂"}, -#line 1127 "HTMLCharacterReference.gperf" - {"gescc;", "⪩"}, + {"boxv;", "│"}, +#line 818 "HTMLCharacterReference.gperf" + {"boxvH;", "╪"}, + {""}, + {""}, + {""}, + {""}, +#line 1524 "HTMLCharacterReference.gperf" + {"nharr;", "↮"}, + {""}, + {""}, + {""}, +#line 1619 "HTMLCharacterReference.gperf" + {"nvap;", "≍⃒"}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 587 "HTMLCharacterReference.gperf" + {"Union;", "⋃"}, +#line 1563 "HTMLCharacterReference.gperf" + {"nparsl;", "⫽⃥"}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 1153 "HTMLCharacterReference.gperf" + {"gsim;", "≳"}, + {""}, + {""}, + {""}, +#line 696 "HTMLCharacterReference.gperf" + {"angmsdah;", "⦯"}, +#line 1404 "HTMLCharacterReference.gperf" + {"lsim;", "≲"}, + {""}, + {""}, + {""}, +#line 476 "HTMLCharacterReference.gperf" + {"Rho;", "Ρ"}, +#line 1062 "HTMLCharacterReference.gperf" + {"esim;", "≂"}, +#line 1128 "HTMLCharacterReference.gperf" + {"gescc;", "⪩"}, +#line 824 "HTMLCharacterReference.gperf" + {"bprime;", "‵"}, + {""}, +#line 232 "HTMLCharacterReference.gperf" + {"Ifr;", "ℑ"}, + {""}, +#line 1338 "HTMLCharacterReference.gperf" + {"lescc;", "⪨"}, + {""}, + {""}, + {""}, +#line 744 "HTMLCharacterReference.gperf" + {"beta;", "β"}, + {""}, + {""}, +#line 407 "HTMLCharacterReference.gperf" + {"Nu;", "Ν"}, + {""}, + {""}, +#line 1228 "HTMLCharacterReference.gperf" + {"infin;", "∞"}, + {""}, + {""}, + {""}, + {""}, #line 823 "HTMLCharacterReference.gperf" - {"bprime;", "‵"}, - {""}, -#line 231 "HTMLCharacterReference.gperf" - {"Ifr;", "ℑ"}, - {""}, -#line 1337 "HTMLCharacterReference.gperf" - {"lescc;", "⪨"}, - {""}, - {""}, - {""}, -#line 743 "HTMLCharacterReference.gperf" - {"beta;", "β"}, - {""}, - {""}, -#line 406 "HTMLCharacterReference.gperf" - {"Nu;", "Ν"}, - {""}, - {""}, -#line 1227 "HTMLCharacterReference.gperf" - {"infin;", "∞"}, - {""}, - {""}, - {""}, - {""}, + {"boxvr;", "├"}, + {""}, + {""}, +#line 1513 "HTMLCharacterReference.gperf" + {"nfr;", "𝔫"}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 1492 "HTMLCharacterReference.gperf" + {"nbump;", "≎̸"}, + {""}, + {""}, + {""}, + {""}, +#line 244 "HTMLCharacterReference.gperf" + {"Iogon;", "Į"}, + {""}, + {""}, + {""}, +#line 907 "HTMLCharacterReference.gperf" + {"csub;", "⫏"}, +#line 1241 "HTMLCharacterReference.gperf" + {"iprod;", "⨼"}, + {""}, + {""}, + {""}, +#line 1599 "HTMLCharacterReference.gperf" + {"nsup;", "⊅"}, +#line 938 "HTMLCharacterReference.gperf" + {"cwint;", "∱"}, + {""}, + {""}, + {""}, + {""}, +#line 724 "HTMLCharacterReference.gperf" + {"awint;", "⨑"}, + {""}, + {""}, +#line 694 "HTMLCharacterReference.gperf" + {"angmsdaf;", "⦭"}, + {""}, + {""}, + {""}, + {""}, +#line 653 "HTMLCharacterReference.gperf" + {"Zfr;", "ℨ"}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 1622 "HTMLCharacterReference.gperf" + {"nvgt;", ">⃒"}, + {""}, + {""}, + {""}, +#line 869 "HTMLCharacterReference.gperf" + {"chi;", "χ"}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 246 "HTMLCharacterReference.gperf" + {"Iota;", "Ι"}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 119 "HTMLCharacterReference.gperf" + {"DotDot;", "⃜"}, + {""}, + {""}, +#line 686 "HTMLCharacterReference.gperf" + {"ange;", "⦤"}, #line 822 "HTMLCharacterReference.gperf" - {"boxvr;", "├"}, - {""}, - {""}, -#line 1512 "HTMLCharacterReference.gperf" - {"nfr;", "𝔫"}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 1491 "HTMLCharacterReference.gperf" - {"nbump;", "≎̸"}, - {""}, - {""}, - {""}, - {""}, -#line 243 "HTMLCharacterReference.gperf" - {"Iogon;", "Į"}, - {""}, - {""}, - {""}, -#line 906 "HTMLCharacterReference.gperf" - {"csub;", "⫏"}, -#line 1240 "HTMLCharacterReference.gperf" - {"iprod;", "⨼"}, - {""}, - {""}, - {""}, -#line 1598 "HTMLCharacterReference.gperf" - {"nsup;", "⊅"}, -#line 937 "HTMLCharacterReference.gperf" - {"cwint;", "∱"}, - {""}, - {""}, - {""}, - {""}, -#line 723 "HTMLCharacterReference.gperf" - {"awint;", "⨑"}, - {""}, - {""}, -#line 693 "HTMLCharacterReference.gperf" - {"angmsdaf;", "⦭"}, - {""}, - {""}, - {""}, - {""}, -#line 652 "HTMLCharacterReference.gperf" - {"Zfr;", "ℨ"}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 1621 "HTMLCharacterReference.gperf" - {"nvgt;", ">⃒"}, - {""}, - {""}, - {""}, -#line 868 "HTMLCharacterReference.gperf" - {"chi;", "χ"}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 245 "HTMLCharacterReference.gperf" - {"Iota;", "Ι"}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 118 "HTMLCharacterReference.gperf" - {"DotDot;", "⃜"}, - {""}, - {""}, -#line 685 "HTMLCharacterReference.gperf" - {"ange;", "⦤"}, -#line 821 "HTMLCharacterReference.gperf" - {"boxvl;", "┤"}, - {""}, - {""}, - {""}, - {""}, -#line 2207 "HTMLCharacterReference.gperf" - {"xharr;", "⟷"}, -#line 267 "HTMLCharacterReference.gperf" - {"LT", "<"}, + {"boxvl;", "┤"}, + {""}, + {""}, + {""}, + {""}, +#line 2208 "HTMLCharacterReference.gperf" + {"xharr;", "⟷"}, #line 268 "HTMLCharacterReference.gperf" - {"LT;", "<"}, - {""}, -#line 1206 "HTMLCharacterReference.gperf" - {"iexcl", "¡"}, + {"LT", "<"}, +#line 269 "HTMLCharacterReference.gperf" + {"LT;", "<"}, + {""}, #line 1207 "HTMLCharacterReference.gperf" - {"iexcl;", "¡"}, - {""}, - {""}, - {""}, - {""}, -#line 1587 "HTMLCharacterReference.gperf" - {"nspar;", "∦"}, - {""}, - {""}, - {""}, - {""}, -#line 980 "HTMLCharacterReference.gperf" - {"doteq;", "≐"}, - {""}, - {""}, - {""}, -#line 829 "HTMLCharacterReference.gperf" - {"bsim;", "∽"}, -#line 1154 "HTMLCharacterReference.gperf" - {"gsiml;", "⪐"}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 1416 "HTMLCharacterReference.gperf" - {"ltimes;", "⋉"}, - {""}, -#line 474 "HTMLCharacterReference.gperf" - {"Rfr;", "ℜ"}, - {""}, - {""}, - {""}, - {""}, -#line 1473 "HTMLCharacterReference.gperf" - {"nLt;", "≪⃒"}, - {""}, - {""}, -#line 680 "HTMLCharacterReference.gperf" - {"andand;", "⩕"}, - {""}, -#line 46 "HTMLCharacterReference.gperf" - {"Auml", "Ä"}, -#line 47 "HTMLCharacterReference.gperf" - {"Auml;", "Ä"}, - {""}, - {""}, - {""}, -#line 160 "HTMLCharacterReference.gperf" - {"Efr;", "𝔈"}, - {""}, -#line 1374 "HTMLCharacterReference.gperf" - {"loang;", "⟬"}, - {""}, - {""}, -#line 351 "HTMLCharacterReference.gperf" - {"Not;", "⫬"}, - {""}, -#line 934 "HTMLCharacterReference.gperf" - {"cuvee;", "⋎"}, - {""}, -#line 1501 "HTMLCharacterReference.gperf" - {"ne;", "≠"}, -#line 2205 "HTMLCharacterReference.gperf" - {"xfr;", "𝔵"}, - {""}, -#line 819 "HTMLCharacterReference.gperf" - {"boxvR;", "╞"}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 1768 "HTMLCharacterReference.gperf" - {"qprime;", "⁗"}, - {""}, + {"iexcl", "¡"}, #line 1208 "HTMLCharacterReference.gperf" - {"iff;", "⇔"}, - {""}, -#line 1153 "HTMLCharacterReference.gperf" - {"gsime;", "⪎"}, - {""}, - {""}, -#line 198 "HTMLCharacterReference.gperf" - {"Gfr;", "𝔊"}, - {""}, -#line 1404 "HTMLCharacterReference.gperf" - {"lsime;", "⪍"}, - {""}, - {""}, - {""}, - {""}, -#line 167 "HTMLCharacterReference.gperf" - {"Eogon;", "Ę"}, - {""}, - {""}, - {""}, -#line 724 "HTMLCharacterReference.gperf" - {"bNot;", "⫭"}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 1509 "HTMLCharacterReference.gperf" - {"nesim;", "≂̸"}, - {""}, - {""}, -#line 1552 "HTMLCharacterReference.gperf" - {"notindot;", "⋵̸"}, -#line 683 "HTMLCharacterReference.gperf" - {"andv;", "⩚"}, - {""}, - {""}, - {""}, - {""}, -#line 1120 "HTMLCharacterReference.gperf" - {"gdot;", "ġ"}, -#line 1524 "HTMLCharacterReference.gperf" - {"nhpar;", "⫲"}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 1016 "HTMLCharacterReference.gperf" - {"edot;", "ė"}, - {""}, - {""}, -#line 1386 "HTMLCharacterReference.gperf" - {"lotimes;", "⨴"}, - {""}, -#line 857 "HTMLCharacterReference.gperf" - {"cdot;", "ċ"}, -#line 907 "HTMLCharacterReference.gperf" - {"csube;", "⫑"}, - {""}, -#line 1308 "HTMLCharacterReference.gperf" - {"lbrksld;", "⦏"}, - {""}, - {""}, -#line 1600 "HTMLCharacterReference.gperf" - {"nsupe;", "⊉"}, - {""}, - {""}, - {""}, + {"iexcl;", "¡"}, + {""}, + {""}, + {""}, + {""}, +#line 1588 "HTMLCharacterReference.gperf" + {"nspar;", "∦"}, + {""}, + {""}, + {""}, + {""}, +#line 981 "HTMLCharacterReference.gperf" + {"doteq;", "≐"}, + {""}, + {""}, + {""}, +#line 830 "HTMLCharacterReference.gperf" + {"bsim;", "∽"}, +#line 1155 "HTMLCharacterReference.gperf" + {"gsiml;", "⪐"}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 1417 "HTMLCharacterReference.gperf" + {"ltimes;", "⋉"}, + {""}, +#line 475 "HTMLCharacterReference.gperf" + {"Rfr;", "ℜ"}, + {""}, + {""}, + {""}, + {""}, +#line 1474 "HTMLCharacterReference.gperf" + {"nLt;", "≪⃒"}, + {""}, + {""}, +#line 681 "HTMLCharacterReference.gperf" + {"andand;", "⩕"}, + {""}, +#line 47 "HTMLCharacterReference.gperf" + {"Auml", "Ä"}, +#line 48 "HTMLCharacterReference.gperf" + {"Auml;", "Ä"}, + {""}, + {""}, + {""}, +#line 161 "HTMLCharacterReference.gperf" + {"Efr;", "𝔈"}, + {""}, +#line 1375 "HTMLCharacterReference.gperf" + {"loang;", "⟬"}, + {""}, + {""}, +#line 352 "HTMLCharacterReference.gperf" + {"Not;", "⫬"}, + {""}, +#line 935 "HTMLCharacterReference.gperf" + {"cuvee;", "⋎"}, + {""}, +#line 1502 "HTMLCharacterReference.gperf" + {"ne;", "≠"}, +#line 2206 "HTMLCharacterReference.gperf" + {"xfr;", "𝔵"}, + {""}, +#line 820 "HTMLCharacterReference.gperf" + {"boxvR;", "╞"}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 1769 "HTMLCharacterReference.gperf" + {"qprime;", "⁗"}, + {""}, +#line 1209 "HTMLCharacterReference.gperf" + {"iff;", "⇔"}, + {""}, +#line 1154 "HTMLCharacterReference.gperf" + {"gsime;", "⪎"}, + {""}, + {""}, +#line 199 "HTMLCharacterReference.gperf" + {"Gfr;", "𝔊"}, + {""}, +#line 1405 "HTMLCharacterReference.gperf" + {"lsime;", "⪍"}, + {""}, + {""}, + {""}, + {""}, +#line 168 "HTMLCharacterReference.gperf" + {"Eogon;", "Ę"}, + {""}, + {""}, + {""}, +#line 725 "HTMLCharacterReference.gperf" + {"bNot;", "⫭"}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 1510 "HTMLCharacterReference.gperf" + {"nesim;", "≂̸"}, + {""}, + {""}, +#line 1553 "HTMLCharacterReference.gperf" + {"notindot;", "⋵̸"}, +#line 684 "HTMLCharacterReference.gperf" + {"andv;", "⩚"}, + {""}, + {""}, + {""}, + {""}, +#line 1121 "HTMLCharacterReference.gperf" + {"gdot;", "ġ"}, +#line 1525 "HTMLCharacterReference.gperf" + {"nhpar;", "⫲"}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 1017 "HTMLCharacterReference.gperf" + {"edot;", "ė"}, + {""}, + {""}, +#line 1387 "HTMLCharacterReference.gperf" + {"lotimes;", "⨴"}, + {""}, #line 858 "HTMLCharacterReference.gperf" - {"cedil", "¸"}, + {"cdot;", "ċ"}, +#line 908 "HTMLCharacterReference.gperf" + {"csube;", "⫑"}, + {""}, +#line 1309 "HTMLCharacterReference.gperf" + {"lbrksld;", "⦏"}, + {""}, + {""}, +#line 1601 "HTMLCharacterReference.gperf" + {"nsupe;", "⊉"}, + {""}, + {""}, + {""}, #line 859 "HTMLCharacterReference.gperf" - {"cedil;", "¸"}, - {""}, -#line 950 "HTMLCharacterReference.gperf" - {"dd;", "ⅆ"}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 2188 "HTMLCharacterReference.gperf" - {"vsupne;", "⊋︀"}, - {""}, - {""}, -#line 792 "HTMLCharacterReference.gperf" - {"boxV;", "║"}, + {"cedil", "¸"}, +#line 860 "HTMLCharacterReference.gperf" + {"cedil;", "¸"}, + {""}, +#line 951 "HTMLCharacterReference.gperf" + {"dd;", "ⅆ"}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 2189 "HTMLCharacterReference.gperf" + {"vsupne;", "⊋︀"}, + {""}, + {""}, #line 793 "HTMLCharacterReference.gperf" - {"boxVH;", "╬"}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 824 "HTMLCharacterReference.gperf" - {"breve;", "˘"}, - {""}, - {""}, - {""}, - {""}, -#line 1158 "HTMLCharacterReference.gperf" - {"gtcir;", "⩺"}, - {""}, - {""}, - {""}, - {""}, -#line 1413 "HTMLCharacterReference.gperf" - {"ltcir;", "⩹"}, - {""}, - {""}, - {""}, - {""}, -#line 1504 "HTMLCharacterReference.gperf" - {"nearr;", "↗"}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 470 "HTMLCharacterReference.gperf" - {"Re;", "ℜ"}, - {""}, - {""}, - {""}, -#line 1572 "HTMLCharacterReference.gperf" - {"nrarrc;", "⤳̸"}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 1235 "HTMLCharacterReference.gperf" - {"intprod;", "⨼"}, - {""}, -#line 1244 "HTMLCharacterReference.gperf" - {"isin;", "∈"}, - {""}, + {"boxV;", "║"}, +#line 794 "HTMLCharacterReference.gperf" + {"boxVH;", "╬"}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 825 "HTMLCharacterReference.gperf" + {"breve;", "˘"}, + {""}, + {""}, + {""}, + {""}, +#line 1159 "HTMLCharacterReference.gperf" + {"gtcir;", "⩺"}, + {""}, + {""}, + {""}, + {""}, +#line 1414 "HTMLCharacterReference.gperf" + {"ltcir;", "⩹"}, + {""}, + {""}, + {""}, + {""}, +#line 1505 "HTMLCharacterReference.gperf" + {"nearr;", "↗"}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 471 "HTMLCharacterReference.gperf" + {"Re;", "ℜ"}, + {""}, + {""}, + {""}, +#line 1573 "HTMLCharacterReference.gperf" + {"nrarrc;", "⤳̸"}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 1236 "HTMLCharacterReference.gperf" + {"intprod;", "⨼"}, + {""}, +#line 1245 "HTMLCharacterReference.gperf" + {"isin;", "∈"}, + {""}, +#line 993 "HTMLCharacterReference.gperf" + {"drcrop;", "⌌"}, + {""}, +#line 693 "HTMLCharacterReference.gperf" + {"angmsdae;", "⦬"}, + {""}, +#line 1250 "HTMLCharacterReference.gperf" + {"isinv;", "∈"}, + {""}, +#line 445 "HTMLCharacterReference.gperf" + {"Pr;", "⪻"}, + {""}, + {""}, +#line 799 "HTMLCharacterReference.gperf" + {"boxVr;", "╟"}, + {""}, + {""}, +#line 348 "HTMLCharacterReference.gperf" + {"Nfr;", "𝔑"}, + {""}, + {""}, + {""}, +#line 913 "HTMLCharacterReference.gperf" + {"cudarrr;", "⤵"}, + {""}, +#line 1152 "HTMLCharacterReference.gperf" + {"gscr;", "ℊ"}, +#line 596 "HTMLCharacterReference.gperf" + {"UpTee;", "⊥"}, +#line 1262 "HTMLCharacterReference.gperf" + {"jsercy;", "ј"}, + {""}, +#line 1117 "HTMLCharacterReference.gperf" + {"gap;", "⪆"}, +#line 1402 "HTMLCharacterReference.gperf" + {"lscr;", "𝓁"}, +#line 831 "HTMLCharacterReference.gperf" + {"bsime;", "⋍"}, #line 992 "HTMLCharacterReference.gperf" - {"drcrop;", "⌌"}, - {""}, -#line 692 "HTMLCharacterReference.gperf" - {"angmsdae;", "⦬"}, - {""}, -#line 1249 "HTMLCharacterReference.gperf" - {"isinv;", "∈"}, - {""}, -#line 444 "HTMLCharacterReference.gperf" - {"Pr;", "⪻"}, - {""}, - {""}, -#line 798 "HTMLCharacterReference.gperf" - {"boxVr;", "╟"}, - {""}, - {""}, -#line 347 "HTMLCharacterReference.gperf" - {"Nfr;", "𝔑"}, - {""}, - {""}, - {""}, -#line 912 "HTMLCharacterReference.gperf" - {"cudarrr;", "⤵"}, - {""}, -#line 1151 "HTMLCharacterReference.gperf" - {"gscr;", "ℊ"}, -#line 595 "HTMLCharacterReference.gperf" - {"UpTee;", "⊥"}, + {"drcorn;", "⌟"}, + {""}, +#line 1288 "HTMLCharacterReference.gperf" + {"lap;", "⪅"}, +#line 1060 "HTMLCharacterReference.gperf" + {"escr;", "ℯ"}, + {""}, + {""}, + {""}, +#line 1300 "HTMLCharacterReference.gperf" + {"lat;", "⪫"}, +#line 906 "HTMLCharacterReference.gperf" + {"cscr;", "𝒸"}, + {""}, + {""}, + {""}, +#line 842 "HTMLCharacterReference.gperf" + {"cap;", "∩"}, +#line 715 "HTMLCharacterReference.gperf" + {"ascr;", "𝒶"}, + {""}, +#line 1306 "HTMLCharacterReference.gperf" + {"lbrace;", "{"}, + {""}, + {""}, #line 1261 "HTMLCharacterReference.gperf" - {"jsercy;", "ј"}, - {""}, -#line 1116 "HTMLCharacterReference.gperf" - {"gap;", "⪆"}, -#line 1401 "HTMLCharacterReference.gperf" - {"lscr;", "𝓁"}, -#line 830 "HTMLCharacterReference.gperf" - {"bsime;", "⋍"}, -#line 991 "HTMLCharacterReference.gperf" - {"drcorn;", "⌟"}, - {""}, -#line 1287 "HTMLCharacterReference.gperf" - {"lap;", "⪅"}, -#line 1059 "HTMLCharacterReference.gperf" - {"escr;", "ℯ"}, - {""}, - {""}, - {""}, -#line 1299 "HTMLCharacterReference.gperf" - {"lat;", "⪫"}, -#line 905 "HTMLCharacterReference.gperf" - {"cscr;", "𝒸"}, - {""}, - {""}, - {""}, -#line 841 "HTMLCharacterReference.gperf" - {"cap;", "∩"}, -#line 714 "HTMLCharacterReference.gperf" - {"ascr;", "𝒶"}, - {""}, -#line 1305 "HTMLCharacterReference.gperf" - {"lbrace;", "{"}, - {""}, - {""}, -#line 1260 "HTMLCharacterReference.gperf" - {"jscr;", "𝒿"}, - {""}, - {""}, - {""}, - {""}, -#line 604 "HTMLCharacterReference.gperf" - {"Uscr;", "𝒰"}, -#line 1519 "HTMLCharacterReference.gperf" - {"ngsim;", "≵"}, - {""}, - {""}, - {""}, -#line 1165 "HTMLCharacterReference.gperf" - {"gtreqless;", "⋛"}, -#line 952 "HTMLCharacterReference.gperf" - {"ddarr;", "⇊"}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 1599 "HTMLCharacterReference.gperf" - {"nsupE;", "⫆̸"}, - {""}, - {""}, - {""}, -#line 993 "HTMLCharacterReference.gperf" - {"dscr;", "𝒹"}, - {""}, - {""}, - {""}, + {"jscr;", "𝒿"}, + {""}, + {""}, + {""}, + {""}, +#line 605 "HTMLCharacterReference.gperf" + {"Uscr;", "𝒰"}, #line 1520 "HTMLCharacterReference.gperf" - {"ngt;", "≯"}, -#line 1194 "HTMLCharacterReference.gperf" - {"hscr;", "𝒽"}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 1617 "HTMLCharacterReference.gperf" - {"nvHarr;", "⤄"}, - {""}, -#line 31 "HTMLCharacterReference.gperf" - {"Afr;", "𝔄"}, -#line 1157 "HTMLCharacterReference.gperf" - {"gtcc;", "⪧"}, -#line 889 "HTMLCharacterReference.gperf" - {"comma;", ","}, - {""}, - {""}, - {""}, -#line 1412 "HTMLCharacterReference.gperf" - {"ltcc;", "⪦"}, - {""}, - {""}, - {""}, - {""}, + {"ngsim;", "≵"}, + {""}, + {""}, + {""}, +#line 1166 "HTMLCharacterReference.gperf" + {"gtreqless;", "⋛"}, +#line 953 "HTMLCharacterReference.gperf" + {"ddarr;", "⇊"}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 1600 "HTMLCharacterReference.gperf" + {"nsupE;", "⫆̸"}, + {""}, + {""}, + {""}, +#line 994 "HTMLCharacterReference.gperf" + {"dscr;", "𝒹"}, + {""}, + {""}, + {""}, #line 1521 "HTMLCharacterReference.gperf" - {"ngtr;", "≯"}, - {""}, - {""}, - {""}, -#line 981 "HTMLCharacterReference.gperf" - {"doteqdot;", "≑"}, -#line 1290 "HTMLCharacterReference.gperf" - {"larr;", "←"}, -#line 797 "HTMLCharacterReference.gperf" - {"boxVl;", "╢"}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 459 "HTMLCharacterReference.gperf" - {"Qscr;", "𝒬"}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 37 "HTMLCharacterReference.gperf" - {"Aogon;", "Ą"}, - {""}, -#line 658 "HTMLCharacterReference.gperf" - {"ac;", "∾"}, - {""}, -#line 1110 "HTMLCharacterReference.gperf" - {"fscr;", "𝒻"}, - {""}, - {""}, - {""}, - {""}, -#line 570 "HTMLCharacterReference.gperf" - {"Uarr;", "↟"}, - {""}, - {""}, - {""}, -#line 918 "HTMLCharacterReference.gperf" - {"cupbrcap;", "⩈"}, -#line 2200 "HTMLCharacterReference.gperf" - {"wscr;", "𝓌"}, -#line 1596 "HTMLCharacterReference.gperf" - {"nsucc;", "⊁"}, - {""}, - {""}, - {""}, - {""}, -#line 849 "HTMLCharacterReference.gperf" - {"caron;", "ˇ"}, - {""}, - {""}, - {""}, -#line 2184 "HTMLCharacterReference.gperf" - {"vscr;", "𝓋"}, - {""}, - {""}, - {""}, - {""}, -#line 943 "HTMLCharacterReference.gperf" - {"darr;", "↓"}, - {""}, -#line 1298 "HTMLCharacterReference.gperf" - {"larrtl;", "↢"}, - {""}, -#line 694 "HTMLCharacterReference.gperf" - {"angmsdag;", "⦮"}, -#line 1176 "HTMLCharacterReference.gperf" - {"harr;", "↔"}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 1586 "HTMLCharacterReference.gperf" - {"nsmid;", "∤"}, - {""}, - {""}, - {""}, - {""}, -#line 795 "HTMLCharacterReference.gperf" - {"boxVR;", "╠"}, - {""}, -#line 169 "HTMLCharacterReference.gperf" - {"Epsilon;", "Ε"}, - {""}, -#line 827 "HTMLCharacterReference.gperf" - {"bscr;", "𝒷"}, -#line 596 "HTMLCharacterReference.gperf" - {"UpTeeArrow;", "↥"}, - {""}, - {""}, - {""}, - {""}, + {"ngt;", "≯"}, +#line 1195 "HTMLCharacterReference.gperf" + {"hscr;", "𝒽"}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 1618 "HTMLCharacterReference.gperf" + {"nvHarr;", "⤄"}, + {""}, +#line 32 "HTMLCharacterReference.gperf" + {"Afr;", "𝔄"}, +#line 1158 "HTMLCharacterReference.gperf" + {"gtcc;", "⪧"}, +#line 890 "HTMLCharacterReference.gperf" + {"comma;", ","}, + {""}, + {""}, + {""}, +#line 1413 "HTMLCharacterReference.gperf" + {"ltcc;", "⪦"}, + {""}, + {""}, + {""}, + {""}, +#line 1522 "HTMLCharacterReference.gperf" + {"ngtr;", "≯"}, + {""}, + {""}, + {""}, +#line 982 "HTMLCharacterReference.gperf" + {"doteqdot;", "≑"}, +#line 1291 "HTMLCharacterReference.gperf" + {"larr;", "←"}, +#line 798 "HTMLCharacterReference.gperf" + {"boxVl;", "╢"}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 460 "HTMLCharacterReference.gperf" + {"Qscr;", "𝒬"}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 38 "HTMLCharacterReference.gperf" + {"Aogon;", "Ą"}, + {""}, +#line 659 "HTMLCharacterReference.gperf" + {"ac;", "∾"}, + {""}, +#line 1111 "HTMLCharacterReference.gperf" + {"fscr;", "𝒻"}, + {""}, + {""}, + {""}, + {""}, +#line 571 "HTMLCharacterReference.gperf" + {"Uarr;", "↟"}, + {""}, + {""}, + {""}, +#line 919 "HTMLCharacterReference.gperf" + {"cupbrcap;", "⩈"}, +#line 2201 "HTMLCharacterReference.gperf" + {"wscr;", "𝓌"}, +#line 1597 "HTMLCharacterReference.gperf" + {"nsucc;", "⊁"}, + {""}, + {""}, + {""}, + {""}, +#line 850 "HTMLCharacterReference.gperf" + {"caron;", "ˇ"}, + {""}, + {""}, + {""}, +#line 2185 "HTMLCharacterReference.gperf" + {"vscr;", "𝓋"}, + {""}, + {""}, + {""}, + {""}, +#line 944 "HTMLCharacterReference.gperf" + {"darr;", "↓"}, + {""}, +#line 1299 "HTMLCharacterReference.gperf" + {"larrtl;", "↢"}, + {""}, +#line 695 "HTMLCharacterReference.gperf" + {"angmsdag;", "⦮"}, +#line 1177 "HTMLCharacterReference.gperf" + {"harr;", "↔"}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 1587 "HTMLCharacterReference.gperf" + {"nsmid;", "∤"}, + {""}, + {""}, + {""}, + {""}, +#line 796 "HTMLCharacterReference.gperf" + {"boxVR;", "╠"}, + {""}, #line 170 "HTMLCharacterReference.gperf" - {"Equal;", "⩵"}, - {""}, -#line 199 "HTMLCharacterReference.gperf" - {"Gg;", "⋙"}, - {""}, - {""}, - {""}, -#line 1009 "HTMLCharacterReference.gperf" - {"easter;", "⩮"}, - {""}, - {""}, - {""}, -#line 1304 "HTMLCharacterReference.gperf" - {"lbbrk;", "❲"}, - {""}, - {""}, - {""}, -#line 609 "HTMLCharacterReference.gperf" - {"Vbar;", "⫫"}, -#line 2213 "HTMLCharacterReference.gperf" - {"xodot;", "⨀"}, -#line 1310 "HTMLCharacterReference.gperf" - {"lcaron;", "ľ"}, - {""}, - {""}, - {""}, - {""}, + {"Epsilon;", "Ε"}, + {""}, +#line 828 "HTMLCharacterReference.gperf" + {"bscr;", "𝒷"}, +#line 597 "HTMLCharacterReference.gperf" + {"UpTeeArrow;", "↥"}, + {""}, + {""}, + {""}, + {""}, +#line 171 "HTMLCharacterReference.gperf" + {"Equal;", "⩵"}, + {""}, +#line 200 "HTMLCharacterReference.gperf" + {"Gg;", "⋙"}, + {""}, + {""}, + {""}, #line 1010 "HTMLCharacterReference.gperf" - {"ecaron;", "ě"}, - {""}, - {""}, - {""}, - {""}, -#line 851 "HTMLCharacterReference.gperf" - {"ccaron;", "č"}, - {""}, - {""}, - {""}, - {""}, -#line 1014 "HTMLCharacterReference.gperf" - {"ecolon;", "≕"}, - {""}, - {""}, - {""}, - {""}, -#line 1419 "HTMLCharacterReference.gperf" - {"ltrPar;", "⦖"}, - {""}, - {""}, -#line 651 "HTMLCharacterReference.gperf" - {"Zeta;", "Ζ"}, - {""}, - {""}, - {""}, - {""}, -#line 2159 "HTMLCharacterReference.gperf" - {"varr;", "↕"}, - {""}, -#line 919 "HTMLCharacterReference.gperf" - {"cupcap;", "⩆"}, - {""}, - {""}, - {""}, -#line 1247 "HTMLCharacterReference.gperf" - {"isins;", "⋴"}, + {"easter;", "⩮"}, + {""}, + {""}, + {""}, +#line 1305 "HTMLCharacterReference.gperf" + {"lbbrk;", "❲"}, + {""}, + {""}, + {""}, +#line 610 "HTMLCharacterReference.gperf" + {"Vbar;", "⫫"}, +#line 2214 "HTMLCharacterReference.gperf" + {"xodot;", "⨀"}, +#line 1311 "HTMLCharacterReference.gperf" + {"lcaron;", "ľ"}, + {""}, + {""}, + {""}, + {""}, +#line 1011 "HTMLCharacterReference.gperf" + {"ecaron;", "ě"}, + {""}, + {""}, + {""}, + {""}, +#line 852 "HTMLCharacterReference.gperf" + {"ccaron;", "č"}, + {""}, + {""}, + {""}, + {""}, +#line 1015 "HTMLCharacterReference.gperf" + {"ecolon;", "≕"}, + {""}, + {""}, + {""}, + {""}, +#line 1420 "HTMLCharacterReference.gperf" + {"ltrPar;", "⦖"}, + {""}, + {""}, +#line 652 "HTMLCharacterReference.gperf" + {"Zeta;", "Ζ"}, + {""}, + {""}, + {""}, + {""}, +#line 2160 "HTMLCharacterReference.gperf" + {"varr;", "↕"}, + {""}, +#line 920 "HTMLCharacterReference.gperf" + {"cupcap;", "⩆"}, + {""}, + {""}, + {""}, +#line 1248 "HTMLCharacterReference.gperf" + {"isins;", "⋴"}, +#line 1297 "HTMLCharacterReference.gperf" + {"larrpl;", "⤹"}, + {""}, + {""}, +#line 219 "HTMLCharacterReference.gperf" + {"Hscr;", "ℋ"}, + {""}, +#line 2200 "HTMLCharacterReference.gperf" + {"wreath;", "≀"}, + {""}, + {""}, +#line 188 "HTMLCharacterReference.gperf" + {"Fscr;", "ℱ"}, + {""}, +#line 949 "HTMLCharacterReference.gperf" + {"dcaron;", "ď"}, + {""}, +#line 213 "HTMLCharacterReference.gperf" + {"Hat;", "^"}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 1244 "HTMLCharacterReference.gperf" + {"iscr;", "𝒾"}, + {""}, + {""}, + {""}, +#line 1396 "HTMLCharacterReference.gperf" + {"lrcorner;", "⌟"}, + {""}, + {""}, + {""}, + {""}, +#line 628 "HTMLCharacterReference.gperf" + {"Wfr;", "𝔚"}, + {""}, + {""}, + {""}, + {""}, +#line 37 "HTMLCharacterReference.gperf" + {"And;", "⩓"}, + {""}, + {""}, #line 1296 "HTMLCharacterReference.gperf" - {"larrpl;", "⤹"}, - {""}, - {""}, -#line 218 "HTMLCharacterReference.gperf" - {"Hscr;", "ℋ"}, - {""}, -#line 2199 "HTMLCharacterReference.gperf" - {"wreath;", "≀"}, - {""}, - {""}, -#line 187 "HTMLCharacterReference.gperf" - {"Fscr;", "ℱ"}, - {""}, -#line 948 "HTMLCharacterReference.gperf" - {"dcaron;", "ď"}, - {""}, -#line 212 "HTMLCharacterReference.gperf" - {"Hat;", "^"}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 1243 "HTMLCharacterReference.gperf" - {"iscr;", "𝒾"}, - {""}, - {""}, - {""}, -#line 1395 "HTMLCharacterReference.gperf" - {"lrcorner;", "⌟"}, - {""}, - {""}, - {""}, - {""}, -#line 627 "HTMLCharacterReference.gperf" - {"Wfr;", "𝔚"}, - {""}, - {""}, - {""}, - {""}, -#line 36 "HTMLCharacterReference.gperf" - {"And;", "⩓"}, - {""}, - {""}, -#line 1295 "HTMLCharacterReference.gperf" - {"larrlp;", "↫"}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 863 "HTMLCharacterReference.gperf" - {"centerdot;", "·"}, - {""}, - {""}, - {""}, -#line 1514 "HTMLCharacterReference.gperf" - {"nge;", "≱"}, -#line 643 "HTMLCharacterReference.gperf" - {"Yscr;", "𝒴"}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 860 "HTMLCharacterReference.gperf" - {"cemptyv;", "⦲"}, - {""}, -#line 894 "HTMLCharacterReference.gperf" - {"complexes;", "ℂ"}, -#line 1376 "HTMLCharacterReference.gperf" - {"lobrk;", "⟦"}, -#line 189 "HTMLCharacterReference.gperf" - {"GT", ">"}, + {"larrlp;", "↫"}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 864 "HTMLCharacterReference.gperf" + {"centerdot;", "·"}, + {""}, + {""}, + {""}, +#line 1515 "HTMLCharacterReference.gperf" + {"nge;", "≱"}, +#line 644 "HTMLCharacterReference.gperf" + {"Yscr;", "𝒴"}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 861 "HTMLCharacterReference.gperf" + {"cemptyv;", "⦲"}, + {""}, +#line 895 "HTMLCharacterReference.gperf" + {"complexes;", "ℂ"}, +#line 1377 "HTMLCharacterReference.gperf" + {"lobrk;", "⟦"}, #line 190 "HTMLCharacterReference.gperf" - {"GT;", ">"}, - {""}, -#line 1583 "HTMLCharacterReference.gperf" - {"nsim;", "≁"}, - {""}, -#line 1053 "HTMLCharacterReference.gperf" - {"equest;", "≟"}, - {""}, - {""}, -#line 1567 "HTMLCharacterReference.gperf" - {"npre;", "⪯̸"}, - {""}, -#line 890 "HTMLCharacterReference.gperf" - {"commat;", "@"}, - {""}, - {""}, -#line 322 "HTMLCharacterReference.gperf" - {"Lscr;", "ℒ"}, -#line 893 "HTMLCharacterReference.gperf" - {"complement;", "∁"}, - {""}, - {""}, -#line 2237 "HTMLCharacterReference.gperf" - {"yuml", "ÿ"}, + {"GT", ">"}, +#line 191 "HTMLCharacterReference.gperf" + {"GT;", ">"}, + {""}, +#line 1584 "HTMLCharacterReference.gperf" + {"nsim;", "≁"}, + {""}, +#line 1054 "HTMLCharacterReference.gperf" + {"equest;", "≟"}, + {""}, + {""}, +#line 1568 "HTMLCharacterReference.gperf" + {"npre;", "⪯̸"}, + {""}, +#line 891 "HTMLCharacterReference.gperf" + {"commat;", "@"}, + {""}, + {""}, +#line 323 "HTMLCharacterReference.gperf" + {"Lscr;", "ℒ"}, +#line 894 "HTMLCharacterReference.gperf" + {"complement;", "∁"}, + {""}, + {""}, #line 2238 "HTMLCharacterReference.gperf" - {"yuml;", "ÿ"}, - {""}, - {""}, -#line 1201 "HTMLCharacterReference.gperf" - {"ic;", "⁣"}, - {""}, - {""}, -#line 1245 "HTMLCharacterReference.gperf" - {"isinE;", "⋹"}, - {""}, - {""}, -#line 555 "HTMLCharacterReference.gperf" - {"Tfr;", "𝔗"}, -#line 2251 "HTMLCharacterReference.gperf" - {"zwnj;", "‌"}, - {""}, - {""}, -#line 957 "HTMLCharacterReference.gperf" - {"demptyv;", "⦱"}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 1590 "HTMLCharacterReference.gperf" - {"nsub;", "⊄"}, - {""}, -#line 1510 "HTMLCharacterReference.gperf" - {"nexist;", "∄"}, -#line 1002 "HTMLCharacterReference.gperf" - {"dwangle;", "⦦"}, - {""}, -#line 1769 "HTMLCharacterReference.gperf" - {"qscr;", "𝓆"}, - {""}, - {""}, -#line 1461 "HTMLCharacterReference.gperf" - {"mp;", "∓"}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 438 "HTMLCharacterReference.gperf" - {"Pfr;", "𝔓"}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 1117 "HTMLCharacterReference.gperf" - {"gbreve;", "ğ"}, - {""}, -#line 2245 "HTMLCharacterReference.gperf" - {"zfr;", "𝔷"}, -#line 994 "HTMLCharacterReference.gperf" - {"dscy;", "ѕ"}, -#line 1357 "HTMLCharacterReference.gperf" - {"lhblk;", "▄"}, - {""}, - {""}, - {""}, -#line 333 "HTMLCharacterReference.gperf" - {"Mscr;", "ℳ"}, - {""}, -#line 1614 "HTMLCharacterReference.gperf" - {"numero;", "№"}, - {""}, -#line 326 "HTMLCharacterReference.gperf" - {"Map;", "⤅"}, - {""}, - {""}, -#line 1492 "HTMLCharacterReference.gperf" - {"nbumpe;", "≏̸"}, - {""}, - {""}, - {""}, - {""}, -#line 657 "HTMLCharacterReference.gperf" - {"abreve;", "ă"}, -#line 1464 "HTMLCharacterReference.gperf" - {"mu;", "μ"}, - {""}, -#line 273 "HTMLCharacterReference.gperf" - {"Larr;", "↞"}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 573 "HTMLCharacterReference.gperf" - {"Ubreve;", "Ŭ"}, - {""}, - {""}, - {""}, -#line 1506 "HTMLCharacterReference.gperf" - {"nedot;", "≐̸"}, -#line 2216 "HTMLCharacterReference.gperf" - {"xotime;", "⨂"}, - {""}, - {""}, - {""}, - {""}, -#line 1231 "HTMLCharacterReference.gperf" - {"intcal;", "⊺"}, - {""}, - {""}, -#line 2158 "HTMLCharacterReference.gperf" - {"varpropto;", "∝"}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 352 "HTMLCharacterReference.gperf" - {"NotCongruent;", "≢"}, - {""}, - {""}, -#line 1021 "HTMLCharacterReference.gperf" - {"egrave", "è"}, + {"yuml", "ÿ"}, +#line 2239 "HTMLCharacterReference.gperf" + {"yuml;", "ÿ"}, + {""}, + {""}, +#line 1202 "HTMLCharacterReference.gperf" + {"ic;", "⁣"}, + {""}, + {""}, +#line 1246 "HTMLCharacterReference.gperf" + {"isinE;", "⋹"}, + {""}, + {""}, +#line 556 "HTMLCharacterReference.gperf" + {"Tfr;", "𝔗"}, +#line 2252 "HTMLCharacterReference.gperf" + {"zwnj;", "‌"}, + {""}, + {""}, +#line 958 "HTMLCharacterReference.gperf" + {"demptyv;", "⦱"}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 1591 "HTMLCharacterReference.gperf" + {"nsub;", "⊄"}, + {""}, +#line 1511 "HTMLCharacterReference.gperf" + {"nexist;", "∄"}, +#line 1003 "HTMLCharacterReference.gperf" + {"dwangle;", "⦦"}, + {""}, +#line 1770 "HTMLCharacterReference.gperf" + {"qscr;", "𝓆"}, + {""}, + {""}, +#line 1462 "HTMLCharacterReference.gperf" + {"mp;", "∓"}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 439 "HTMLCharacterReference.gperf" + {"Pfr;", "𝔓"}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 1118 "HTMLCharacterReference.gperf" + {"gbreve;", "ğ"}, + {""}, +#line 2246 "HTMLCharacterReference.gperf" + {"zfr;", "𝔷"}, +#line 995 "HTMLCharacterReference.gperf" + {"dscy;", "ѕ"}, +#line 1358 "HTMLCharacterReference.gperf" + {"lhblk;", "▄"}, + {""}, + {""}, + {""}, +#line 334 "HTMLCharacterReference.gperf" + {"Mscr;", "ℳ"}, + {""}, +#line 1615 "HTMLCharacterReference.gperf" + {"numero;", "№"}, + {""}, +#line 327 "HTMLCharacterReference.gperf" + {"Map;", "⤅"}, + {""}, + {""}, +#line 1493 "HTMLCharacterReference.gperf" + {"nbumpe;", "≏̸"}, + {""}, + {""}, + {""}, + {""}, +#line 658 "HTMLCharacterReference.gperf" + {"abreve;", "ă"}, +#line 1465 "HTMLCharacterReference.gperf" + {"mu;", "μ"}, + {""}, +#line 274 "HTMLCharacterReference.gperf" + {"Larr;", "↞"}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 574 "HTMLCharacterReference.gperf" + {"Ubreve;", "Ŭ"}, + {""}, + {""}, + {""}, +#line 1507 "HTMLCharacterReference.gperf" + {"nedot;", "≐̸"}, +#line 2217 "HTMLCharacterReference.gperf" + {"xotime;", "⨂"}, + {""}, + {""}, + {""}, + {""}, +#line 1232 "HTMLCharacterReference.gperf" + {"intcal;", "⊺"}, + {""}, + {""}, +#line 2159 "HTMLCharacterReference.gperf" + {"varpropto;", "∝"}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 353 "HTMLCharacterReference.gperf" + {"NotCongruent;", "≢"}, + {""}, + {""}, #line 1022 "HTMLCharacterReference.gperf" - {"egrave;", "è"}, - {""}, - {""}, -#line 865 "HTMLCharacterReference.gperf" - {"chcy;", "ч"}, -#line 850 "HTMLCharacterReference.gperf" - {"ccaps;", "⩍"}, -#line 1074 "HTMLCharacterReference.gperf" - {"female;", "♀"}, -#line 740 "HTMLCharacterReference.gperf" - {"bemptyv;", "⦰"}, - {""}, - {""}, -#line 670 "HTMLCharacterReference.gperf" - {"agrave", "à"}, + {"egrave", "è"}, +#line 1023 "HTMLCharacterReference.gperf" + {"egrave;", "è"}, + {""}, + {""}, +#line 866 "HTMLCharacterReference.gperf" + {"chcy;", "ч"}, +#line 851 "HTMLCharacterReference.gperf" + {"ccaps;", "⩍"}, +#line 1075 "HTMLCharacterReference.gperf" + {"female;", "♀"}, +#line 741 "HTMLCharacterReference.gperf" + {"bemptyv;", "⦰"}, + {""}, + {""}, #line 671 "HTMLCharacterReference.gperf" - {"agrave;", "à"}, - {""}, - {""}, - {""}, - {""}, -#line 1174 "HTMLCharacterReference.gperf" - {"hamilt;", "ℋ"}, - {""}, - {""}, -#line 174 "HTMLCharacterReference.gperf" - {"Esim;", "⩳"}, -#line 579 "HTMLCharacterReference.gperf" - {"Ugrave", "Ù"}, + {"agrave", "à"}, +#line 672 "HTMLCharacterReference.gperf" + {"agrave;", "à"}, + {""}, + {""}, + {""}, + {""}, +#line 1175 "HTMLCharacterReference.gperf" + {"hamilt;", "ℋ"}, + {""}, + {""}, +#line 175 "HTMLCharacterReference.gperf" + {"Esim;", "⩳"}, #line 580 "HTMLCharacterReference.gperf" - {"Ugrave;", "Ù"}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 148 "HTMLCharacterReference.gperf" - {"Dscr;", "𝒟"}, -#line 2173 "HTMLCharacterReference.gperf" - {"veeeq;", "≚"}, -#line 710 "HTMLCharacterReference.gperf" - {"approx;", "≈"}, - {""}, -#line 621 "HTMLCharacterReference.gperf" - {"Vfr;", "𝔙"}, -#line 1474 "HTMLCharacterReference.gperf" - {"nLtv;", "≪̸"}, -#line 1407 "HTMLCharacterReference.gperf" - {"lsquo;", "‘"}, + {"Ugrave", "Ù"}, +#line 581 "HTMLCharacterReference.gperf" + {"Ugrave;", "Ù"}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 149 "HTMLCharacterReference.gperf" + {"Dscr;", "𝒟"}, +#line 2174 "HTMLCharacterReference.gperf" + {"veeeq;", "≚"}, +#line 711 "HTMLCharacterReference.gperf" + {"approx;", "≈"}, + {""}, +#line 622 "HTMLCharacterReference.gperf" + {"Vfr;", "𝔙"}, +#line 1475 "HTMLCharacterReference.gperf" + {"nLtv;", "≪̸"}, #line 1408 "HTMLCharacterReference.gperf" - {"lsquor;", "‚"}, -#line 151 "HTMLCharacterReference.gperf" - {"ETH", "Ð"}, -#line 582 "HTMLCharacterReference.gperf" - {"UnderBar;", "_"}, - {""}, - {""}, - {""}, - {""}, + {"lsquo;", "‘"}, +#line 1409 "HTMLCharacterReference.gperf" + {"lsquor;", "‚"}, #line 152 "HTMLCharacterReference.gperf" - {"ETH;", "Ð"}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 274 "HTMLCharacterReference.gperf" - {"Lcaron;", "Ľ"}, - {""}, - {""}, - {""}, - {""}, -#line 256 "HTMLCharacterReference.gperf" - {"Jsercy;", "Ј"}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 1515 "HTMLCharacterReference.gperf" - {"ngeq;", "≱"}, + {"ETH", "Ð"}, +#line 583 "HTMLCharacterReference.gperf" + {"UnderBar;", "_"}, + {""}, + {""}, + {""}, + {""}, +#line 153 "HTMLCharacterReference.gperf" + {"ETH;", "Ð"}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 275 "HTMLCharacterReference.gperf" + {"Lcaron;", "Ľ"}, + {""}, + {""}, + {""}, + {""}, +#line 257 "HTMLCharacterReference.gperf" + {"Jsercy;", "Ј"}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, #line 1516 "HTMLCharacterReference.gperf" - {"ngeqq;", "≧̸"}, - {""}, - {""}, - {""}, - {""}, -#line 1584 "HTMLCharacterReference.gperf" - {"nsime;", "≄"}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 255 "HTMLCharacterReference.gperf" - {"Jscr;", "𝒥"}, - {""}, - {""}, - {""}, - {""}, -#line 230 "HTMLCharacterReference.gperf" - {"Idot;", "İ"}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 1241 "HTMLCharacterReference.gperf" - {"iquest", "¿"}, + {"ngeq;", "≱"}, +#line 1517 "HTMLCharacterReference.gperf" + {"ngeqq;", "≧̸"}, + {""}, + {""}, + {""}, + {""}, +#line 1585 "HTMLCharacterReference.gperf" + {"nsime;", "≄"}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 256 "HTMLCharacterReference.gperf" + {"Jscr;", "𝒥"}, + {""}, + {""}, + {""}, + {""}, +#line 231 "HTMLCharacterReference.gperf" + {"Idot;", "İ"}, + {""}, + {""}, + {""}, + {""}, + {""}, #line 1242 "HTMLCharacterReference.gperf" - {"iquest;", "¿"}, - {""}, - {""}, - {""}, -#line 1405 "HTMLCharacterReference.gperf" - {"lsimg;", "⪏"}, - {""}, - {""}, - {""}, -#line 633 "HTMLCharacterReference.gperf" - {"Xscr;", "𝒳"}, -#line 1312 "HTMLCharacterReference.gperf" - {"lceil;", "⌈"}, - {""}, - {""}, - {""}, -#line 102 "HTMLCharacterReference.gperf" - {"Darr;", "↡"}, -#line 1592 "HTMLCharacterReference.gperf" - {"nsube;", "⊈"}, -#line 523 "HTMLCharacterReference.gperf" - {"Square;", "□"}, - {""}, - {""}, -#line 712 "HTMLCharacterReference.gperf" - {"aring", "å"}, + {"iquest", "¿"}, +#line 1243 "HTMLCharacterReference.gperf" + {"iquest;", "¿"}, + {""}, + {""}, + {""}, +#line 1406 "HTMLCharacterReference.gperf" + {"lsimg;", "⪏"}, + {""}, + {""}, + {""}, +#line 634 "HTMLCharacterReference.gperf" + {"Xscr;", "𝒳"}, +#line 1313 "HTMLCharacterReference.gperf" + {"lceil;", "⌈"}, + {""}, + {""}, + {""}, +#line 103 "HTMLCharacterReference.gperf" + {"Darr;", "↡"}, +#line 1593 "HTMLCharacterReference.gperf" + {"nsube;", "⊈"}, +#line 524 "HTMLCharacterReference.gperf" + {"Square;", "□"}, + {""}, + {""}, #line 713 "HTMLCharacterReference.gperf" - {"aring;", "å"}, -#line 1047 "HTMLCharacterReference.gperf" - {"eqcirc;", "≖"}, - {""}, - {""}, -#line 663 "HTMLCharacterReference.gperf" - {"acute", "´"}, -#line 664 "HTMLCharacterReference.gperf" - {"acute;", "´"}, - {""}, - {""}, -#line 1444 "HTMLCharacterReference.gperf" - {"mho;", "℧"}, - {""}, -#line 603 "HTMLCharacterReference.gperf" - {"Uring;", "Ů"}, -#line 2186 "HTMLCharacterReference.gperf" - {"vsubne;", "⊊︀"}, - {""}, -#line 2232 "HTMLCharacterReference.gperf" - {"yfr;", "𝔶"}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 1568 "HTMLCharacterReference.gperf" - {"nprec;", "⊀"}, - {""}, - {""}, - {""}, -#line 649 "HTMLCharacterReference.gperf" - {"Zdot;", "Ż"}, -#line 716 "HTMLCharacterReference.gperf" - {"asymp;", "≈"}, - {""}, - {""}, - {""}, -#line 530 "HTMLCharacterReference.gperf" - {"Sscr;", "𝒮"}, - {""}, - {""}, - {""}, - {""}, -#line 286 "HTMLCharacterReference.gperf" - {"LeftFloor;", "⌊"}, -#line 2180 "HTMLCharacterReference.gperf" - {"vnsup;", "⊃⃒"}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, + {"aring", "å"}, +#line 714 "HTMLCharacterReference.gperf" + {"aring;", "å"}, #line 1048 "HTMLCharacterReference.gperf" - {"eqcolon;", "≕"}, - {""}, -#line 1236 "HTMLCharacterReference.gperf" - {"iocy;", "ё"}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 842 "HTMLCharacterReference.gperf" - {"capand;", "⩄"}, - {""}, - {""}, - {""}, - {""}, -#line 1585 "HTMLCharacterReference.gperf" - {"nsimeq;", "≄"}, - {""}, -#line 542 "HTMLCharacterReference.gperf" - {"Superset;", "⊃"}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 1620 "HTMLCharacterReference.gperf" - {"nvge;", "≥⃒"}, -#line 1291 "HTMLCharacterReference.gperf" - {"larrb;", "⇤"}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 1463 "HTMLCharacterReference.gperf" - {"mstpos;", "∾"}, - {""}, -#line 1577 "HTMLCharacterReference.gperf" - {"nsc;", "⊁"}, -#line 246 "HTMLCharacterReference.gperf" - {"Iscr;", "ℐ"}, -#line 855 "HTMLCharacterReference.gperf" - {"ccups;", "⩌"}, -#line 104 "HTMLCharacterReference.gperf" - {"Dcaron;", "Ď"}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 1511 "HTMLCharacterReference.gperf" - {"nexists;", "∄"}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 660 "HTMLCharacterReference.gperf" - {"acd;", "∿"}, - {""}, -#line 1210 "HTMLCharacterReference.gperf" - {"igrave", "ì"}, + {"eqcirc;", "≖"}, + {""}, + {""}, +#line 664 "HTMLCharacterReference.gperf" + {"acute", "´"}, +#line 665 "HTMLCharacterReference.gperf" + {"acute;", "´"}, + {""}, + {""}, +#line 1445 "HTMLCharacterReference.gperf" + {"mho;", "℧"}, + {""}, +#line 604 "HTMLCharacterReference.gperf" + {"Uring;", "Ů"}, +#line 2187 "HTMLCharacterReference.gperf" + {"vsubne;", "⊊︀"}, + {""}, +#line 2233 "HTMLCharacterReference.gperf" + {"yfr;", "𝔶"}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 1569 "HTMLCharacterReference.gperf" + {"nprec;", "⊀"}, + {""}, + {""}, + {""}, +#line 650 "HTMLCharacterReference.gperf" + {"Zdot;", "Ż"}, +#line 717 "HTMLCharacterReference.gperf" + {"asymp;", "≈"}, + {""}, + {""}, + {""}, +#line 531 "HTMLCharacterReference.gperf" + {"Sscr;", "𝒮"}, + {""}, + {""}, + {""}, + {""}, +#line 287 "HTMLCharacterReference.gperf" + {"LeftFloor;", "⌊"}, +#line 2181 "HTMLCharacterReference.gperf" + {"vnsup;", "⊃⃒"}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 1049 "HTMLCharacterReference.gperf" + {"eqcolon;", "≕"}, + {""}, +#line 1237 "HTMLCharacterReference.gperf" + {"iocy;", "ё"}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 843 "HTMLCharacterReference.gperf" + {"capand;", "⩄"}, + {""}, + {""}, + {""}, + {""}, +#line 1586 "HTMLCharacterReference.gperf" + {"nsimeq;", "≄"}, + {""}, +#line 543 "HTMLCharacterReference.gperf" + {"Superset;", "⊃"}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 1621 "HTMLCharacterReference.gperf" + {"nvge;", "≥⃒"}, +#line 1292 "HTMLCharacterReference.gperf" + {"larrb;", "⇤"}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 1464 "HTMLCharacterReference.gperf" + {"mstpos;", "∾"}, + {""}, +#line 1578 "HTMLCharacterReference.gperf" + {"nsc;", "⊁"}, +#line 247 "HTMLCharacterReference.gperf" + {"Iscr;", "ℐ"}, +#line 856 "HTMLCharacterReference.gperf" + {"ccups;", "⩌"}, +#line 105 "HTMLCharacterReference.gperf" + {"Dcaron;", "Ď"}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 1512 "HTMLCharacterReference.gperf" + {"nexists;", "∄"}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 661 "HTMLCharacterReference.gperf" + {"acd;", "∿"}, + {""}, #line 1211 "HTMLCharacterReference.gperf" - {"igrave;", "ì"}, - {""}, - {""}, -#line 1580 "HTMLCharacterReference.gperf" - {"nscr;", "𝓃"}, - {""}, - {""}, - {""}, -#line 1481 "HTMLCharacterReference.gperf" - {"nap;", "≉"}, - {""}, - {""}, - {""}, - {""}, -#line 691 "HTMLCharacterReference.gperf" - {"angmsdad;", "⦫"}, - {""}, - {""}, - {""}, -#line 509 "HTMLCharacterReference.gperf" - {"Sc;", "⪼"}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 635 "HTMLCharacterReference.gperf" - {"YIcy;", "Ї"}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 1591 "HTMLCharacterReference.gperf" - {"nsubE;", "⫅̸"}, - {""}, - {""}, - {""}, -#line 159 "HTMLCharacterReference.gperf" - {"Edot;", "Ė"}, - {""}, - {""}, - {""}, - {""}, -#line 654 "HTMLCharacterReference.gperf" - {"Zscr;", "𝒵"}, - {""}, - {""}, - {""}, - {""}, + {"igrave", "ì"}, +#line 1212 "HTMLCharacterReference.gperf" + {"igrave;", "ì"}, + {""}, + {""}, +#line 1581 "HTMLCharacterReference.gperf" + {"nscr;", "𝓃"}, + {""}, + {""}, + {""}, +#line 1482 "HTMLCharacterReference.gperf" + {"nap;", "≉"}, + {""}, + {""}, + {""}, + {""}, +#line 692 "HTMLCharacterReference.gperf" + {"angmsdad;", "⦫"}, + {""}, + {""}, + {""}, +#line 510 "HTMLCharacterReference.gperf" + {"Sc;", "⪼"}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, #line 636 "HTMLCharacterReference.gperf" - {"YUcy;", "Ю"}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 278 "HTMLCharacterReference.gperf" - {"LeftArrow;", "←"}, -#line 815 "HTMLCharacterReference.gperf" - {"boxur;", "└"}, - {""}, - {""}, -#line 1443 "HTMLCharacterReference.gperf" - {"mfr;", "𝔪"}, -#line 197 "HTMLCharacterReference.gperf" - {"Gdot;", "Ġ"}, - {""}, -#line 43 "HTMLCharacterReference.gperf" - {"Assign;", "≔"}, - {""}, - {""}, - {""}, - {""}, - {""}, + {"YIcy;", "Ї"}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 1592 "HTMLCharacterReference.gperf" + {"nsubE;", "⫅̸"}, + {""}, + {""}, + {""}, +#line 160 "HTMLCharacterReference.gperf" + {"Edot;", "Ė"}, + {""}, + {""}, + {""}, + {""}, +#line 655 "HTMLCharacterReference.gperf" + {"Zscr;", "𝒵"}, + {""}, + {""}, + {""}, + {""}, +#line 637 "HTMLCharacterReference.gperf" + {"YUcy;", "Ю"}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, #line 279 "HTMLCharacterReference.gperf" - {"LeftArrowBar;", "⇤"}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 532 "HTMLCharacterReference.gperf" - {"Sub;", "⋐"}, -#line 615 "HTMLCharacterReference.gperf" - {"Vert;", "‖"}, - {""}, - {""}, - {""}, -#line 263 "HTMLCharacterReference.gperf" - {"Kfr;", "𝔎"}, - {""}, - {""}, - {""}, - {""}, -#line 688 "HTMLCharacterReference.gperf" - {"angmsdaa;", "⦨"}, - {""}, - {""}, - {""}, - {""}, -#line 94 "HTMLCharacterReference.gperf" - {"Cup;", "⋓"}, - {""}, -#line 825 "HTMLCharacterReference.gperf" - {"brvbar", "¦"}, + {"LeftArrow;", "←"}, +#line 816 "HTMLCharacterReference.gperf" + {"boxur;", "└"}, + {""}, + {""}, +#line 1444 "HTMLCharacterReference.gperf" + {"mfr;", "𝔪"}, +#line 198 "HTMLCharacterReference.gperf" + {"Gdot;", "Ġ"}, + {""}, +#line 44 "HTMLCharacterReference.gperf" + {"Assign;", "≔"}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 280 "HTMLCharacterReference.gperf" + {"LeftArrowBar;", "⇤"}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 533 "HTMLCharacterReference.gperf" + {"Sub;", "⋐"}, +#line 616 "HTMLCharacterReference.gperf" + {"Vert;", "‖"}, + {""}, + {""}, + {""}, +#line 264 "HTMLCharacterReference.gperf" + {"Kfr;", "𝔎"}, + {""}, + {""}, + {""}, + {""}, +#line 689 "HTMLCharacterReference.gperf" + {"angmsdaa;", "⦨"}, + {""}, + {""}, + {""}, + {""}, +#line 95 "HTMLCharacterReference.gperf" + {"Cup;", "⋓"}, + {""}, #line 826 "HTMLCharacterReference.gperf" - {"brvbar;", "¦"}, - {""}, - {""}, - {""}, -#line 1050 "HTMLCharacterReference.gperf" - {"eqslantgtr;", "⪖"}, -#line 510 "HTMLCharacterReference.gperf" - {"Scaron;", "Š"}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 783 "HTMLCharacterReference.gperf" - {"boxH;", "═"}, - {""}, - {""}, -#line 500 "HTMLCharacterReference.gperf" - {"RoundImplies;", "⥰"}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 502 "HTMLCharacterReference.gperf" - {"Rscr;", "ℛ"}, - {""}, - {""}, - {""}, -#line 711 "HTMLCharacterReference.gperf" - {"approxeq;", "≊"}, - {""}, -#line 1466 "HTMLCharacterReference.gperf" - {"mumap;", "⊸"}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 173 "HTMLCharacterReference.gperf" - {"Escr;", "ℰ"}, - {""}, - {""}, - {""}, -#line 1142 "HTMLCharacterReference.gperf" - {"gnE;", "≩"}, - {""}, -#line 814 "HTMLCharacterReference.gperf" - {"boxul;", "┘"}, - {""}, - {""}, -#line 1367 "HTMLCharacterReference.gperf" - {"lnE;", "≨"}, -#line 2219 "HTMLCharacterReference.gperf" - {"xscr;", "𝓍"}, - {""}, - {""}, - {""}, - {""}, -#line 1517 "HTMLCharacterReference.gperf" - {"ngeqslant;", "⩾̸"}, - {""}, - {""}, - {""}, -#line 705 "HTMLCharacterReference.gperf" - {"apE;", "⩰"}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 208 "HTMLCharacterReference.gperf" - {"Gscr;", "𝒢"}, - {""}, + {"brvbar", "¦"}, +#line 827 "HTMLCharacterReference.gperf" + {"brvbar;", "¦"}, + {""}, + {""}, + {""}, +#line 1051 "HTMLCharacterReference.gperf" + {"eqslantgtr;", "⪖"}, +#line 511 "HTMLCharacterReference.gperf" + {"Scaron;", "Š"}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 784 "HTMLCharacterReference.gperf" + {"boxH;", "═"}, + {""}, + {""}, +#line 501 "HTMLCharacterReference.gperf" + {"RoundImplies;", "⥰"}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 503 "HTMLCharacterReference.gperf" + {"Rscr;", "ℛ"}, + {""}, + {""}, + {""}, +#line 712 "HTMLCharacterReference.gperf" + {"approxeq;", "≊"}, + {""}, +#line 1467 "HTMLCharacterReference.gperf" + {"mumap;", "⊸"}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 174 "HTMLCharacterReference.gperf" + {"Escr;", "ℰ"}, + {""}, + {""}, + {""}, +#line 1143 "HTMLCharacterReference.gperf" + {"gnE;", "≩"}, + {""}, +#line 815 "HTMLCharacterReference.gperf" + {"boxul;", "┘"}, + {""}, + {""}, +#line 1368 "HTMLCharacterReference.gperf" + {"lnE;", "≨"}, +#line 2220 "HTMLCharacterReference.gperf" + {"xscr;", "𝓍"}, + {""}, + {""}, + {""}, + {""}, +#line 1518 "HTMLCharacterReference.gperf" + {"ngeqslant;", "⩾̸"}, + {""}, + {""}, + {""}, +#line 706 "HTMLCharacterReference.gperf" + {"apE;", "⩰"}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 209 "HTMLCharacterReference.gperf" + {"Gscr;", "𝒢"}, + {""}, +#line 1495 "HTMLCharacterReference.gperf" + {"ncaron;", "ň"}, + {""}, + {""}, #line 1494 "HTMLCharacterReference.gperf" - {"ncaron;", "ň"}, - {""}, - {""}, -#line 1493 "HTMLCharacterReference.gperf" - {"ncap;", "⩃"}, - {""}, - {""}, - {""}, -#line 150 "HTMLCharacterReference.gperf" - {"ENG;", "Ŋ"}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 2223 "HTMLCharacterReference.gperf" - {"xvee;", "⋁"}, - {""}, - {""}, -#line 375 "HTMLCharacterReference.gperf" - {"NotLessSlantEqual;", "⩽̸"}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 2244 "HTMLCharacterReference.gperf" - {"zeta;", "ζ"}, - {""}, - {""}, -#line 371 "HTMLCharacterReference.gperf" - {"NotLess;", "≮"}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 465 "HTMLCharacterReference.gperf" - {"Rarr;", "↠"}, - {""}, -#line 647 "HTMLCharacterReference.gperf" - {"Zcaron;", "Ž"}, - {""}, - {""}, - {""}, -#line 813 "HTMLCharacterReference.gperf" - {"boxuR;", "╘"}, - {""}, -#line 2230 "HTMLCharacterReference.gperf" - {"yen", "¥"}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 2231 "HTMLCharacterReference.gperf" - {"yen;", "¥"}, - {""}, - {""}, -#line 1193 "HTMLCharacterReference.gperf" - {"horbar;", "―"}, - {""}, -#line 690 "HTMLCharacterReference.gperf" - {"angmsdac;", "⦪"}, -#line 556 "HTMLCharacterReference.gperf" - {"Therefore;", "∴"}, - {""}, - {""}, - {""}, - {""}, -#line 1301 "HTMLCharacterReference.gperf" - {"late;", "⪭"}, -#line 1484 "HTMLCharacterReference.gperf" - {"napos;", "ʼn"}, - {""}, - {""}, -#line 613 "HTMLCharacterReference.gperf" - {"Vee;", "⋁"}, -#line 99 "HTMLCharacterReference.gperf" - {"DScy;", "Ѕ"}, - {""}, - {""}, - {""}, -#line 1190 "HTMLCharacterReference.gperf" - {"hookleftarrow;", "↩"}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, + {"ncap;", "⩃"}, + {""}, + {""}, + {""}, +#line 151 "HTMLCharacterReference.gperf" + {"ENG;", "Ŋ"}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 2224 "HTMLCharacterReference.gperf" + {"xvee;", "⋁"}, + {""}, + {""}, +#line 376 "HTMLCharacterReference.gperf" + {"NotLessSlantEqual;", "⩽̸"}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 2245 "HTMLCharacterReference.gperf" + {"zeta;", "ζ"}, + {""}, + {""}, +#line 372 "HTMLCharacterReference.gperf" + {"NotLess;", "≮"}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, #line 466 "HTMLCharacterReference.gperf" - {"Rarrtl;", "⤖"}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 1381 "HTMLCharacterReference.gperf" - {"looparrowleft;", "↫"}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, + {"Rarr;", "↠"}, + {""}, +#line 648 "HTMLCharacterReference.gperf" + {"Zcaron;", "Ž"}, + {""}, + {""}, + {""}, +#line 814 "HTMLCharacterReference.gperf" + {"boxuR;", "╘"}, + {""}, +#line 2231 "HTMLCharacterReference.gperf" + {"yen", "¥"}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 2232 "HTMLCharacterReference.gperf" + {"yen;", "¥"}, + {""}, + {""}, +#line 1194 "HTMLCharacterReference.gperf" + {"horbar;", "―"}, + {""}, +#line 691 "HTMLCharacterReference.gperf" + {"angmsdac;", "⦪"}, +#line 557 "HTMLCharacterReference.gperf" + {"Therefore;", "∴"}, + {""}, + {""}, + {""}, + {""}, +#line 1302 "HTMLCharacterReference.gperf" + {"late;", "⪭"}, +#line 1485 "HTMLCharacterReference.gperf" + {"napos;", "ʼn"}, + {""}, + {""}, +#line 614 "HTMLCharacterReference.gperf" + {"Vee;", "⋁"}, +#line 100 "HTMLCharacterReference.gperf" + {"DScy;", "Ѕ"}, + {""}, + {""}, + {""}, +#line 1191 "HTMLCharacterReference.gperf" + {"hookleftarrow;", "↩"}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, #line 467 "HTMLCharacterReference.gperf" - {"Rcaron;", "Ř"}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 403 "HTMLCharacterReference.gperf" - {"Nscr;", "𝒩"}, - {""}, - {""}, -#line 1161 "HTMLCharacterReference.gperf" - {"gtquest;", "⩼"}, -#line 454 "HTMLCharacterReference.gperf" - {"Psi;", "Ψ"}, - {""}, - {""}, -#line 155 "HTMLCharacterReference.gperf" - {"Ecaron;", "Ě"}, -#line 1418 "HTMLCharacterReference.gperf" - {"ltquest;", "⩻"}, - {""}, - {""}, -#line 848 "HTMLCharacterReference.gperf" - {"caret;", "⁁"}, - {""}, - {""}, - {""}, -#line 1205 "HTMLCharacterReference.gperf" - {"iecy;", "е"}, - {""}, - {""}, - {""}, - {""}, -#line 2201 "HTMLCharacterReference.gperf" - {"xcap;", "⋂"}, -#line 718 "HTMLCharacterReference.gperf" - {"atilde", "ã"}, + {"Rarrtl;", "⤖"}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 1382 "HTMLCharacterReference.gperf" + {"looparrowleft;", "↫"}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 468 "HTMLCharacterReference.gperf" + {"Rcaron;", "Ř"}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 404 "HTMLCharacterReference.gperf" + {"Nscr;", "𝒩"}, + {""}, + {""}, +#line 1162 "HTMLCharacterReference.gperf" + {"gtquest;", "⩼"}, +#line 455 "HTMLCharacterReference.gperf" + {"Psi;", "Ψ"}, + {""}, + {""}, +#line 156 "HTMLCharacterReference.gperf" + {"Ecaron;", "Ě"}, +#line 1419 "HTMLCharacterReference.gperf" + {"ltquest;", "⩻"}, + {""}, + {""}, +#line 849 "HTMLCharacterReference.gperf" + {"caret;", "⁁"}, + {""}, + {""}, + {""}, +#line 1206 "HTMLCharacterReference.gperf" + {"iecy;", "е"}, + {""}, + {""}, + {""}, + {""}, +#line 2202 "HTMLCharacterReference.gperf" + {"xcap;", "⋂"}, #line 719 "HTMLCharacterReference.gperf" - {"atilde;", "ã"}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 1601 "HTMLCharacterReference.gperf" - {"nsupset;", "⊃⃒"}, -#line 55 "HTMLCharacterReference.gperf" - {"Bfr;", "𝔅"}, -#line 221 "HTMLCharacterReference.gperf" - {"HumpEqual;", "≏"}, - {""}, -#line 605 "HTMLCharacterReference.gperf" - {"Utilde;", "Ũ"}, - {""}, - {""}, -#line 1011 "HTMLCharacterReference.gperf" - {"ecir;", "≖"}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 75 "HTMLCharacterReference.gperf" - {"Cfr;", "ℭ"}, - {""}, - {""}, - {""}, -#line 856 "HTMLCharacterReference.gperf" - {"ccupssm;", "⩐"}, - {""}, -#line 1313 "HTMLCharacterReference.gperf" - {"lcub;", "{"}, -#line 2179 "HTMLCharacterReference.gperf" - {"vnsub;", "⊂⃒"}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 1063 "HTMLCharacterReference.gperf" - {"eth", "ð"}, - {""}, - {""}, - {""}, - {""}, - {""}, + {"atilde", "ã"}, +#line 720 "HTMLCharacterReference.gperf" + {"atilde;", "ã"}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 1602 "HTMLCharacterReference.gperf" + {"nsupset;", "⊃⃒"}, +#line 56 "HTMLCharacterReference.gperf" + {"Bfr;", "𝔅"}, +#line 222 "HTMLCharacterReference.gperf" + {"HumpEqual;", "≏"}, + {""}, +#line 606 "HTMLCharacterReference.gperf" + {"Utilde;", "Ũ"}, + {""}, + {""}, +#line 1012 "HTMLCharacterReference.gperf" + {"ecir;", "≖"}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 76 "HTMLCharacterReference.gperf" + {"Cfr;", "ℭ"}, + {""}, + {""}, + {""}, +#line 857 "HTMLCharacterReference.gperf" + {"ccupssm;", "⩐"}, + {""}, +#line 1314 "HTMLCharacterReference.gperf" + {"lcub;", "{"}, +#line 2180 "HTMLCharacterReference.gperf" + {"vnsub;", "⊂⃒"}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, #line 1064 "HTMLCharacterReference.gperf" - {"eth;", "ð"}, -#line 1498 "HTMLCharacterReference.gperf" - {"ncup;", "⩂"}, - {""}, - {""}, + {"eth", "ð"}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 1065 "HTMLCharacterReference.gperf" + {"eth;", "ð"}, +#line 1499 "HTMLCharacterReference.gperf" + {"ncup;", "⩂"}, + {""}, + {""}, +#line 1139 "HTMLCharacterReference.gperf" + {"gl;", "≷"}, + {""}, +#line 43 "HTMLCharacterReference.gperf" + {"Ascr;", "𝒜"}, + {""}, + {""}, +#line 1360 "HTMLCharacterReference.gperf" + {"ll;", "≪"}, + {""}, +#line 101 "HTMLCharacterReference.gperf" + {"DZcy;", "Џ"}, +#line 233 "HTMLCharacterReference.gperf" + {"Igrave", "Ì"}, +#line 234 "HTMLCharacterReference.gperf" + {"Igrave;", "Ì"}, +#line 1026 "HTMLCharacterReference.gperf" + {"el;", "⪙"}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 1029 "HTMLCharacterReference.gperf" + {"els;", "⪕"}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 2244 "HTMLCharacterReference.gperf" + {"zeetrf;", "ℨ"}, + {""}, +#line 440 "HTMLCharacterReference.gperf" + {"Phi;", "Φ"}, +#line 1383 "HTMLCharacterReference.gperf" + {"looparrowright;", "↬"}, +#line 360 "HTMLCharacterReference.gperf" + {"NotGreater;", "≯"}, +#line 60 "HTMLCharacterReference.gperf" + {"Bumpeq;", "≎"}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 1141 "HTMLCharacterReference.gperf" + {"gla;", "⪥"}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 450 "HTMLCharacterReference.gperf" + {"Prime;", "″"}, + {""}, +#line 1247 "HTMLCharacterReference.gperf" + {"isindot;", "⋵"}, + {""}, #line 1138 "HTMLCharacterReference.gperf" - {"gl;", "≷"}, - {""}, -#line 42 "HTMLCharacterReference.gperf" - {"Ascr;", "𝒜"}, - {""}, - {""}, + {"gjcy;", "ѓ"}, + {""}, + {""}, + {""}, + {""}, #line 1359 "HTMLCharacterReference.gperf" - {"ll;", "≪"}, - {""}, -#line 100 "HTMLCharacterReference.gperf" - {"DZcy;", "Џ"}, -#line 232 "HTMLCharacterReference.gperf" - {"Igrave", "Ì"}, -#line 233 "HTMLCharacterReference.gperf" - {"Igrave;", "Ì"}, -#line 1025 "HTMLCharacterReference.gperf" - {"el;", "⪙"}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 1028 "HTMLCharacterReference.gperf" - {"els;", "⪕"}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 2243 "HTMLCharacterReference.gperf" - {"zeetrf;", "ℨ"}, - {""}, -#line 439 "HTMLCharacterReference.gperf" - {"Phi;", "Φ"}, -#line 1382 "HTMLCharacterReference.gperf" - {"looparrowright;", "↬"}, -#line 359 "HTMLCharacterReference.gperf" - {"NotGreater;", "≯"}, -#line 59 "HTMLCharacterReference.gperf" - {"Bumpeq;", "≎"}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 1140 "HTMLCharacterReference.gperf" - {"gla;", "⪥"}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 449 "HTMLCharacterReference.gperf" - {"Prime;", "″"}, - {""}, -#line 1246 "HTMLCharacterReference.gperf" - {"isindot;", "⋵"}, - {""}, -#line 1137 "HTMLCharacterReference.gperf" - {"gjcy;", "ѓ"}, - {""}, - {""}, - {""}, - {""}, -#line 1358 "HTMLCharacterReference.gperf" - {"ljcy;", "љ"}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, + {"ljcy;", "љ"}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 970 "HTMLCharacterReference.gperf" + {"div;", "÷"}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 963 "HTMLCharacterReference.gperf" + {"diam;", "⋄"}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 1598 "HTMLCharacterReference.gperf" + {"nsucceq;", "⪰̸"}, + {""}, + {""}, +#line 1303 "HTMLCharacterReference.gperf" + {"lates;", "⪭︀"}, + {""}, +#line 1293 "HTMLCharacterReference.gperf" + {"larrbfs;", "⤟"}, + {""}, +#line 517 "HTMLCharacterReference.gperf" + {"ShortLeftArrow;", "←"}, +#line 780 "HTMLCharacterReference.gperf" + {"boxDL;", "╗"}, +#line 2195 "HTMLCharacterReference.gperf" + {"weierp;", "℘"}, +#line 1486 "HTMLCharacterReference.gperf" + {"napprox;", "≉"}, +#line 1403 "HTMLCharacterReference.gperf" + {"lsh;", "↰"}, +#line 507 "HTMLCharacterReference.gperf" + {"SHcy;", "Ш"}, + {""}, +#line 338 "HTMLCharacterReference.gperf" + {"Ncaron;", "Ň"}, +#line 207 "HTMLCharacterReference.gperf" + {"GreaterSlantEqual;", "⩾"}, + {""}, +#line 975 "HTMLCharacterReference.gperf" + {"djcy;", "ђ"}, +#line 1400 "HTMLCharacterReference.gperf" + {"lrtri;", "⊿"}, +#line 1227 "HTMLCharacterReference.gperf" + {"incare;", "℅"}, + {""}, +#line 870 "HTMLCharacterReference.gperf" + {"cir;", "○"}, + {""}, #line 969 "HTMLCharacterReference.gperf" - {"div;", "÷"}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 962 "HTMLCharacterReference.gperf" - {"diam;", "⋄"}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 1597 "HTMLCharacterReference.gperf" - {"nsucceq;", "⪰̸"}, - {""}, - {""}, -#line 1302 "HTMLCharacterReference.gperf" - {"lates;", "⪭︀"}, - {""}, -#line 1292 "HTMLCharacterReference.gperf" - {"larrbfs;", "⤟"}, - {""}, -#line 516 "HTMLCharacterReference.gperf" - {"ShortLeftArrow;", "←"}, -#line 779 "HTMLCharacterReference.gperf" - {"boxDL;", "╗"}, -#line 2194 "HTMLCharacterReference.gperf" - {"weierp;", "℘"}, -#line 1485 "HTMLCharacterReference.gperf" - {"napprox;", "≉"}, -#line 1402 "HTMLCharacterReference.gperf" - {"lsh;", "↰"}, -#line 506 "HTMLCharacterReference.gperf" - {"SHcy;", "Ш"}, - {""}, -#line 337 "HTMLCharacterReference.gperf" - {"Ncaron;", "Ň"}, -#line 206 "HTMLCharacterReference.gperf" - {"GreaterSlantEqual;", "⩾"}, - {""}, -#line 974 "HTMLCharacterReference.gperf" - {"djcy;", "ђ"}, -#line 1399 "HTMLCharacterReference.gperf" - {"lrtri;", "⊿"}, -#line 1226 "HTMLCharacterReference.gperf" - {"incare;", "℅"}, - {""}, -#line 869 "HTMLCharacterReference.gperf" - {"cir;", "○"}, - {""}, -#line 968 "HTMLCharacterReference.gperf" - {"disin;", "⋲"}, - {""}, - {""}, - {""}, - {""}, -#line 1422 "HTMLCharacterReference.gperf" - {"ltrif;", "◂"}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 1172 "HTMLCharacterReference.gperf" - {"hairsp;", " "}, - {""}, - {""}, - {""}, -#line 1360 "HTMLCharacterReference.gperf" - {"llarr;", "⇇"}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 953 "HTMLCharacterReference.gperf" - {"ddotseq;", "⩷"}, - {""}, -#line 1081 "HTMLCharacterReference.gperf" - {"flat;", "♭"}, - {""}, - {""}, - {""}, - {""}, -#line 2203 "HTMLCharacterReference.gperf" - {"xcup;", "⋃"}, - {""}, -#line 193 "HTMLCharacterReference.gperf" - {"Gbreve;", "Ğ"}, - {""}, - {""}, - {""}, -#line 1114 "HTMLCharacterReference.gperf" - {"gamma;", "γ"}, - {""}, - {""}, - {""}, - {""}, -#line 999 "HTMLCharacterReference.gperf" - {"dtrif;", "▾"}, -#line 1251 "HTMLCharacterReference.gperf" - {"itilde;", "ĩ"}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 161 "HTMLCharacterReference.gperf" - {"Egrave", "È"}, + {"disin;", "⋲"}, + {""}, + {""}, + {""}, + {""}, +#line 1423 "HTMLCharacterReference.gperf" + {"ltrif;", "◂"}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 1173 "HTMLCharacterReference.gperf" + {"hairsp;", " "}, + {""}, + {""}, + {""}, +#line 1361 "HTMLCharacterReference.gperf" + {"llarr;", "⇇"}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 954 "HTMLCharacterReference.gperf" + {"ddotseq;", "⩷"}, + {""}, +#line 1082 "HTMLCharacterReference.gperf" + {"flat;", "♭"}, + {""}, + {""}, + {""}, + {""}, +#line 2204 "HTMLCharacterReference.gperf" + {"xcup;", "⋃"}, + {""}, +#line 194 "HTMLCharacterReference.gperf" + {"Gbreve;", "Ğ"}, + {""}, + {""}, + {""}, +#line 1115 "HTMLCharacterReference.gperf" + {"gamma;", "γ"}, + {""}, + {""}, + {""}, + {""}, +#line 1000 "HTMLCharacterReference.gperf" + {"dtrif;", "▾"}, +#line 1252 "HTMLCharacterReference.gperf" + {"itilde;", "ĩ"}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, #line 162 "HTMLCharacterReference.gperf" - {"Egrave;", "È"}, - {""}, - {""}, -#line 629 "HTMLCharacterReference.gperf" - {"Wscr;", "𝒲"}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 2242 "HTMLCharacterReference.gperf" - {"zdot;", "ż"}, - {""}, - {""}, - {""}, - {""}, -#line 1602 "HTMLCharacterReference.gperf" - {"nsupseteq;", "⊉"}, + {"Egrave", "È"}, +#line 163 "HTMLCharacterReference.gperf" + {"Egrave;", "È"}, + {""}, + {""}, +#line 630 "HTMLCharacterReference.gperf" + {"Wscr;", "𝒲"}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 2243 "HTMLCharacterReference.gperf" + {"zdot;", "ż"}, + {""}, + {""}, + {""}, + {""}, #line 1603 "HTMLCharacterReference.gperf" - {"nsupseteqq;", "⫆̸"}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 645 "HTMLCharacterReference.gperf" - {"ZHcy;", "Ж"}, -#line 735 "HTMLCharacterReference.gperf" - {"bcong;", "≌"}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 1212 "HTMLCharacterReference.gperf" - {"ii;", "ⅈ"}, - {""}, - {""}, - {""}, -#line 87 "HTMLCharacterReference.gperf" - {"Conint;", "∯"}, - {""}, -#line 455 "HTMLCharacterReference.gperf" - {"QUOT", "\""}, + {"nsupseteq;", "⊉"}, +#line 1604 "HTMLCharacterReference.gperf" + {"nsupseteqq;", "⫆̸"}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 646 "HTMLCharacterReference.gperf" + {"ZHcy;", "Ж"}, +#line 736 "HTMLCharacterReference.gperf" + {"bcong;", "≌"}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 1213 "HTMLCharacterReference.gperf" + {"ii;", "ⅈ"}, + {""}, + {""}, + {""}, +#line 88 "HTMLCharacterReference.gperf" + {"Conint;", "∯"}, + {""}, #line 456 "HTMLCharacterReference.gperf" - {"QUOT;", "\""}, -#line 1118 "HTMLCharacterReference.gperf" - {"gcirc;", "ĝ"}, - {""}, - {""}, - {""}, - {""}, -#line 2183 "HTMLCharacterReference.gperf" - {"vrtri;", "⊳"}, - {""}, - {""}, - {""}, -#line 1012 "HTMLCharacterReference.gperf" - {"ecirc", "ê"}, + {"QUOT", "\""}, +#line 457 "HTMLCharacterReference.gperf" + {"QUOT;", "\""}, +#line 1119 "HTMLCharacterReference.gperf" + {"gcirc;", "ĝ"}, + {""}, + {""}, + {""}, + {""}, +#line 2184 "HTMLCharacterReference.gperf" + {"vrtri;", "⊳"}, + {""}, + {""}, + {""}, #line 1013 "HTMLCharacterReference.gperf" - {"ecirc;", "ê"}, - {""}, - {""}, - {""}, - {""}, -#line 854 "HTMLCharacterReference.gperf" - {"ccirc;", "ĉ"}, - {""}, - {""}, - {""}, -#line 661 "HTMLCharacterReference.gperf" - {"acirc", "â"}, + {"ecirc", "ê"}, +#line 1014 "HTMLCharacterReference.gperf" + {"ecirc;", "ê"}, + {""}, + {""}, + {""}, + {""}, +#line 855 "HTMLCharacterReference.gperf" + {"ccirc;", "ĉ"}, + {""}, + {""}, + {""}, #line 662 "HTMLCharacterReference.gperf" - {"acirc;", "â"}, -#line 598 "HTMLCharacterReference.gperf" - {"Updownarrow;", "⇕"}, - {""}, - {""}, - {""}, -#line 1255 "HTMLCharacterReference.gperf" - {"jcirc;", "ĵ"}, -#line 892 "HTMLCharacterReference.gperf" - {"compfn;", "∘"}, - {""}, - {""}, -#line 574 "HTMLCharacterReference.gperf" - {"Ucirc", "Û"}, -#line 575 "HTMLCharacterReference.gperf" - {"Ucirc;", "Û"}, - {""}, - {""}, - {""}, -#line 566 "HTMLCharacterReference.gperf" - {"Tscr;", "𝒯"}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 788 "HTMLCharacterReference.gperf" - {"boxUL;", "╝"}, -#line 374 "HTMLCharacterReference.gperf" - {"NotLessLess;", "≪̸"}, - {""}, -#line 445 "HTMLCharacterReference.gperf" - {"Precedes;", "≺"}, + {"acirc", "â"}, +#line 663 "HTMLCharacterReference.gperf" + {"acirc;", "â"}, #line 599 "HTMLCharacterReference.gperf" - {"UpperLeftArrow;", "↖"}, - {""}, -#line 706 "HTMLCharacterReference.gperf" - {"apacir;", "⩯"}, - {""}, - {""}, -#line 709 "HTMLCharacterReference.gperf" - {"apos;", "'"}, -#line 1180 "HTMLCharacterReference.gperf" - {"hcirc;", "ĥ"}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 1228 "HTMLCharacterReference.gperf" - {"infintie;", "⧝"}, -#line 453 "HTMLCharacterReference.gperf" - {"Pscr;", "𝒫"}, - {""}, -#line 2175 "HTMLCharacterReference.gperf" - {"verbar;", "|"}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 2249 "HTMLCharacterReference.gperf" - {"zscr;", "𝓏"}, - {""}, -#line 585 "HTMLCharacterReference.gperf" - {"UnderParenthesis;", "⏝"}, - {""}, + {"Updownarrow;", "⇕"}, + {""}, + {""}, + {""}, +#line 1256 "HTMLCharacterReference.gperf" + {"jcirc;", "ĵ"}, +#line 893 "HTMLCharacterReference.gperf" + {"compfn;", "∘"}, + {""}, + {""}, +#line 575 "HTMLCharacterReference.gperf" + {"Ucirc", "Û"}, +#line 576 "HTMLCharacterReference.gperf" + {"Ucirc;", "Û"}, + {""}, + {""}, + {""}, +#line 567 "HTMLCharacterReference.gperf" + {"Tscr;", "𝒯"}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 789 "HTMLCharacterReference.gperf" + {"boxUL;", "╝"}, +#line 375 "HTMLCharacterReference.gperf" + {"NotLessLess;", "≪̸"}, + {""}, +#line 446 "HTMLCharacterReference.gperf" + {"Precedes;", "≺"}, +#line 600 "HTMLCharacterReference.gperf" + {"UpperLeftArrow;", "↖"}, + {""}, +#line 707 "HTMLCharacterReference.gperf" + {"apacir;", "⩯"}, + {""}, + {""}, +#line 710 "HTMLCharacterReference.gperf" + {"apos;", "'"}, +#line 1181 "HTMLCharacterReference.gperf" + {"hcirc;", "ĥ"}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 1229 "HTMLCharacterReference.gperf" + {"infintie;", "⧝"}, +#line 454 "HTMLCharacterReference.gperf" + {"Pscr;", "𝒫"}, + {""}, +#line 2176 "HTMLCharacterReference.gperf" + {"verbar;", "|"}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 2250 "HTMLCharacterReference.gperf" + {"zscr;", "𝓏"}, + {""}, +#line 586 "HTMLCharacterReference.gperf" + {"UnderParenthesis;", "⏝"}, + {""}, +#line 967 "HTMLCharacterReference.gperf" + {"die;", "¨"}, +#line 872 "HTMLCharacterReference.gperf" + {"circ;", "ˆ"}, + {""}, + {""}, +#line 311 "HTMLCharacterReference.gperf" + {"Ll;", "⋘"}, + {""}, + {""}, #line 966 "HTMLCharacterReference.gperf" - {"die;", "¨"}, -#line 871 "HTMLCharacterReference.gperf" - {"circ;", "ˆ"}, - {""}, - {""}, -#line 310 "HTMLCharacterReference.gperf" - {"Ll;", "⋘"}, - {""}, - {""}, -#line 965 "HTMLCharacterReference.gperf" - {"diams;", "♦"}, -#line 799 "HTMLCharacterReference.gperf" - {"boxbox;", "⧉"}, - {""}, - {""}, -#line 1315 "HTMLCharacterReference.gperf" - {"ldca;", "⤶"}, -#line 767 "HTMLCharacterReference.gperf" - {"blank;", "␣"}, - {""}, - {""}, - {""}, - {""}, -#line 1396 "HTMLCharacterReference.gperf" - {"lrhar;", "⇋"}, - {""}, - {""}, - {""}, -#line 895 "HTMLCharacterReference.gperf" - {"cong;", "≅"}, -#line 1483 "HTMLCharacterReference.gperf" - {"napid;", "≋̸"}, - {""}, - {""}, - {""}, -#line 1322 "HTMLCharacterReference.gperf" - {"leftarrow;", "←"}, - {""}, -#line 1164 "HTMLCharacterReference.gperf" - {"gtrdot;", "⋗"}, - {""}, - {""}, - {""}, -#line 2190 "HTMLCharacterReference.gperf" - {"wcirc;", "ŵ"}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 923 "HTMLCharacterReference.gperf" - {"cups;", "∪︀"}, -#line 803 "HTMLCharacterReference.gperf" - {"boxdr;", "┌"}, -#line 921 "HTMLCharacterReference.gperf" - {"cupdot;", "⊍"}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 27 "HTMLCharacterReference.gperf" - {"Abreve;", "Ă"}, - {""}, - {""}, -#line 280 "HTMLCharacterReference.gperf" - {"LeftArrowRightArrow;", "⇆"}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 1282 "HTMLCharacterReference.gperf" - {"lagran;", "ℒ"}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 990 "HTMLCharacterReference.gperf" - {"drbkarow;", "⤐"}, - {""}, - {""}, - {""}, -#line 525 "HTMLCharacterReference.gperf" - {"SquareSubset;", "⊏"}, - {""}, -#line 623 "HTMLCharacterReference.gperf" - {"Vscr;", "𝒱"}, -#line 771 "HTMLCharacterReference.gperf" - {"block;", "█"}, - {""}, - {""}, -#line 323 "HTMLCharacterReference.gperf" - {"Lsh;", "↰"}, - {""}, - {""}, -#line 185 "HTMLCharacterReference.gperf" - {"ForAll;", "∀"}, - {""}, - {""}, - {""}, - {""}, -#line 844 "HTMLCharacterReference.gperf" - {"capcap;", "⩋"}, - {""}, - {""}, - {""}, -#line 32 "HTMLCharacterReference.gperf" - {"Agrave", "À"}, + {"diams;", "♦"}, +#line 800 "HTMLCharacterReference.gperf" + {"boxbox;", "⧉"}, + {""}, + {""}, +#line 1316 "HTMLCharacterReference.gperf" + {"ldca;", "⤶"}, +#line 768 "HTMLCharacterReference.gperf" + {"blank;", "␣"}, + {""}, + {""}, + {""}, + {""}, +#line 1397 "HTMLCharacterReference.gperf" + {"lrhar;", "⇋"}, + {""}, + {""}, + {""}, +#line 896 "HTMLCharacterReference.gperf" + {"cong;", "≅"}, +#line 1484 "HTMLCharacterReference.gperf" + {"napid;", "≋̸"}, + {""}, + {""}, + {""}, +#line 1323 "HTMLCharacterReference.gperf" + {"leftarrow;", "←"}, + {""}, +#line 1165 "HTMLCharacterReference.gperf" + {"gtrdot;", "⋗"}, + {""}, + {""}, + {""}, +#line 2191 "HTMLCharacterReference.gperf" + {"wcirc;", "ŵ"}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 924 "HTMLCharacterReference.gperf" + {"cups;", "∪︀"}, +#line 804 "HTMLCharacterReference.gperf" + {"boxdr;", "┌"}, +#line 922 "HTMLCharacterReference.gperf" + {"cupdot;", "⊍"}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 28 "HTMLCharacterReference.gperf" + {"Abreve;", "Ă"}, + {""}, + {""}, +#line 281 "HTMLCharacterReference.gperf" + {"LeftArrowRightArrow;", "⇆"}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 1283 "HTMLCharacterReference.gperf" + {"lagran;", "ℒ"}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 991 "HTMLCharacterReference.gperf" + {"drbkarow;", "⤐"}, + {""}, + {""}, + {""}, +#line 526 "HTMLCharacterReference.gperf" + {"SquareSubset;", "⊏"}, + {""}, +#line 624 "HTMLCharacterReference.gperf" + {"Vscr;", "𝒱"}, +#line 772 "HTMLCharacterReference.gperf" + {"block;", "█"}, + {""}, + {""}, +#line 324 "HTMLCharacterReference.gperf" + {"Lsh;", "↰"}, + {""}, + {""}, +#line 186 "HTMLCharacterReference.gperf" + {"ForAll;", "∀"}, + {""}, + {""}, + {""}, + {""}, +#line 845 "HTMLCharacterReference.gperf" + {"capcap;", "⩋"}, + {""}, + {""}, + {""}, #line 33 "HTMLCharacterReference.gperf" - {"Agrave;", "À"}, - {""}, - {""}, - {""}, -#line 1001 "HTMLCharacterReference.gperf" - {"duhar;", "⥯"}, -#line 1619 "HTMLCharacterReference.gperf" - {"nvdash;", "⊬"}, - {""}, - {""}, - {""}, -#line 786 "HTMLCharacterReference.gperf" - {"boxHd;", "╤"}, -#line 1115 "HTMLCharacterReference.gperf" - {"gammad;", "ϝ"}, - {""}, -#line 377 "HTMLCharacterReference.gperf" - {"NotNestedGreaterGreater;", "⪢̸"}, - {""}, - {""}, -#line 552 "HTMLCharacterReference.gperf" - {"Tcaron;", "Ť"}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 1353 "HTMLCharacterReference.gperf" - {"lgE;", "⪑"}, -#line 54 "HTMLCharacterReference.gperf" - {"Beta;", "Β"}, -#line 213 "HTMLCharacterReference.gperf" - {"Hcirc;", "Ĥ"}, - {""}, + {"Agrave", "À"}, +#line 34 "HTMLCharacterReference.gperf" + {"Agrave;", "À"}, + {""}, + {""}, + {""}, +#line 1002 "HTMLCharacterReference.gperf" + {"duhar;", "⥯"}, +#line 1620 "HTMLCharacterReference.gperf" + {"nvdash;", "⊬"}, + {""}, + {""}, + {""}, +#line 787 "HTMLCharacterReference.gperf" + {"boxHd;", "╤"}, +#line 1116 "HTMLCharacterReference.gperf" + {"gammad;", "ϝ"}, + {""}, #line 378 "HTMLCharacterReference.gperf" - {"NotNestedLessLess;", "⪡̸"}, -#line 551 "HTMLCharacterReference.gperf" - {"Tau;", "Τ"}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 802 "HTMLCharacterReference.gperf" - {"boxdl;", "┐"}, -#line 2172 "HTMLCharacterReference.gperf" - {"veebar;", "⊻"}, -#line 526 "HTMLCharacterReference.gperf" - {"SquareSubsetEqual;", "⊑"}, - {""}, -#line 1202 "HTMLCharacterReference.gperf" - {"icirc", "î"}, -#line 1203 "HTMLCharacterReference.gperf" - {"icirc;", "î"}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 529 "HTMLCharacterReference.gperf" - {"SquareUnion;", "⊔"}, - {""}, -#line 1144 "HTMLCharacterReference.gperf" - {"gnapprox;", "⪊"}, -#line 1579 "HTMLCharacterReference.gperf" - {"nsce;", "⪰̸"}, - {""}, - {""}, - {""}, -#line 1369 "HTMLCharacterReference.gperf" - {"lnapprox;", "⪉"}, - {""}, - {""}, -#line 2240 "HTMLCharacterReference.gperf" - {"zcaron;", "ž"}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 1766 "HTMLCharacterReference.gperf" - {"qint;", "⨌"}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 639 "HTMLCharacterReference.gperf" - {"Ycirc;", "Ŷ"}, - {""}, - {""}, - {""}, -#line 2235 "HTMLCharacterReference.gperf" - {"yscr;", "𝓎"}, -#line 1355 "HTMLCharacterReference.gperf" - {"lharu;", "↼"}, - {""}, - {""}, - {""}, - {""}, -#line 700 "HTMLCharacterReference.gperf" - {"angst;", "Å"}, -#line 1229 "HTMLCharacterReference.gperf" - {"inodot;", "ı"}, - {""}, - {""}, + {"NotNestedGreaterGreater;", "⪢̸"}, + {""}, + {""}, +#line 553 "HTMLCharacterReference.gperf" + {"Tcaron;", "Ť"}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 1354 "HTMLCharacterReference.gperf" + {"lgE;", "⪑"}, +#line 55 "HTMLCharacterReference.gperf" + {"Beta;", "Β"}, +#line 214 "HTMLCharacterReference.gperf" + {"Hcirc;", "Ĥ"}, + {""}, +#line 379 "HTMLCharacterReference.gperf" + {"NotNestedLessLess;", "⪡̸"}, +#line 552 "HTMLCharacterReference.gperf" + {"Tau;", "Τ"}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 803 "HTMLCharacterReference.gperf" + {"boxdl;", "┐"}, +#line 2173 "HTMLCharacterReference.gperf" + {"veebar;", "⊻"}, #line 527 "HTMLCharacterReference.gperf" - {"SquareSuperset;", "⊐"}, -#line 904 "HTMLCharacterReference.gperf" - {"cross;", "✗"}, - {""}, - {""}, - {""}, -#line 528 "HTMLCharacterReference.gperf" - {"SquareSupersetEqual;", "⊒"}, -#line 1083 "HTMLCharacterReference.gperf" - {"fltns;", "▱"}, - {""}, -#line 631 "HTMLCharacterReference.gperf" - {"Xi;", "Ξ"}, -#line 571 "HTMLCharacterReference.gperf" - {"Uarrocir;", "⥉"}, - {""}, -#line 801 "HTMLCharacterReference.gperf" - {"boxdR;", "╒"}, + {"SquareSubsetEqual;", "⊑"}, + {""}, +#line 1203 "HTMLCharacterReference.gperf" + {"icirc", "î"}, +#line 1204 "HTMLCharacterReference.gperf" + {"icirc;", "î"}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 530 "HTMLCharacterReference.gperf" + {"SquareUnion;", "⊔"}, + {""}, +#line 1145 "HTMLCharacterReference.gperf" + {"gnapprox;", "⪊"}, +#line 1580 "HTMLCharacterReference.gperf" + {"nsce;", "⪰̸"}, + {""}, + {""}, + {""}, +#line 1370 "HTMLCharacterReference.gperf" + {"lnapprox;", "⪉"}, + {""}, + {""}, +#line 2241 "HTMLCharacterReference.gperf" + {"zcaron;", "ž"}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 1767 "HTMLCharacterReference.gperf" + {"qint;", "⨌"}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 640 "HTMLCharacterReference.gperf" + {"Ycirc;", "Ŷ"}, + {""}, + {""}, + {""}, +#line 2236 "HTMLCharacterReference.gperf" + {"yscr;", "𝓎"}, #line 1356 "HTMLCharacterReference.gperf" - {"lharul;", "⥪"}, - {""}, -#line 843 "HTMLCharacterReference.gperf" - {"capbrcup;", "⩉"}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 40 "HTMLCharacterReference.gperf" - {"Aring", "Å"}, + {"lharu;", "↼"}, + {""}, + {""}, + {""}, + {""}, +#line 701 "HTMLCharacterReference.gperf" + {"angst;", "Å"}, +#line 1230 "HTMLCharacterReference.gperf" + {"inodot;", "ı"}, + {""}, + {""}, +#line 528 "HTMLCharacterReference.gperf" + {"SquareSuperset;", "⊐"}, +#line 905 "HTMLCharacterReference.gperf" + {"cross;", "✗"}, + {""}, + {""}, + {""}, +#line 529 "HTMLCharacterReference.gperf" + {"SquareSupersetEqual;", "⊒"}, +#line 1084 "HTMLCharacterReference.gperf" + {"fltns;", "▱"}, + {""}, +#line 632 "HTMLCharacterReference.gperf" + {"Xi;", "Ξ"}, +#line 572 "HTMLCharacterReference.gperf" + {"Uarrocir;", "⥉"}, + {""}, +#line 802 "HTMLCharacterReference.gperf" + {"boxdR;", "╒"}, +#line 1357 "HTMLCharacterReference.gperf" + {"lharul;", "⥪"}, + {""}, +#line 844 "HTMLCharacterReference.gperf" + {"capbrcup;", "⩉"}, + {""}, + {""}, + {""}, + {""}, + {""}, #line 41 "HTMLCharacterReference.gperf" - {"Aring;", "Å"}, - {""}, - {""}, - {""}, - {""}, -#line 1108 "HTMLCharacterReference.gperf" - {"frasl;", "⁄"}, - {""}, - {""}, - {""}, - {""}, -#line 1316 "HTMLCharacterReference.gperf" - {"ldquo;", "“"}, + {"Aring", "Å"}, +#line 42 "HTMLCharacterReference.gperf" + {"Aring;", "Å"}, + {""}, + {""}, + {""}, + {""}, +#line 1109 "HTMLCharacterReference.gperf" + {"frasl;", "⁄"}, + {""}, + {""}, + {""}, + {""}, #line 1317 "HTMLCharacterReference.gperf" - {"ldquor;", "„"}, -#line 1569 "HTMLCharacterReference.gperf" - {"npreceq;", "⪯̸"}, - {""}, -#line 1341 "HTMLCharacterReference.gperf" - {"lesg;", "⋚︀"}, -#line 828 "HTMLCharacterReference.gperf" - {"bsemi;", "⁏"}, -#line 1634 "HTMLCharacterReference.gperf" - {"nwnear;", "⤧"}, -#line 717 "HTMLCharacterReference.gperf" - {"asympeq;", "≍"}, - {""}, - {""}, -#line 818 "HTMLCharacterReference.gperf" - {"boxvL;", "╡"}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 1385 "HTMLCharacterReference.gperf" - {"loplus;", "⨭"}, - {""}, - {""}, -#line 681 "HTMLCharacterReference.gperf" - {"andd;", "⩜"}, - {""}, -#line 247 "HTMLCharacterReference.gperf" - {"Itilde;", "Ĩ"}, - {""}, - {""}, - {""}, - {""}, -#line 205 "HTMLCharacterReference.gperf" - {"GreaterLess;", "≷"}, - {""}, - {""}, -#line 984 "HTMLCharacterReference.gperf" - {"dotsquare;", "⊡"}, - {""}, -#line 920 "HTMLCharacterReference.gperf" - {"cupcup;", "⩊"}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 1605 "HTMLCharacterReference.gperf" - {"ntilde", "ñ"}, + {"ldquo;", "“"}, +#line 1318 "HTMLCharacterReference.gperf" + {"ldquor;", "„"}, +#line 1570 "HTMLCharacterReference.gperf" + {"npreceq;", "⪯̸"}, + {""}, +#line 1342 "HTMLCharacterReference.gperf" + {"lesg;", "⋚︀"}, +#line 829 "HTMLCharacterReference.gperf" + {"bsemi;", "⁏"}, +#line 1635 "HTMLCharacterReference.gperf" + {"nwnear;", "⤧"}, +#line 718 "HTMLCharacterReference.gperf" + {"asympeq;", "≍"}, + {""}, + {""}, +#line 819 "HTMLCharacterReference.gperf" + {"boxvL;", "╡"}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 1386 "HTMLCharacterReference.gperf" + {"loplus;", "⨭"}, + {""}, + {""}, +#line 682 "HTMLCharacterReference.gperf" + {"andd;", "⩜"}, + {""}, +#line 248 "HTMLCharacterReference.gperf" + {"Itilde;", "Ĩ"}, + {""}, + {""}, + {""}, + {""}, +#line 206 "HTMLCharacterReference.gperf" + {"GreaterLess;", "≷"}, + {""}, + {""}, +#line 985 "HTMLCharacterReference.gperf" + {"dotsquare;", "⊡"}, + {""}, +#line 921 "HTMLCharacterReference.gperf" + {"cupcup;", "⩊"}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, #line 1606 "HTMLCharacterReference.gperf" - {"ntilde;", "ñ"}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 1593 "HTMLCharacterReference.gperf" - {"nsubset;", "⊂⃒"}, - {""}, - {""}, - {""}, -#line 1397 "HTMLCharacterReference.gperf" - {"lrhard;", "⥭"}, - {""}, -#line 1232 "HTMLCharacterReference.gperf" - {"integers;", "ℤ"}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 557 "HTMLCharacterReference.gperf" - {"Theta;", "Θ"}, - {""}, - {""}, -#line 1267 "HTMLCharacterReference.gperf" - {"kfr;", "𝔨"}, - {""}, - {""}, - {""}, -#line 388 "HTMLCharacterReference.gperf" - {"NotSquareSuperset;", "⊐̸"}, - {""}, - {""}, - {""}, - {""}, + {"ntilde", "ñ"}, +#line 1607 "HTMLCharacterReference.gperf" + {"ntilde;", "ñ"}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 1594 "HTMLCharacterReference.gperf" + {"nsubset;", "⊂⃒"}, + {""}, + {""}, + {""}, +#line 1398 "HTMLCharacterReference.gperf" + {"lrhard;", "⥭"}, + {""}, +#line 1233 "HTMLCharacterReference.gperf" + {"integers;", "ℤ"}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 558 "HTMLCharacterReference.gperf" + {"Theta;", "Θ"}, + {""}, + {""}, +#line 1268 "HTMLCharacterReference.gperf" + {"kfr;", "𝔨"}, + {""}, + {""}, + {""}, #line 389 "HTMLCharacterReference.gperf" - {"NotSquareSupersetEqual;", "⋣"}, - {""}, -#line 1288 "HTMLCharacterReference.gperf" - {"laquo", "«"}, + {"NotSquareSuperset;", "⊐̸"}, + {""}, + {""}, + {""}, + {""}, +#line 390 "HTMLCharacterReference.gperf" + {"NotSquareSupersetEqual;", "⋣"}, + {""}, #line 1289 "HTMLCharacterReference.gperf" - {"laquo;", "«"}, -#line 872 "HTMLCharacterReference.gperf" - {"circeq;", "≗"}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 1525 "HTMLCharacterReference.gperf" - {"ni;", "∋"}, - {""}, - {""}, - {""}, - {""}, - {""}, + {"laquo", "«"}, +#line 1290 "HTMLCharacterReference.gperf" + {"laquo;", "«"}, +#line 873 "HTMLCharacterReference.gperf" + {"circeq;", "≗"}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, #line 1526 "HTMLCharacterReference.gperf" - {"nis;", "⋼"}, -#line 301 "HTMLCharacterReference.gperf" - {"Leftarrow;", "⇐"}, - {""}, - {""}, - {""}, -#line 982 "HTMLCharacterReference.gperf" - {"dotminus;", "∸"}, -#line 1462 "HTMLCharacterReference.gperf" - {"mscr;", "𝓂"}, - {""}, - {""}, - {""}, -#line 1433 "HTMLCharacterReference.gperf" - {"map;", "↦"}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 1326 "HTMLCharacterReference.gperf" - {"leftleftarrows;", "⇇"}, - {""}, -#line 1400 "HTMLCharacterReference.gperf" - {"lsaquo;", "‹"}, - {""}, -#line 1528 "HTMLCharacterReference.gperf" - {"niv;", "∋"}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 265 "HTMLCharacterReference.gperf" - {"Kscr;", "𝒦"}, - {""}, - {""}, - {""}, - {""}, -#line 363 "HTMLCharacterReference.gperf" - {"NotGreaterLess;", "≹"}, - {""}, -#line 120 "HTMLCharacterReference.gperf" - {"DoubleContourIntegral;", "∯"}, - {""}, -#line 76 "HTMLCharacterReference.gperf" - {"Chi;", "Χ"}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 515 "HTMLCharacterReference.gperf" - {"ShortDownArrow;", "↓"}, -#line 737 "HTMLCharacterReference.gperf" - {"bdquo;", "„"}, - {""}, - {""}, -#line 809 "HTMLCharacterReference.gperf" - {"boxminus;", "⊟"}, -#line 549 "HTMLCharacterReference.gperf" - {"TScy;", "Ц"}, -#line 1542 "HTMLCharacterReference.gperf" - {"nlsim;", "≴"}, - {""}, -#line 132 "HTMLCharacterReference.gperf" - {"DoubleUpDownArrow;", "⇕"}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, + {"ni;", "∋"}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 1527 "HTMLCharacterReference.gperf" + {"nis;", "⋼"}, +#line 302 "HTMLCharacterReference.gperf" + {"Leftarrow;", "⇐"}, + {""}, + {""}, + {""}, +#line 983 "HTMLCharacterReference.gperf" + {"dotminus;", "∸"}, +#line 1463 "HTMLCharacterReference.gperf" + {"mscr;", "𝓂"}, + {""}, + {""}, + {""}, +#line 1434 "HTMLCharacterReference.gperf" + {"map;", "↦"}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 1327 "HTMLCharacterReference.gperf" + {"leftleftarrows;", "⇇"}, + {""}, +#line 1401 "HTMLCharacterReference.gperf" + {"lsaquo;", "‹"}, + {""}, #line 1529 "HTMLCharacterReference.gperf" - {"njcy;", "њ"}, -#line 1496 "HTMLCharacterReference.gperf" - {"ncong;", "≇"}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 2191 "HTMLCharacterReference.gperf" - {"wedbar;", "⩟"}, - {""}, - {""}, -#line 2246 "HTMLCharacterReference.gperf" - {"zhcy;", "ж"}, -#line 57 "HTMLCharacterReference.gperf" - {"Breve;", "˘"}, - {""}, -#line 1111 "HTMLCharacterReference.gperf" - {"gE;", "≧"}, + {"niv;", "∋"}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 266 "HTMLCharacterReference.gperf" + {"Kscr;", "𝒦"}, + {""}, + {""}, + {""}, + {""}, +#line 364 "HTMLCharacterReference.gperf" + {"NotGreaterLess;", "≹"}, + {""}, +#line 121 "HTMLCharacterReference.gperf" + {"DoubleContourIntegral;", "∯"}, + {""}, +#line 77 "HTMLCharacterReference.gperf" + {"Chi;", "Χ"}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 516 "HTMLCharacterReference.gperf" + {"ShortDownArrow;", "↓"}, +#line 738 "HTMLCharacterReference.gperf" + {"bdquo;", "„"}, + {""}, + {""}, +#line 810 "HTMLCharacterReference.gperf" + {"boxminus;", "⊟"}, +#line 550 "HTMLCharacterReference.gperf" + {"TScy;", "Ц"}, #line 1543 "HTMLCharacterReference.gperf" - {"nlt;", "≮"}, -#line 2040 "HTMLCharacterReference.gperf" - {"tbrk;", "⎴"}, - {""}, -#line 730 "HTMLCharacterReference.gperf" - {"barvee;", "⊽"}, -#line 1277 "HTMLCharacterReference.gperf" - {"lE;", "≦"}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 1119 "HTMLCharacterReference.gperf" - {"gcy;", "г"}, - {""}, -#line 251 "HTMLCharacterReference.gperf" - {"Jcirc;", "Ĵ"}, - {""}, - {""}, -#line 1314 "HTMLCharacterReference.gperf" - {"lcy;", "л"}, - {""}, - {""}, - {""}, - {""}, -#line 1015 "HTMLCharacterReference.gperf" - {"ecy;", "э"}, - {""}, - {""}, - {""}, -#line 597 "HTMLCharacterReference.gperf" - {"Uparrow;", "⇑"}, - {""}, -#line 932 "HTMLCharacterReference.gperf" - {"curvearrowleft;", "↶"}, -#line 1575 "HTMLCharacterReference.gperf" - {"nrtri;", "⋫"}, - {""}, - {""}, -#line 665 "HTMLCharacterReference.gperf" - {"acy;", "а"}, - {""}, - {""}, - {""}, - {""}, -#line 1256 "HTMLCharacterReference.gperf" - {"jcy;", "й"}, -#line 2236 "HTMLCharacterReference.gperf" - {"yucy;", "ю"}, - {""}, -#line 1128 "HTMLCharacterReference.gperf" - {"gesdot;", "⪀"}, - {""}, -#line 576 "HTMLCharacterReference.gperf" - {"Ucy;", "У"}, - {""}, -#line 1057 "HTMLCharacterReference.gperf" - {"erDot;", "≓"}, -#line 1338 "HTMLCharacterReference.gperf" - {"lesdot;", "⩿"}, - {""}, - {""}, - {""}, -#line 1532 "HTMLCharacterReference.gperf" - {"nlarr;", "↚"}, - {""}, - {""}, -#line 2067 "HTMLCharacterReference.gperf" - {"top;", "⊤"}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 1178 "HTMLCharacterReference.gperf" - {"harrw;", "↭"}, - {""}, -#line 2208 "HTMLCharacterReference.gperf" - {"xi;", "ξ"}, -#line 949 "HTMLCharacterReference.gperf" - {"dcy;", "д"}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 583 "HTMLCharacterReference.gperf" - {"UnderBrace;", "⏟"}, -#line 1627 "HTMLCharacterReference.gperf" - {"nvrArr;", "⤃"}, + {"nlsim;", "≴"}, + {""}, +#line 133 "HTMLCharacterReference.gperf" + {"DoubleUpDownArrow;", "⇕"}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 1530 "HTMLCharacterReference.gperf" + {"njcy;", "њ"}, +#line 1497 "HTMLCharacterReference.gperf" + {"ncong;", "≇"}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 2192 "HTMLCharacterReference.gperf" + {"wedbar;", "⩟"}, + {""}, + {""}, +#line 2247 "HTMLCharacterReference.gperf" + {"zhcy;", "ж"}, +#line 58 "HTMLCharacterReference.gperf" + {"Breve;", "˘"}, + {""}, +#line 1112 "HTMLCharacterReference.gperf" + {"gE;", "≧"}, +#line 1544 "HTMLCharacterReference.gperf" + {"nlt;", "≮"}, +#line 2041 "HTMLCharacterReference.gperf" + {"tbrk;", "⎴"}, + {""}, +#line 731 "HTMLCharacterReference.gperf" + {"barvee;", "⊽"}, +#line 1278 "HTMLCharacterReference.gperf" + {"lE;", "≦"}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 1120 "HTMLCharacterReference.gperf" + {"gcy;", "г"}, + {""}, +#line 252 "HTMLCharacterReference.gperf" + {"Jcirc;", "Ĵ"}, + {""}, + {""}, +#line 1315 "HTMLCharacterReference.gperf" + {"lcy;", "л"}, + {""}, + {""}, + {""}, + {""}, +#line 1016 "HTMLCharacterReference.gperf" + {"ecy;", "э"}, + {""}, + {""}, + {""}, +#line 598 "HTMLCharacterReference.gperf" + {"Uparrow;", "⇑"}, + {""}, +#line 933 "HTMLCharacterReference.gperf" + {"curvearrowleft;", "↶"}, +#line 1576 "HTMLCharacterReference.gperf" + {"nrtri;", "⋫"}, + {""}, + {""}, +#line 666 "HTMLCharacterReference.gperf" + {"acy;", "а"}, + {""}, + {""}, + {""}, + {""}, +#line 1257 "HTMLCharacterReference.gperf" + {"jcy;", "й"}, +#line 2237 "HTMLCharacterReference.gperf" + {"yucy;", "ю"}, + {""}, +#line 1129 "HTMLCharacterReference.gperf" + {"gesdot;", "⪀"}, + {""}, +#line 577 "HTMLCharacterReference.gperf" + {"Ucy;", "У"}, + {""}, +#line 1058 "HTMLCharacterReference.gperf" + {"erDot;", "≓"}, +#line 1339 "HTMLCharacterReference.gperf" + {"lesdot;", "⩿"}, + {""}, + {""}, + {""}, +#line 1533 "HTMLCharacterReference.gperf" + {"nlarr;", "↚"}, + {""}, + {""}, +#line 2068 "HTMLCharacterReference.gperf" + {"top;", "⊤"}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 1179 "HTMLCharacterReference.gperf" + {"harrw;", "↭"}, + {""}, +#line 2209 "HTMLCharacterReference.gperf" + {"xi;", "ξ"}, +#line 950 "HTMLCharacterReference.gperf" + {"dcy;", "д"}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, #line 584 "HTMLCharacterReference.gperf" - {"UnderBracket;", "⎵"}, -#line 1340 "HTMLCharacterReference.gperf" - {"lesdotor;", "⪃"}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 512 "HTMLCharacterReference.gperf" - {"Scirc;", "Ŝ"}, -#line 1342 "HTMLCharacterReference.gperf" - {"lesges;", "⪓"}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 751 "HTMLCharacterReference.gperf" - {"bigoplus;", "⨁"}, - {""}, - {""}, -#line 1477 "HTMLCharacterReference.gperf" - {"nVdash;", "⊮"}, -#line 1771 "HTMLCharacterReference.gperf" - {"quatint;", "⨖"}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 1594 "HTMLCharacterReference.gperf" - {"nsubseteq;", "⊈"}, + {"UnderBrace;", "⏟"}, +#line 1628 "HTMLCharacterReference.gperf" + {"nvrArr;", "⤃"}, +#line 585 "HTMLCharacterReference.gperf" + {"UnderBracket;", "⎵"}, +#line 1341 "HTMLCharacterReference.gperf" + {"lesdotor;", "⪃"}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 513 "HTMLCharacterReference.gperf" + {"Scirc;", "Ŝ"}, +#line 1343 "HTMLCharacterReference.gperf" + {"lesges;", "⪓"}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 752 "HTMLCharacterReference.gperf" + {"bigoplus;", "⨁"}, + {""}, + {""}, +#line 1478 "HTMLCharacterReference.gperf" + {"nVdash;", "⊮"}, +#line 1772 "HTMLCharacterReference.gperf" + {"quatint;", "⨖"}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, #line 1595 "HTMLCharacterReference.gperf" - {"nsubseteqq;", "⫅̸"}, - {""}, - {""}, - {""}, -#line 72 "HTMLCharacterReference.gperf" - {"Cdot;", "Ċ"}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 914 "HTMLCharacterReference.gperf" - {"cuesc;", "⋟"}, - {""}, - {""}, -#line 1073 "HTMLCharacterReference.gperf" - {"fcy;", "ф"}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 762 "HTMLCharacterReference.gperf" - {"blacksquare;", "▪"}, - {""}, - {""}, -#line 227 "HTMLCharacterReference.gperf" - {"Icirc", "Î"}, + {"nsubseteq;", "⊈"}, +#line 1596 "HTMLCharacterReference.gperf" + {"nsubseteqq;", "⫅̸"}, + {""}, + {""}, + {""}, +#line 73 "HTMLCharacterReference.gperf" + {"Cdot;", "Ċ"}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 915 "HTMLCharacterReference.gperf" + {"cuesc;", "⋟"}, + {""}, + {""}, +#line 1074 "HTMLCharacterReference.gperf" + {"fcy;", "ф"}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 763 "HTMLCharacterReference.gperf" + {"blacksquare;", "▪"}, + {""}, + {""}, #line 228 "HTMLCharacterReference.gperf" - {"Icirc;", "Î"}, - {""}, - {""}, -#line 811 "HTMLCharacterReference.gperf" - {"boxtimes;", "⊠"}, - {""}, -#line 794 "HTMLCharacterReference.gperf" - {"boxVL;", "╣"}, - {""}, - {""}, -#line 2169 "HTMLCharacterReference.gperf" - {"vcy;", "в"}, -#line 2057 "HTMLCharacterReference.gperf" - {"thorn", "þ"}, + {"Icirc", "Î"}, +#line 229 "HTMLCharacterReference.gperf" + {"Icirc;", "Î"}, + {""}, + {""}, +#line 812 "HTMLCharacterReference.gperf" + {"boxtimes;", "⊠"}, + {""}, +#line 795 "HTMLCharacterReference.gperf" + {"boxVL;", "╣"}, + {""}, + {""}, +#line 2170 "HTMLCharacterReference.gperf" + {"vcy;", "в"}, #line 2058 "HTMLCharacterReference.gperf" - {"thorn;", "þ"}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 503 "HTMLCharacterReference.gperf" - {"Rsh;", "↱"}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 376 "HTMLCharacterReference.gperf" - {"NotLessTilde;", "≴"}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 736 "HTMLCharacterReference.gperf" - {"bcy;", "б"}, - {""}, -#line 404 "HTMLCharacterReference.gperf" - {"Ntilde", "Ñ"}, + {"thorn", "þ"}, +#line 2059 "HTMLCharacterReference.gperf" + {"thorn;", "þ"}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 504 "HTMLCharacterReference.gperf" + {"Rsh;", "↱"}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 377 "HTMLCharacterReference.gperf" + {"NotLessTilde;", "≴"}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 737 "HTMLCharacterReference.gperf" + {"bcy;", "б"}, + {""}, #line 405 "HTMLCharacterReference.gperf" - {"Ntilde;", "Ñ"}, - {""}, - {""}, -#line 708 "HTMLCharacterReference.gperf" - {"apid;", "≋"}, - {""}, -#line 1508 "HTMLCharacterReference.gperf" - {"nesear;", "⤨"}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 1003 "HTMLCharacterReference.gperf" - {"dzcy;", "џ"}, - {""}, -#line 1415 "HTMLCharacterReference.gperf" - {"lthree;", "⋋"}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 23 "HTMLCharacterReference.gperf" - {"AMP", "&"}, - {""}, - {""}, - {""}, -#line 1438 "HTMLCharacterReference.gperf" - {"marker;", "▮"}, - {""}, + {"Ntilde", "Ñ"}, +#line 406 "HTMLCharacterReference.gperf" + {"Ntilde;", "Ñ"}, + {""}, + {""}, +#line 709 "HTMLCharacterReference.gperf" + {"apid;", "≋"}, + {""}, +#line 1509 "HTMLCharacterReference.gperf" + {"nesear;", "⤨"}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 1004 "HTMLCharacterReference.gperf" + {"dzcy;", "џ"}, + {""}, +#line 1416 "HTMLCharacterReference.gperf" + {"lthree;", "⋋"}, + {""}, + {""}, + {""}, + {""}, + {""}, #line 24 "HTMLCharacterReference.gperf" - {"AMP;", "&"}, - {""}, -#line 1772 "HTMLCharacterReference.gperf" - {"quest;", "?"}, - {""}, - {""}, -#line 1534 "HTMLCharacterReference.gperf" - {"nle;", "≰"}, -#line 58 "HTMLCharacterReference.gperf" - {"Bscr;", "ℬ"}, -#line 2210 "HTMLCharacterReference.gperf" - {"xlarr;", "⟵"}, -#line 1024 "HTMLCharacterReference.gperf" - {"egsdot;", "⪘"}, -#line 289 "HTMLCharacterReference.gperf" - {"LeftTee;", "⊣"}, -#line 1278 "HTMLCharacterReference.gperf" - {"lEg;", "⪋"}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 963 "HTMLCharacterReference.gperf" - {"diamond;", "⋄"}, -#line 2046 "HTMLCharacterReference.gperf" - {"tfr;", "𝔱"}, -#line 93 "HTMLCharacterReference.gperf" - {"Cscr;", "𝒞"}, - {""}, - {""}, - {""}, -#line 64 "HTMLCharacterReference.gperf" - {"Cap;", "⋒"}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 2222 "HTMLCharacterReference.gperf" - {"xutri;", "△"}, - {""}, - {""}, -#line 180 "HTMLCharacterReference.gperf" - {"Fcy;", "Ф"}, -#line 1182 "HTMLCharacterReference.gperf" - {"heartsuit;", "♥"}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 44 "HTMLCharacterReference.gperf" - {"Atilde", "Ã"}, + {"AMP", "&"}, + {""}, + {""}, + {""}, +#line 1439 "HTMLCharacterReference.gperf" + {"marker;", "▮"}, + {""}, +#line 25 "HTMLCharacterReference.gperf" + {"AMP;", "&"}, + {""}, +#line 1773 "HTMLCharacterReference.gperf" + {"quest;", "?"}, + {""}, + {""}, +#line 1535 "HTMLCharacterReference.gperf" + {"nle;", "≰"}, +#line 59 "HTMLCharacterReference.gperf" + {"Bscr;", "ℬ"}, +#line 2211 "HTMLCharacterReference.gperf" + {"xlarr;", "⟵"}, +#line 1025 "HTMLCharacterReference.gperf" + {"egsdot;", "⪘"}, +#line 290 "HTMLCharacterReference.gperf" + {"LeftTee;", "⊣"}, +#line 1279 "HTMLCharacterReference.gperf" + {"lEg;", "⪋"}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 964 "HTMLCharacterReference.gperf" + {"diamond;", "⋄"}, +#line 2047 "HTMLCharacterReference.gperf" + {"tfr;", "𝔱"}, +#line 94 "HTMLCharacterReference.gperf" + {"Cscr;", "𝒞"}, + {""}, + {""}, + {""}, +#line 65 "HTMLCharacterReference.gperf" + {"Cap;", "⋒"}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 2223 "HTMLCharacterReference.gperf" + {"xutri;", "△"}, + {""}, + {""}, +#line 181 "HTMLCharacterReference.gperf" + {"Fcy;", "Ф"}, +#line 1183 "HTMLCharacterReference.gperf" + {"heartsuit;", "♥"}, + {""}, + {""}, + {""}, + {""}, + {""}, #line 45 "HTMLCharacterReference.gperf" - {"Atilde;", "Ã"}, - {""}, -#line 1204 "HTMLCharacterReference.gperf" - {"icy;", "и"}, - {""}, - {""}, -#line 1197 "HTMLCharacterReference.gperf" - {"hybull;", "⁃"}, - {""}, - {""}, - {""}, -#line 1556 "HTMLCharacterReference.gperf" - {"notni;", "∌"}, - {""}, - {""}, - {""}, -#line 880 "HTMLCharacterReference.gperf" - {"cire;", "≗"}, -#line 191 "HTMLCharacterReference.gperf" - {"Gamma;", "Γ"}, - {""}, - {""}, - {""}, -#line 2072 "HTMLCharacterReference.gperf" - {"tosa;", "⤩"}, -#line 1276 "HTMLCharacterReference.gperf" - {"lBarr;", "⤎"}, - {""}, - {""}, -#line 1497 "HTMLCharacterReference.gperf" - {"ncongdot;", "⩭̸"}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 640 "HTMLCharacterReference.gperf" - {"Ycy;", "Ы"}, - {""}, -#line 1005 "HTMLCharacterReference.gperf" - {"eDDot;", "⩷"}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 967 "HTMLCharacterReference.gperf" - {"digamma;", "ϝ"}, - {""}, - {""}, -#line 1273 "HTMLCharacterReference.gperf" - {"lAarr;", "⇚"}, - {""}, - {""}, - {""}, -#line 156 "HTMLCharacterReference.gperf" - {"Ecirc", "Ê"}, -#line 157 "HTMLCharacterReference.gperf" - {"Ecirc;", "Ê"}, - {""}, - {""}, - {""}, -#line 1971 "HTMLCharacterReference.gperf" - {"star;", "☆"}, -#line 1018 "HTMLCharacterReference.gperf" - {"efDot;", "≒"}, - {""}, - {""}, - {""}, + {"Atilde", "Ã"}, +#line 46 "HTMLCharacterReference.gperf" + {"Atilde;", "Ã"}, + {""}, +#line 1205 "HTMLCharacterReference.gperf" + {"icy;", "и"}, + {""}, + {""}, +#line 1198 "HTMLCharacterReference.gperf" + {"hybull;", "⁃"}, + {""}, + {""}, + {""}, +#line 1557 "HTMLCharacterReference.gperf" + {"notni;", "∌"}, + {""}, + {""}, + {""}, +#line 881 "HTMLCharacterReference.gperf" + {"cire;", "≗"}, +#line 192 "HTMLCharacterReference.gperf" + {"Gamma;", "Γ"}, + {""}, + {""}, + {""}, +#line 2073 "HTMLCharacterReference.gperf" + {"tosa;", "⤩"}, +#line 1277 "HTMLCharacterReference.gperf" + {"lBarr;", "⤎"}, + {""}, + {""}, +#line 1498 "HTMLCharacterReference.gperf" + {"ncongdot;", "⩭̸"}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 641 "HTMLCharacterReference.gperf" + {"Ycy;", "Ы"}, + {""}, +#line 1006 "HTMLCharacterReference.gperf" + {"eDDot;", "⩷"}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 968 "HTMLCharacterReference.gperf" + {"digamma;", "ϝ"}, + {""}, + {""}, #line 1274 "HTMLCharacterReference.gperf" - {"lArr;", "⇐"}, -#line 2202 "HTMLCharacterReference.gperf" - {"xcirc;", "◯"}, - {""}, - {""}, - {""}, - {""}, -#line 1136 "HTMLCharacterReference.gperf" - {"gimel;", "ℷ"}, - {""}, - {""}, - {""}, -#line 1949 "HTMLCharacterReference.gperf" - {"spar;", "∥"}, -#line 517 "HTMLCharacterReference.gperf" - {"ShortRightArrow;", "→"}, - {""}, - {""}, -#line 276 "HTMLCharacterReference.gperf" - {"Lcy;", "Л"}, - {""}, -#line 195 "HTMLCharacterReference.gperf" - {"Gcirc;", "Ĝ"}, - {""}, - {""}, -#line 2002 "HTMLCharacterReference.gperf" - {"sum;", "∑"}, -#line 2149 "HTMLCharacterReference.gperf" - {"vBar;", "⫨"}, - {""}, -#line 964 "HTMLCharacterReference.gperf" - {"diamondsuit;", "♦"}, -#line 240 "HTMLCharacterReference.gperf" - {"Intersection;", "⋂"}, - {""}, - {""}, + {"lAarr;", "⇚"}, + {""}, + {""}, + {""}, +#line 157 "HTMLCharacterReference.gperf" + {"Ecirc", "Ê"}, +#line 158 "HTMLCharacterReference.gperf" + {"Ecirc;", "Ê"}, + {""}, + {""}, + {""}, +#line 1972 "HTMLCharacterReference.gperf" + {"star;", "☆"}, +#line 1019 "HTMLCharacterReference.gperf" + {"efDot;", "≒"}, + {""}, + {""}, + {""}, +#line 1275 "HTMLCharacterReference.gperf" + {"lArr;", "⇐"}, +#line 2203 "HTMLCharacterReference.gperf" + {"xcirc;", "◯"}, + {""}, + {""}, + {""}, + {""}, +#line 1137 "HTMLCharacterReference.gperf" + {"gimel;", "ℷ"}, + {""}, + {""}, + {""}, +#line 1950 "HTMLCharacterReference.gperf" + {"spar;", "∥"}, +#line 518 "HTMLCharacterReference.gperf" + {"ShortRightArrow;", "→"}, + {""}, + {""}, +#line 277 "HTMLCharacterReference.gperf" + {"Lcy;", "Л"}, + {""}, +#line 196 "HTMLCharacterReference.gperf" + {"Gcirc;", "Ĝ"}, + {""}, + {""}, +#line 2003 "HTMLCharacterReference.gperf" + {"sum;", "∑"}, #line 2150 "HTMLCharacterReference.gperf" - {"vBarv;", "⫩"}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 1615 "HTMLCharacterReference.gperf" - {"numsp;", " "}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 939 "HTMLCharacterReference.gperf" - {"dArr;", "⇓"}, - {""}, -#line 1459 "HTMLCharacterReference.gperf" - {"models;", "⊧"}, -#line 1129 "HTMLCharacterReference.gperf" - {"gesdoto;", "⪂"}, -#line 2010 "HTMLCharacterReference.gperf" - {"sup;", "⊃"}, -#line 1171 "HTMLCharacterReference.gperf" - {"hArr;", "⇔"}, - {""}, -#line 1770 "HTMLCharacterReference.gperf" - {"quaternions;", "ℍ"}, -#line 1339 "HTMLCharacterReference.gperf" - {"lesdoto;", "⪁"}, -#line 1513 "HTMLCharacterReference.gperf" - {"ngE;", "≧̸"}, - {""}, - {""}, - {""}, - {""}, -#line 2004 "HTMLCharacterReference.gperf" - {"sup1", "¹"}, + {"vBar;", "⫨"}, + {""}, +#line 965 "HTMLCharacterReference.gperf" + {"diamondsuit;", "♦"}, +#line 241 "HTMLCharacterReference.gperf" + {"Intersection;", "⋂"}, + {""}, + {""}, +#line 2151 "HTMLCharacterReference.gperf" + {"vBarv;", "⫩"}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 1616 "HTMLCharacterReference.gperf" + {"numsp;", " "}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 940 "HTMLCharacterReference.gperf" + {"dArr;", "⇓"}, + {""}, +#line 1460 "HTMLCharacterReference.gperf" + {"models;", "⊧"}, +#line 1130 "HTMLCharacterReference.gperf" + {"gesdoto;", "⪂"}, +#line 2011 "HTMLCharacterReference.gperf" + {"sup;", "⊃"}, +#line 1172 "HTMLCharacterReference.gperf" + {"hArr;", "⇔"}, + {""}, +#line 1771 "HTMLCharacterReference.gperf" + {"quaternions;", "ℍ"}, +#line 1340 "HTMLCharacterReference.gperf" + {"lesdoto;", "⪁"}, +#line 1514 "HTMLCharacterReference.gperf" + {"ngE;", "≧̸"}, + {""}, + {""}, + {""}, + {""}, #line 2005 "HTMLCharacterReference.gperf" - {"sup1;", "¹"}, -#line 1966 "HTMLCharacterReference.gperf" - {"srarr;", "→"}, - {""}, - {""}, + {"sup1", "¹"}, #line 2006 "HTMLCharacterReference.gperf" - {"sup2", "²"}, + {"sup1;", "¹"}, +#line 1967 "HTMLCharacterReference.gperf" + {"srarr;", "→"}, + {""}, + {""}, #line 2007 "HTMLCharacterReference.gperf" - {"sup2;", "²"}, - {""}, - {""}, - {""}, + {"sup2", "²"}, #line 2008 "HTMLCharacterReference.gperf" - {"sup3", "³"}, + {"sup2;", "²"}, + {""}, + {""}, + {""}, #line 2009 "HTMLCharacterReference.gperf" - {"sup3;", "³"}, - {""}, - {""}, - {""}, -#line 327 "HTMLCharacterReference.gperf" - {"Mcy;", "М"}, - {""}, -#line 2033 "HTMLCharacterReference.gperf" - {"swarr;", "↙"}, - {""}, - {""}, -#line 131 "HTMLCharacterReference.gperf" - {"DoubleUpArrow;", "⇑"}, - {""}, - {""}, -#line 67 "HTMLCharacterReference.gperf" - {"Ccaron;", "Č"}, -#line 1564 "HTMLCharacterReference.gperf" - {"npolint;", "⨔"}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 1387 "HTMLCharacterReference.gperf" - {"lowast;", "∗"}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 386 "HTMLCharacterReference.gperf" - {"NotSquareSubset;", "⊏̸"}, - {""}, - {""}, - {""}, -#line 2148 "HTMLCharacterReference.gperf" - {"vArr;", "⇕"}, + {"sup3", "³"}, +#line 2010 "HTMLCharacterReference.gperf" + {"sup3;", "³"}, + {""}, + {""}, + {""}, +#line 328 "HTMLCharacterReference.gperf" + {"Mcy;", "М"}, + {""}, +#line 2034 "HTMLCharacterReference.gperf" + {"swarr;", "↙"}, + {""}, + {""}, +#line 132 "HTMLCharacterReference.gperf" + {"DoubleUpArrow;", "⇑"}, + {""}, + {""}, +#line 68 "HTMLCharacterReference.gperf" + {"Ccaron;", "Č"}, +#line 1565 "HTMLCharacterReference.gperf" + {"npolint;", "⨔"}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 1388 "HTMLCharacterReference.gperf" + {"lowast;", "∗"}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, #line 387 "HTMLCharacterReference.gperf" - {"NotSquareSubsetEqual;", "⋢"}, - {""}, - {""}, - {""}, -#line 1537 "HTMLCharacterReference.gperf" - {"nleq;", "≰"}, + {"NotSquareSubset;", "⊏̸"}, + {""}, + {""}, + {""}, +#line 2149 "HTMLCharacterReference.gperf" + {"vArr;", "⇕"}, +#line 388 "HTMLCharacterReference.gperf" + {"NotSquareSubsetEqual;", "⋢"}, + {""}, + {""}, + {""}, #line 1538 "HTMLCharacterReference.gperf" - {"nleqq;", "≦̸"}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 207 "HTMLCharacterReference.gperf" - {"GreaterTilde;", "≳"}, -#line 543 "HTMLCharacterReference.gperf" - {"SupersetEqual;", "⊇"}, - {""}, -#line 884 "HTMLCharacterReference.gperf" - {"clubs;", "♣"}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 105 "HTMLCharacterReference.gperf" - {"Dcy;", "Д"}, - {""}, - {""}, - {""}, -#line 73 "HTMLCharacterReference.gperf" - {"Cedilla;", "¸"}, -#line 659 "HTMLCharacterReference.gperf" - {"acE;", "∾̳"}, - {""}, - {""}, -#line 624 "HTMLCharacterReference.gperf" - {"Vvdash;", "⊪"}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 535 "HTMLCharacterReference.gperf" - {"Succeeds;", "≻"}, -#line 1533 "HTMLCharacterReference.gperf" - {"nldr;", "‥"}, - {""}, - {""}, - {""}, - {""}, -#line 258 "HTMLCharacterReference.gperf" - {"KHcy;", "Х"}, -#line 600 "HTMLCharacterReference.gperf" - {"UpperRightArrow;", "↗"}, - {""}, - {""}, - {""}, - {""}, -#line 1214 "HTMLCharacterReference.gperf" - {"iiint;", "∭"}, - {""}, -#line 423 "HTMLCharacterReference.gperf" - {"Or;", "⩔"}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 211 "HTMLCharacterReference.gperf" - {"Hacek;", "ˇ"}, - {""}, - {""}, -#line 252 "HTMLCharacterReference.gperf" - {"Jcy;", "Й"}, -#line 1084 "HTMLCharacterReference.gperf" - {"fnof;", "ƒ"}, - {""}, -#line 192 "HTMLCharacterReference.gperf" - {"Gammad;", "Ϝ"}, - {""}, - {""}, -#line 2066 "HTMLCharacterReference.gperf" - {"toea;", "⤨"}, - {""}, - {""}, - {""}, -#line 1962 "HTMLCharacterReference.gperf" - {"squ;", "□"}, -#line 28 "HTMLCharacterReference.gperf" - {"Acirc", "Â"}, -#line 29 "HTMLCharacterReference.gperf" - {"Acirc;", "Â"}, -#line 2221 "HTMLCharacterReference.gperf" - {"xuplus;", "⨄"}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 95 "HTMLCharacterReference.gperf" - {"CupCap;", "≍"}, - {""}, - {""}, - {""}, - {""}, -#line 2047 "HTMLCharacterReference.gperf" - {"there4;", "∴"}, - {""}, - {""}, -#line 1345 "HTMLCharacterReference.gperf" - {"lesseqgtr;", "⋚"}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 440 "HTMLCharacterReference.gperf" - {"Pi;", "Π"}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 1423 "HTMLCharacterReference.gperf" - {"lurdshar;", "⥊"}, -#line 1149 "HTMLCharacterReference.gperf" - {"gopf;", "𝕘"}, - {""}, - {""}, - {""}, -#line 513 "HTMLCharacterReference.gperf" - {"Scy;", "С"}, -#line 1384 "HTMLCharacterReference.gperf" - {"lopf;", "𝕝"}, - {""}, -#line 976 "HTMLCharacterReference.gperf" - {"dlcrop;", "⌍"}, - {""}, - {""}, -#line 1040 "HTMLCharacterReference.gperf" - {"eopf;", "𝕖"}, - {""}, - {""}, - {""}, - {""}, -#line 898 "HTMLCharacterReference.gperf" - {"copf;", "𝕔"}, - {""}, - {""}, - {""}, - {""}, -#line 703 "HTMLCharacterReference.gperf" - {"aopf;", "𝕒"}, -#line 1912 "HTMLCharacterReference.gperf" - {"sharp;", "♯"}, -#line 760 "HTMLCharacterReference.gperf" - {"bkarow;", "⤍"}, - {""}, - {""}, -#line 1259 "HTMLCharacterReference.gperf" - {"jopf;", "𝕛"}, - {""}, - {""}, -#line 362 "HTMLCharacterReference.gperf" - {"NotGreaterGreater;", "≫̸"}, -#line 1910 "HTMLCharacterReference.gperf" - {"sfr;", "𝔰"}, -#line 589 "HTMLCharacterReference.gperf" - {"Uopf;", "𝕌"}, - {""}, -#line 975 "HTMLCharacterReference.gperf" - {"dlcorn;", "⌞"}, -#line 114 "HTMLCharacterReference.gperf" - {"Diamond;", "⋄"}, - {""}, - {""}, -#line 272 "HTMLCharacterReference.gperf" - {"Laplacetrf;", "ℒ"}, - {""}, - {""}, -#line 430 "HTMLCharacterReference.gperf" - {"Ouml", "Ö"}, -#line 431 "HTMLCharacterReference.gperf" - {"Ouml;", "Ö"}, - {""}, -#line 2215 "HTMLCharacterReference.gperf" - {"xoplus;", "⨁"}, - {""}, - {""}, - {""}, -#line 1486 "HTMLCharacterReference.gperf" - {"natur;", "♮"}, + {"nleq;", "≰"}, +#line 1539 "HTMLCharacterReference.gperf" + {"nleqq;", "≦̸"}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 208 "HTMLCharacterReference.gperf" + {"GreaterTilde;", "≳"}, #line 544 "HTMLCharacterReference.gperf" - {"Supset;", "⋑"}, - {""}, -#line 229 "HTMLCharacterReference.gperf" - {"Icy;", "И"}, -#line 978 "HTMLCharacterReference.gperf" - {"dopf;", "𝕕"}, - {""}, -#line 2220 "HTMLCharacterReference.gperf" - {"xsqcup;", "⨆"}, - {""}, -#line 1056 "HTMLCharacterReference.gperf" - {"eqvparsl;", "⧥"}, -#line 1192 "HTMLCharacterReference.gperf" - {"hopf;", "𝕙"}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 299 "HTMLCharacterReference.gperf" - {"LeftVector;", "↼"}, -#line 1968 "HTMLCharacterReference.gperf" - {"ssetmn;", "∖"}, -#line 1633 "HTMLCharacterReference.gperf" - {"nwarrow;", "↖"}, - {""}, - {""}, -#line 360 "HTMLCharacterReference.gperf" - {"NotGreaterEqual;", "≱"}, - {""}, - {""}, -#line 1499 "HTMLCharacterReference.gperf" - {"ncy;", "н"}, - {""}, -#line 2021 "HTMLCharacterReference.gperf" - {"supne;", "⊋"}, - {""}, - {""}, - {""}, - {""}, -#line 2192 "HTMLCharacterReference.gperf" - {"wedge;", "∧"}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 1213 "HTMLCharacterReference.gperf" - {"iiiint;", "⨌"}, - {""}, - {""}, -#line 458 "HTMLCharacterReference.gperf" - {"Qopf;", "ℚ"}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 1975 "HTMLCharacterReference.gperf" - {"strns;", "¯"}, -#line 2073 "HTMLCharacterReference.gperf" - {"tprime;", "‴"}, - {""}, - {""}, -#line 1085 "HTMLCharacterReference.gperf" - {"fopf;", "𝕗"}, - {""}, - {""}, - {""}, -#line 648 "HTMLCharacterReference.gperf" - {"Zcy;", "З"}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 2196 "HTMLCharacterReference.gperf" - {"wopf;", "𝕨"}, - {""}, - {""}, -#line 896 "HTMLCharacterReference.gperf" - {"congdot;", "⩭"}, - {""}, - {""}, + {"SupersetEqual;", "⊇"}, + {""}, +#line 885 "HTMLCharacterReference.gperf" + {"clubs;", "♣"}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 106 "HTMLCharacterReference.gperf" + {"Dcy;", "Д"}, + {""}, + {""}, + {""}, +#line 74 "HTMLCharacterReference.gperf" + {"Cedilla;", "¸"}, +#line 660 "HTMLCharacterReference.gperf" + {"acE;", "∾̳"}, + {""}, + {""}, #line 625 "HTMLCharacterReference.gperf" - {"Wcirc;", "Ŵ"}, - {""}, - {""}, - {""}, -#line 2181 "HTMLCharacterReference.gperf" - {"vopf;", "𝕧"}, -#line 785 "HTMLCharacterReference.gperf" - {"boxHU;", "╩"}, -#line 1576 "HTMLCharacterReference.gperf" - {"nrtrie;", "⋭"}, -#line 750 "HTMLCharacterReference.gperf" - {"bigodot;", "⨀"}, - {""}, - {""}, -#line 186 "HTMLCharacterReference.gperf" - {"Fouriertrf;", "ℱ"}, - {""}, - {""}, - {""}, - {""}, -#line 1216 "HTMLCharacterReference.gperf" - {"iiota;", "℩"}, - {""}, - {""}, -#line 1281 "HTMLCharacterReference.gperf" - {"laemptyv;", "⦴"}, -#line 1284 "HTMLCharacterReference.gperf" - {"lang;", "⟨"}, - {""}, -#line 699 "HTMLCharacterReference.gperf" - {"angsph;", "∢"}, - {""}, - {""}, -#line 134 "HTMLCharacterReference.gperf" - {"DownArrow;", "↓"}, -#line 970 "HTMLCharacterReference.gperf" - {"divide", "÷"}, -#line 971 "HTMLCharacterReference.gperf" - {"divide;", "÷"}, - {""}, -#line 725 "HTMLCharacterReference.gperf" - {"backcong;", "≌"}, -#line 775 "HTMLCharacterReference.gperf" - {"bopf;", "𝕓"}, -#line 451 "HTMLCharacterReference.gperf" - {"Proportion;", "∷"}, - {""}, - {""}, -#line 1089 "HTMLCharacterReference.gperf" - {"fpartint;", "⨍"}, -#line 1539 "HTMLCharacterReference.gperf" - {"nleqslant;", "⩽̸"}, - {""}, -#line 1293 "HTMLCharacterReference.gperf" - {"larrfs;", "⤝"}, -#line 135 "HTMLCharacterReference.gperf" - {"DownArrowBar;", "⤓"}, - {""}, -#line 1036 "HTMLCharacterReference.gperf" - {"emsp;", " "}, - {""}, - {""}, -#line 677 "HTMLCharacterReference.gperf" - {"amp", "&"}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 678 "HTMLCharacterReference.gperf" - {"amp;", "&"}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 60 "HTMLCharacterReference.gperf" - {"CHcy;", "Ч"}, - {""}, -#line 1035 "HTMLCharacterReference.gperf" - {"emsp14;", " "}, - {""}, - {""}, + {"Vvdash;", "⊪"}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 536 "HTMLCharacterReference.gperf" + {"Succeeds;", "≻"}, +#line 1534 "HTMLCharacterReference.gperf" + {"nldr;", "‥"}, + {""}, + {""}, + {""}, + {""}, +#line 259 "HTMLCharacterReference.gperf" + {"KHcy;", "Х"}, +#line 601 "HTMLCharacterReference.gperf" + {"UpperRightArrow;", "↗"}, + {""}, + {""}, + {""}, + {""}, +#line 1215 "HTMLCharacterReference.gperf" + {"iiint;", "∭"}, + {""}, +#line 424 "HTMLCharacterReference.gperf" + {"Or;", "⩔"}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 212 "HTMLCharacterReference.gperf" + {"Hacek;", "ˇ"}, + {""}, + {""}, +#line 253 "HTMLCharacterReference.gperf" + {"Jcy;", "Й"}, +#line 1085 "HTMLCharacterReference.gperf" + {"fnof;", "ƒ"}, + {""}, +#line 193 "HTMLCharacterReference.gperf" + {"Gammad;", "Ϝ"}, + {""}, + {""}, +#line 2067 "HTMLCharacterReference.gperf" + {"toea;", "⤨"}, + {""}, + {""}, + {""}, +#line 1963 "HTMLCharacterReference.gperf" + {"squ;", "□"}, +#line 29 "HTMLCharacterReference.gperf" + {"Acirc", "Â"}, +#line 30 "HTMLCharacterReference.gperf" + {"Acirc;", "Â"}, +#line 2222 "HTMLCharacterReference.gperf" + {"xuplus;", "⨄"}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 96 "HTMLCharacterReference.gperf" + {"CupCap;", "≍"}, + {""}, + {""}, + {""}, + {""}, #line 2048 "HTMLCharacterReference.gperf" - {"therefore;", "∴"}, - {""}, -#line 2193 "HTMLCharacterReference.gperf" - {"wedgeq;", "≙"}, -#line 1553 "HTMLCharacterReference.gperf" - {"notinva;", "∉"}, - {""}, -#line 1068 "HTMLCharacterReference.gperf" - {"excl;", "!"}, - {""}, - {""}, - {""}, - {""}, -#line 1909 "HTMLCharacterReference.gperf" - {"sext;", "✶"}, - {""}, -#line 505 "HTMLCharacterReference.gperf" - {"SHCHcy;", "Щ"}, - {""}, -#line 469 "HTMLCharacterReference.gperf" - {"Rcy;", "Р"}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 2020 "HTMLCharacterReference.gperf" - {"supnE;", "⫌"}, - {""}, - {""}, -#line 158 "HTMLCharacterReference.gperf" - {"Ecy;", "Э"}, -#line 216 "HTMLCharacterReference.gperf" - {"Hopf;", "ℍ"}, - {""}, - {""}, - {""}, - {""}, -#line 184 "HTMLCharacterReference.gperf" - {"Fopf;", "𝔽"}, - {""}, - {""}, - {""}, - {""}, -#line 1905 "HTMLCharacterReference.gperf" - {"semi;", ";"}, - {""}, -#line 1034 "HTMLCharacterReference.gperf" - {"emsp13;", " "}, - {""}, - {""}, -#line 1238 "HTMLCharacterReference.gperf" - {"iopf;", "𝕚"}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 196 "HTMLCharacterReference.gperf" - {"Gcy;", "Г"}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 1272 "HTMLCharacterReference.gperf" - {"kscr;", "𝓀"}, -#line 812 "HTMLCharacterReference.gperf" - {"boxuL;", "╛"}, - {""}, -#line 1862 "HTMLCharacterReference.gperf" - {"rpar;", ")"}, -#line 1361 "HTMLCharacterReference.gperf" - {"llcorner;", "⌞"}, -#line 642 "HTMLCharacterReference.gperf" - {"Yopf;", "𝕐"}, - {""}, -#line 1189 "HTMLCharacterReference.gperf" - {"homtht;", "∻"}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 847 "HTMLCharacterReference.gperf" - {"caps;", "∩︀"}, -#line 2157 "HTMLCharacterReference.gperf" - {"varpi;", "ϖ"}, -#line 846 "HTMLCharacterReference.gperf" - {"capdot;", "⩀"}, - {""}, - {""}, - {""}, - {""}, -#line 614 "HTMLCharacterReference.gperf" - {"Verbar;", "‖"}, - {""}, -#line 414 "HTMLCharacterReference.gperf" - {"Ofr;", "𝔒"}, - {""}, - {""}, - {""}, - {""}, -#line 682 "HTMLCharacterReference.gperf" - {"andslope;", "⩘"}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 1570 "HTMLCharacterReference.gperf" - {"nrArr;", "⇏"}, - {""}, - {""}, - {""}, - {""}, -#line 1901 "HTMLCharacterReference.gperf" - {"searr;", "↘"}, -#line 1409 "HTMLCharacterReference.gperf" - {"lstrok;", "ł"}, -#line 2022 "HTMLCharacterReference.gperf" - {"supplus;", "⫀"}, - {""}, -#line 319 "HTMLCharacterReference.gperf" - {"Lopf;", "𝕃"}, -#line 1908 "HTMLCharacterReference.gperf" - {"setmn;", "∖"}, - {""}, - {""}, - {""}, - {""}, -#line 1630 "HTMLCharacterReference.gperf" - {"nwArr;", "⇖"}, - {""}, -#line 1874 "HTMLCharacterReference.gperf" - {"rtri;", "▹"}, -#line 1865 "HTMLCharacterReference.gperf" - {"rrarr;", "⇉"}, -#line 49 "HTMLCharacterReference.gperf" - {"Barv;", "⫧"}, -#line 886 "HTMLCharacterReference.gperf" - {"colon;", ":"}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 550 "HTMLCharacterReference.gperf" - {"Tab;", "\t"}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 1300 "HTMLCharacterReference.gperf" - {"latail;", "⤙"}, - {""}, -#line 1806 "HTMLCharacterReference.gperf" - {"rbarr;", "⤍"}, -#line 1767 "HTMLCharacterReference.gperf" - {"qopf;", "𝕢"}, - {""}, - {""}, -#line 163 "HTMLCharacterReference.gperf" - {"Element;", "∈"}, - {""}, - {""}, - {""}, -#line 1970 "HTMLCharacterReference.gperf" - {"sstarf;", "⋆"}, - {""}, - {""}, - {""}, - {""}, -#line 996 "HTMLCharacterReference.gperf" - {"dstrok;", "đ"}, - {""}, - {""}, - {""}, - {""}, -#line 1196 "HTMLCharacterReference.gperf" - {"hstrok;", "ħ"}, - {""}, - {""}, -#line 834 "HTMLCharacterReference.gperf" - {"bull;", "•"}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 332 "HTMLCharacterReference.gperf" - {"Mopf;", "𝕄"}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 1350 "HTMLCharacterReference.gperf" - {"lfloor;", "⌊"}, - {""}, -#line 339 "HTMLCharacterReference.gperf" - {"Ncy;", "Н"}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 2068 "HTMLCharacterReference.gperf" - {"topbot;", "⌶"}, - {""}, - {""}, - {""}, -#line 1030 "HTMLCharacterReference.gperf" - {"emacr;", "ē"}, -#line 1051 "HTMLCharacterReference.gperf" - {"eqslantless;", "⪕"}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 291 "HTMLCharacterReference.gperf" - {"LeftTeeVector;", "⥚"}, -#line 804 "HTMLCharacterReference.gperf" - {"boxh;", "─"}, -#line 675 "HTMLCharacterReference.gperf" - {"amacr;", "ā"}, -#line 1836 "HTMLCharacterReference.gperf" - {"rho;", "ρ"}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 1071 "HTMLCharacterReference.gperf" - {"exponentiale;", "ⅇ"}, - {""}, - {""}, -#line 581 "HTMLCharacterReference.gperf" - {"Umacr;", "Ū"}, - {""}, - {""}, -#line 2154 "HTMLCharacterReference.gperf" - {"varkappa;", "ϰ"}, - {""}, - {""}, -#line 1417 "HTMLCharacterReference.gperf" - {"ltlarr;", "⥶"}, - {""}, - {""}, - {""}, - {""}, -#line 1198 "HTMLCharacterReference.gperf" - {"hyphen;", "‐"}, - {""}, - {""}, - {""}, -#line 2228 "HTMLCharacterReference.gperf" - {"ycirc;", "ŷ"}, -#line 1283 "HTMLCharacterReference.gperf" - {"lambda;", "λ"}, - {""}, - {""}, - {""}, - {""}, -#line 941 "HTMLCharacterReference.gperf" - {"dagger;", "†"}, - {""}, - {""}, -#line 271 "HTMLCharacterReference.gperf" - {"Lang;", "⟪"}, -#line 460 "HTMLCharacterReference.gperf" - {"RBarr;", "⤐"}, - {""}, - {""}, -#line 833 "HTMLCharacterReference.gperf" - {"bsolhsub;", "⟈"}, -#line 224 "HTMLCharacterReference.gperf" - {"IOcy;", "Ё"}, -#line 2217 "HTMLCharacterReference.gperf" - {"xrArr;", "⟹"}, -#line 747 "HTMLCharacterReference.gperf" - {"bigcap;", "⋂"}, - {""}, -#line 30 "HTMLCharacterReference.gperf" - {"Acy;", "А"}, -#line 116 "HTMLCharacterReference.gperf" - {"Dopf;", "𝔻"}, - {""}, - {""}, - {""}, -#line 1856 "HTMLCharacterReference.gperf" - {"roarr;", "⇾"}, - {""}, - {""}, - {""}, - {""}, -#line 1319 "HTMLCharacterReference.gperf" - {"ldrushar;", "⥋"}, -#line 90 "HTMLCharacterReference.gperf" - {"Coproduct;", "∐"}, + {"there4;", "∴"}, + {""}, + {""}, #line 1346 "HTMLCharacterReference.gperf" - {"lesseqqgtr;", "⪋"}, -#line 1964 "HTMLCharacterReference.gperf" - {"squarf;", "▪"}, - {""}, - {""}, - {""}, -#line 558 "HTMLCharacterReference.gperf" - {"ThickSpace;", "  "}, -#line 1248 "HTMLCharacterReference.gperf" - {"isinsv;", "⋳"}, - {""}, -#line 448 "HTMLCharacterReference.gperf" - {"PrecedesTilde;", "≾"}, - {""}, - {""}, -#line 973 "HTMLCharacterReference.gperf" - {"divonx;", "⋇"}, - {""}, -#line 2130 "HTMLCharacterReference.gperf" - {"upsi;", "υ"}, -#line 2044 "HTMLCharacterReference.gperf" - {"tdot;", "⃛"}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 1522 "HTMLCharacterReference.gperf" - {"nhArr;", "⇎"}, -#line 845 "HTMLCharacterReference.gperf" - {"capcup;", "⩇"}, - {""}, - {""}, -#line 1170 "HTMLCharacterReference.gperf" - {"gvnE;", "≩︀"}, - {""}, -#line 915 "HTMLCharacterReference.gperf" - {"cularr;", "↶"}, - {""}, - {""}, -#line 1426 "HTMLCharacterReference.gperf" - {"lvnE;", "≨︀"}, - {""}, -#line 379 "HTMLCharacterReference.gperf" - {"NotPrecedes;", "⊀"}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 1233 "HTMLCharacterReference.gperf" - {"intercal;", "⊺"}, -#line 254 "HTMLCharacterReference.gperf" - {"Jopf;", "𝕁"}, - {""}, - {""}, - {""}, -#line 1428 "HTMLCharacterReference.gperf" - {"macr", "¯"}, -#line 1429 "HTMLCharacterReference.gperf" - {"macr;", "¯"}, - {""}, -#line 219 "HTMLCharacterReference.gperf" - {"Hstrok;", "Ħ"}, - {""}, + {"lesseqgtr;", "⋚"}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 441 "HTMLCharacterReference.gperf" + {"Pi;", "Π"}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 1424 "HTMLCharacterReference.gperf" + {"lurdshar;", "⥊"}, +#line 1150 "HTMLCharacterReference.gperf" + {"gopf;", "𝕘"}, + {""}, + {""}, + {""}, +#line 514 "HTMLCharacterReference.gperf" + {"Scy;", "С"}, +#line 1385 "HTMLCharacterReference.gperf" + {"lopf;", "𝕝"}, + {""}, +#line 977 "HTMLCharacterReference.gperf" + {"dlcrop;", "⌍"}, + {""}, + {""}, +#line 1041 "HTMLCharacterReference.gperf" + {"eopf;", "𝕖"}, + {""}, + {""}, + {""}, + {""}, +#line 899 "HTMLCharacterReference.gperf" + {"copf;", "𝕔"}, + {""}, + {""}, + {""}, + {""}, +#line 704 "HTMLCharacterReference.gperf" + {"aopf;", "𝕒"}, +#line 1913 "HTMLCharacterReference.gperf" + {"sharp;", "♯"}, +#line 761 "HTMLCharacterReference.gperf" + {"bkarow;", "⤍"}, + {""}, + {""}, +#line 1260 "HTMLCharacterReference.gperf" + {"jopf;", "𝕛"}, + {""}, + {""}, +#line 363 "HTMLCharacterReference.gperf" + {"NotGreaterGreater;", "≫̸"}, +#line 1911 "HTMLCharacterReference.gperf" + {"sfr;", "𝔰"}, +#line 590 "HTMLCharacterReference.gperf" + {"Uopf;", "𝕌"}, + {""}, +#line 976 "HTMLCharacterReference.gperf" + {"dlcorn;", "⌞"}, +#line 115 "HTMLCharacterReference.gperf" + {"Diamond;", "⋄"}, + {""}, + {""}, +#line 273 "HTMLCharacterReference.gperf" + {"Laplacetrf;", "ℒ"}, + {""}, + {""}, +#line 431 "HTMLCharacterReference.gperf" + {"Ouml", "Ö"}, +#line 432 "HTMLCharacterReference.gperf" + {"Ouml;", "Ö"}, + {""}, +#line 2216 "HTMLCharacterReference.gperf" + {"xoplus;", "⨁"}, + {""}, + {""}, + {""}, +#line 1487 "HTMLCharacterReference.gperf" + {"natur;", "♮"}, +#line 545 "HTMLCharacterReference.gperf" + {"Supset;", "⋑"}, + {""}, +#line 230 "HTMLCharacterReference.gperf" + {"Icy;", "И"}, +#line 979 "HTMLCharacterReference.gperf" + {"dopf;", "𝕕"}, + {""}, +#line 2221 "HTMLCharacterReference.gperf" + {"xsqcup;", "⨆"}, + {""}, +#line 1057 "HTMLCharacterReference.gperf" + {"eqvparsl;", "⧥"}, +#line 1193 "HTMLCharacterReference.gperf" + {"hopf;", "𝕙"}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 300 "HTMLCharacterReference.gperf" + {"LeftVector;", "↼"}, +#line 1969 "HTMLCharacterReference.gperf" + {"ssetmn;", "∖"}, +#line 1634 "HTMLCharacterReference.gperf" + {"nwarrow;", "↖"}, + {""}, + {""}, +#line 361 "HTMLCharacterReference.gperf" + {"NotGreaterEqual;", "≱"}, + {""}, + {""}, +#line 1500 "HTMLCharacterReference.gperf" + {"ncy;", "н"}, + {""}, +#line 2022 "HTMLCharacterReference.gperf" + {"supne;", "⊋"}, + {""}, + {""}, + {""}, + {""}, +#line 2193 "HTMLCharacterReference.gperf" + {"wedge;", "∧"}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 1214 "HTMLCharacterReference.gperf" + {"iiiint;", "⨌"}, + {""}, + {""}, +#line 459 "HTMLCharacterReference.gperf" + {"Qopf;", "ℚ"}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 1976 "HTMLCharacterReference.gperf" + {"strns;", "¯"}, +#line 2074 "HTMLCharacterReference.gperf" + {"tprime;", "‴"}, + {""}, + {""}, +#line 1086 "HTMLCharacterReference.gperf" + {"fopf;", "𝕗"}, + {""}, + {""}, + {""}, +#line 649 "HTMLCharacterReference.gperf" + {"Zcy;", "З"}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 2197 "HTMLCharacterReference.gperf" + {"wopf;", "𝕨"}, + {""}, + {""}, +#line 897 "HTMLCharacterReference.gperf" + {"congdot;", "⩭"}, + {""}, + {""}, +#line 626 "HTMLCharacterReference.gperf" + {"Wcirc;", "Ŵ"}, + {""}, + {""}, + {""}, +#line 2182 "HTMLCharacterReference.gperf" + {"vopf;", "𝕧"}, +#line 786 "HTMLCharacterReference.gperf" + {"boxHU;", "╩"}, +#line 1577 "HTMLCharacterReference.gperf" + {"nrtrie;", "⋭"}, +#line 751 "HTMLCharacterReference.gperf" + {"bigodot;", "⨀"}, + {""}, + {""}, +#line 187 "HTMLCharacterReference.gperf" + {"Fouriertrf;", "ℱ"}, + {""}, + {""}, + {""}, + {""}, +#line 1217 "HTMLCharacterReference.gperf" + {"iiota;", "℩"}, + {""}, + {""}, +#line 1282 "HTMLCharacterReference.gperf" + {"laemptyv;", "⦴"}, +#line 1285 "HTMLCharacterReference.gperf" + {"lang;", "⟨"}, + {""}, +#line 700 "HTMLCharacterReference.gperf" + {"angsph;", "∢"}, + {""}, + {""}, +#line 135 "HTMLCharacterReference.gperf" + {"DownArrow;", "↓"}, +#line 971 "HTMLCharacterReference.gperf" + {"divide", "÷"}, +#line 972 "HTMLCharacterReference.gperf" + {"divide;", "÷"}, + {""}, +#line 726 "HTMLCharacterReference.gperf" + {"backcong;", "≌"}, +#line 776 "HTMLCharacterReference.gperf" + {"bopf;", "𝕓"}, +#line 452 "HTMLCharacterReference.gperf" + {"Proportion;", "∷"}, + {""}, + {""}, +#line 1090 "HTMLCharacterReference.gperf" + {"fpartint;", "⨍"}, +#line 1540 "HTMLCharacterReference.gperf" + {"nleqslant;", "⩽̸"}, + {""}, +#line 1294 "HTMLCharacterReference.gperf" + {"larrfs;", "⤝"}, +#line 136 "HTMLCharacterReference.gperf" + {"DownArrowBar;", "⤓"}, + {""}, +#line 1037 "HTMLCharacterReference.gperf" + {"emsp;", " "}, + {""}, + {""}, +#line 678 "HTMLCharacterReference.gperf" + {"amp", "&"}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 679 "HTMLCharacterReference.gperf" + {"amp;", "&"}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 61 "HTMLCharacterReference.gperf" + {"CHcy;", "Ч"}, + {""}, +#line 1036 "HTMLCharacterReference.gperf" + {"emsp14;", " "}, + {""}, + {""}, +#line 2049 "HTMLCharacterReference.gperf" + {"therefore;", "∴"}, + {""}, +#line 2194 "HTMLCharacterReference.gperf" + {"wedgeq;", "≙"}, +#line 1554 "HTMLCharacterReference.gperf" + {"notinva;", "∉"}, + {""}, +#line 1069 "HTMLCharacterReference.gperf" + {"excl;", "!"}, + {""}, + {""}, + {""}, + {""}, +#line 1910 "HTMLCharacterReference.gperf" + {"sext;", "✶"}, + {""}, +#line 506 "HTMLCharacterReference.gperf" + {"SHCHcy;", "Щ"}, + {""}, +#line 470 "HTMLCharacterReference.gperf" + {"Rcy;", "Р"}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 2021 "HTMLCharacterReference.gperf" + {"supnE;", "⫌"}, + {""}, + {""}, +#line 159 "HTMLCharacterReference.gperf" + {"Ecy;", "Э"}, +#line 217 "HTMLCharacterReference.gperf" + {"Hopf;", "ℍ"}, + {""}, + {""}, + {""}, + {""}, +#line 185 "HTMLCharacterReference.gperf" + {"Fopf;", "𝔽"}, + {""}, + {""}, + {""}, + {""}, +#line 1906 "HTMLCharacterReference.gperf" + {"semi;", ";"}, + {""}, +#line 1035 "HTMLCharacterReference.gperf" + {"emsp13;", " "}, + {""}, + {""}, +#line 1239 "HTMLCharacterReference.gperf" + {"iopf;", "𝕚"}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 197 "HTMLCharacterReference.gperf" + {"Gcy;", "Г"}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 1273 "HTMLCharacterReference.gperf" + {"kscr;", "𝓀"}, +#line 813 "HTMLCharacterReference.gperf" + {"boxuL;", "╛"}, + {""}, +#line 1863 "HTMLCharacterReference.gperf" + {"rpar;", ")"}, +#line 1362 "HTMLCharacterReference.gperf" + {"llcorner;", "⌞"}, +#line 643 "HTMLCharacterReference.gperf" + {"Yopf;", "𝕐"}, + {""}, +#line 1190 "HTMLCharacterReference.gperf" + {"homtht;", "∻"}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 848 "HTMLCharacterReference.gperf" + {"caps;", "∩︀"}, +#line 2158 "HTMLCharacterReference.gperf" + {"varpi;", "ϖ"}, +#line 847 "HTMLCharacterReference.gperf" + {"capdot;", "⩀"}, + {""}, + {""}, + {""}, + {""}, +#line 615 "HTMLCharacterReference.gperf" + {"Verbar;", "‖"}, + {""}, +#line 415 "HTMLCharacterReference.gperf" + {"Ofr;", "𝔒"}, + {""}, + {""}, + {""}, + {""}, +#line 683 "HTMLCharacterReference.gperf" + {"andslope;", "⩘"}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 1571 "HTMLCharacterReference.gperf" + {"nrArr;", "⇏"}, + {""}, + {""}, + {""}, + {""}, +#line 1902 "HTMLCharacterReference.gperf" + {"searr;", "↘"}, +#line 1410 "HTMLCharacterReference.gperf" + {"lstrok;", "ł"}, +#line 2023 "HTMLCharacterReference.gperf" + {"supplus;", "⫀"}, + {""}, +#line 320 "HTMLCharacterReference.gperf" + {"Lopf;", "𝕃"}, +#line 1909 "HTMLCharacterReference.gperf" + {"setmn;", "∖"}, + {""}, + {""}, + {""}, + {""}, +#line 1631 "HTMLCharacterReference.gperf" + {"nwArr;", "⇖"}, + {""}, #line 1875 "HTMLCharacterReference.gperf" - {"rtrie;", "⊵"}, -#line 1518 "HTMLCharacterReference.gperf" - {"nges;", "⩾̸"}, - {""}, - {""}, + {"rtri;", "▹"}, +#line 1866 "HTMLCharacterReference.gperf" + {"rrarr;", "⇉"}, +#line 50 "HTMLCharacterReference.gperf" + {"Barv;", "⫧"}, +#line 887 "HTMLCharacterReference.gperf" + {"colon;", ":"}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 551 "HTMLCharacterReference.gperf" + {"Tab;", "\t"}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 1301 "HTMLCharacterReference.gperf" + {"latail;", "⤙"}, + {""}, +#line 1807 "HTMLCharacterReference.gperf" + {"rbarr;", "⤍"}, +#line 1768 "HTMLCharacterReference.gperf" + {"qopf;", "𝕢"}, + {""}, + {""}, +#line 164 "HTMLCharacterReference.gperf" + {"Element;", "∈"}, + {""}, + {""}, + {""}, +#line 1971 "HTMLCharacterReference.gperf" + {"sstarf;", "⋆"}, + {""}, + {""}, + {""}, + {""}, +#line 997 "HTMLCharacterReference.gperf" + {"dstrok;", "đ"}, + {""}, + {""}, + {""}, + {""}, +#line 1197 "HTMLCharacterReference.gperf" + {"hstrok;", "ħ"}, + {""}, + {""}, +#line 835 "HTMLCharacterReference.gperf" + {"bull;", "•"}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 333 "HTMLCharacterReference.gperf" + {"Mopf;", "𝕄"}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 1351 "HTMLCharacterReference.gperf" + {"lfloor;", "⌊"}, + {""}, +#line 340 "HTMLCharacterReference.gperf" + {"Ncy;", "Н"}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 2069 "HTMLCharacterReference.gperf" + {"topbot;", "⌶"}, + {""}, + {""}, + {""}, +#line 1031 "HTMLCharacterReference.gperf" + {"emacr;", "ē"}, +#line 1052 "HTMLCharacterReference.gperf" + {"eqslantless;", "⪕"}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 292 "HTMLCharacterReference.gperf" + {"LeftTeeVector;", "⥚"}, +#line 805 "HTMLCharacterReference.gperf" + {"boxh;", "─"}, +#line 676 "HTMLCharacterReference.gperf" + {"amacr;", "ā"}, +#line 1837 "HTMLCharacterReference.gperf" + {"rho;", "ρ"}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 1072 "HTMLCharacterReference.gperf" + {"exponentiale;", "ⅇ"}, + {""}, + {""}, +#line 582 "HTMLCharacterReference.gperf" + {"Umacr;", "Ū"}, + {""}, + {""}, +#line 2155 "HTMLCharacterReference.gperf" + {"varkappa;", "ϰ"}, + {""}, + {""}, +#line 1418 "HTMLCharacterReference.gperf" + {"ltlarr;", "⥶"}, + {""}, + {""}, + {""}, + {""}, +#line 1199 "HTMLCharacterReference.gperf" + {"hyphen;", "‐"}, + {""}, + {""}, + {""}, +#line 2229 "HTMLCharacterReference.gperf" + {"ycirc;", "ŷ"}, +#line 1284 "HTMLCharacterReference.gperf" + {"lambda;", "λ"}, + {""}, + {""}, + {""}, + {""}, +#line 942 "HTMLCharacterReference.gperf" + {"dagger;", "†"}, + {""}, + {""}, +#line 272 "HTMLCharacterReference.gperf" + {"Lang;", "⟪"}, #line 461 "HTMLCharacterReference.gperf" - {"REG", "®"}, - {""}, - {""}, - {""}, - {""}, - {""}, + {"RBarr;", "⤐"}, + {""}, + {""}, +#line 834 "HTMLCharacterReference.gperf" + {"bsolhsub;", "⟈"}, +#line 225 "HTMLCharacterReference.gperf" + {"IOcy;", "Ё"}, +#line 2218 "HTMLCharacterReference.gperf" + {"xrArr;", "⟹"}, +#line 748 "HTMLCharacterReference.gperf" + {"bigcap;", "⋂"}, + {""}, +#line 31 "HTMLCharacterReference.gperf" + {"Acy;", "А"}, +#line 117 "HTMLCharacterReference.gperf" + {"Dopf;", "𝔻"}, + {""}, + {""}, + {""}, +#line 1857 "HTMLCharacterReference.gperf" + {"roarr;", "⇾"}, + {""}, + {""}, + {""}, + {""}, +#line 1320 "HTMLCharacterReference.gperf" + {"ldrushar;", "⥋"}, +#line 91 "HTMLCharacterReference.gperf" + {"Coproduct;", "∐"}, +#line 1347 "HTMLCharacterReference.gperf" + {"lesseqqgtr;", "⪋"}, +#line 1965 "HTMLCharacterReference.gperf" + {"squarf;", "▪"}, + {""}, + {""}, + {""}, +#line 559 "HTMLCharacterReference.gperf" + {"ThickSpace;", "  "}, +#line 1249 "HTMLCharacterReference.gperf" + {"isinsv;", "⋳"}, + {""}, +#line 449 "HTMLCharacterReference.gperf" + {"PrecedesTilde;", "≾"}, + {""}, + {""}, +#line 974 "HTMLCharacterReference.gperf" + {"divonx;", "⋇"}, + {""}, +#line 2131 "HTMLCharacterReference.gperf" + {"upsi;", "υ"}, +#line 2045 "HTMLCharacterReference.gperf" + {"tdot;", "⃛"}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 1523 "HTMLCharacterReference.gperf" + {"nhArr;", "⇎"}, +#line 846 "HTMLCharacterReference.gperf" + {"capcup;", "⩇"}, + {""}, + {""}, +#line 1171 "HTMLCharacterReference.gperf" + {"gvnE;", "≩︀"}, + {""}, +#line 916 "HTMLCharacterReference.gperf" + {"cularr;", "↶"}, + {""}, + {""}, +#line 1427 "HTMLCharacterReference.gperf" + {"lvnE;", "≨︀"}, + {""}, +#line 380 "HTMLCharacterReference.gperf" + {"NotPrecedes;", "⊀"}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 1234 "HTMLCharacterReference.gperf" + {"intercal;", "⊺"}, +#line 255 "HTMLCharacterReference.gperf" + {"Jopf;", "𝕁"}, + {""}, + {""}, + {""}, +#line 1429 "HTMLCharacterReference.gperf" + {"macr", "¯"}, +#line 1430 "HTMLCharacterReference.gperf" + {"macr;", "¯"}, + {""}, +#line 220 "HTMLCharacterReference.gperf" + {"Hstrok;", "Ħ"}, + {""}, +#line 1876 "HTMLCharacterReference.gperf" + {"rtrie;", "⊵"}, +#line 1519 "HTMLCharacterReference.gperf" + {"nges;", "⩾̸"}, + {""}, + {""}, #line 462 "HTMLCharacterReference.gperf" - {"REG;", "®"}, -#line 632 "HTMLCharacterReference.gperf" - {"Xopf;", "𝕏"}, - {""}, - {""}, - {""}, -#line 1672 "HTMLCharacterReference.gperf" - {"or;", "∨"}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 1810 "HTMLCharacterReference.gperf" - {"rbrke;", "⦌"}, -#line 1191 "HTMLCharacterReference.gperf" - {"hookrightarrow;", "↪"}, - {""}, -#line 2187 "HTMLCharacterReference.gperf" - {"vsupnE;", "⫌︀"}, - {""}, - {""}, - {""}, -#line 935 "HTMLCharacterReference.gperf" - {"cuwed;", "⋏"}, - {""}, -#line 1344 "HTMLCharacterReference.gperf" - {"lessdot;", "⋖"}, -#line 446 "HTMLCharacterReference.gperf" - {"PrecedesEqual;", "⪯"}, - {""}, - {""}, -#line 2152 "HTMLCharacterReference.gperf" - {"vangrt;", "⦜"}, - {""}, - {""}, - {""}, - {""}, -#line 1832 "HTMLCharacterReference.gperf" - {"rfr;", "𝔯"}, - {""}, - {""}, -#line 1684 "HTMLCharacterReference.gperf" - {"orv;", "⩛"}, - {""}, -#line 758 "HTMLCharacterReference.gperf" - {"bigvee;", "⋁"}, -#line 1505 "HTMLCharacterReference.gperf" - {"nearrow;", "↗"}, -#line 2142 "HTMLCharacterReference.gperf" - {"utri;", "▵"}, - {""}, - {""}, - {""}, -#line 2145 "HTMLCharacterReference.gperf" - {"uuml", "ü"}, -#line 2146 "HTMLCharacterReference.gperf" - {"uuml;", "ü"}, -#line 521 "HTMLCharacterReference.gperf" - {"Sopf;", "𝕊"}, -#line 1682 "HTMLCharacterReference.gperf" - {"oror;", "⩖"}, -#line 1566 "HTMLCharacterReference.gperf" - {"nprcue;", "⋠"}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 1780 "HTMLCharacterReference.gperf" - {"rHar;", "⥤"}, - {""}, - {""}, - {""}, -#line 874 "HTMLCharacterReference.gperf" - {"circlearrowright;", "↻"}, - {""}, - {""}, -#line 2089 "HTMLCharacterReference.gperf" - {"tscr;", "𝓉"}, - {""}, - {""}, - {""}, -#line 876 "HTMLCharacterReference.gperf" - {"circledS;", "Ⓢ"}, -#line 1391 "HTMLCharacterReference.gperf" - {"lozf;", "⧫"}, -#line 1669 "HTMLCharacterReference.gperf" - {"opar;", "⦷"}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 324 "HTMLCharacterReference.gperf" - {"Lstrok;", "Ł"}, -#line 1773 "HTMLCharacterReference.gperf" - {"questeq;", "≟"}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 1026 "HTMLCharacterReference.gperf" - {"elinters;", "⏧"}, -#line 2212 "HTMLCharacterReference.gperf" - {"xnis;", "⋻"}, -#line 1379 "HTMLCharacterReference.gperf" - {"longmapsto;", "⟼"}, - {""}, - {""}, - {""}, -#line 2144 "HTMLCharacterReference.gperf" - {"uuarr;", "⇈"}, -#line 1218 "HTMLCharacterReference.gperf" - {"imacr;", "ī"}, - {""}, -#line 297 "HTMLCharacterReference.gperf" - {"LeftUpVector;", "↿"}, - {""}, -#line 244 "HTMLCharacterReference.gperf" - {"Iopf;", "𝕀"}, -#line 260 "HTMLCharacterReference.gperf" - {"Kappa;", "Κ"}, - {""}, - {""}, -#line 1122 "HTMLCharacterReference.gperf" - {"gel;", "⋛"}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 2014 "HTMLCharacterReference.gperf" - {"supe;", "⊇"}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 2206 "HTMLCharacterReference.gperf" - {"xhArr;", "⟺"}, -#line 1969 "HTMLCharacterReference.gperf" - {"ssmile;", "⌣"}, - {""}, - {""}, -#line 1547 "HTMLCharacterReference.gperf" - {"nopf;", "𝕟"}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, + {"REG", "®"}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 463 "HTMLCharacterReference.gperf" + {"REG;", "®"}, +#line 633 "HTMLCharacterReference.gperf" + {"Xopf;", "𝕏"}, + {""}, + {""}, + {""}, #line 1673 "HTMLCharacterReference.gperf" - {"orarr;", "↻"}, - {""}, - {""}, -#line 1693 "HTMLCharacterReference.gperf" - {"ouml", "ö"}, + {"or;", "∨"}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 1811 "HTMLCharacterReference.gperf" + {"rbrke;", "⦌"}, +#line 1192 "HTMLCharacterReference.gperf" + {"hookrightarrow;", "↪"}, + {""}, +#line 2188 "HTMLCharacterReference.gperf" + {"vsupnE;", "⫌︀"}, + {""}, + {""}, + {""}, +#line 936 "HTMLCharacterReference.gperf" + {"cuwed;", "⋏"}, + {""}, +#line 1345 "HTMLCharacterReference.gperf" + {"lessdot;", "⋖"}, +#line 447 "HTMLCharacterReference.gperf" + {"PrecedesEqual;", "⪯"}, + {""}, + {""}, +#line 2153 "HTMLCharacterReference.gperf" + {"vangrt;", "⦜"}, + {""}, + {""}, + {""}, + {""}, +#line 1833 "HTMLCharacterReference.gperf" + {"rfr;", "𝔯"}, + {""}, + {""}, +#line 1685 "HTMLCharacterReference.gperf" + {"orv;", "⩛"}, + {""}, +#line 759 "HTMLCharacterReference.gperf" + {"bigvee;", "⋁"}, +#line 1506 "HTMLCharacterReference.gperf" + {"nearrow;", "↗"}, +#line 2143 "HTMLCharacterReference.gperf" + {"utri;", "▵"}, + {""}, + {""}, + {""}, +#line 2146 "HTMLCharacterReference.gperf" + {"uuml", "ü"}, +#line 2147 "HTMLCharacterReference.gperf" + {"uuml;", "ü"}, +#line 522 "HTMLCharacterReference.gperf" + {"Sopf;", "𝕊"}, +#line 1683 "HTMLCharacterReference.gperf" + {"oror;", "⩖"}, +#line 1567 "HTMLCharacterReference.gperf" + {"nprcue;", "⋠"}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 1781 "HTMLCharacterReference.gperf" + {"rHar;", "⥤"}, + {""}, + {""}, + {""}, +#line 875 "HTMLCharacterReference.gperf" + {"circlearrowright;", "↻"}, + {""}, + {""}, +#line 2090 "HTMLCharacterReference.gperf" + {"tscr;", "𝓉"}, + {""}, + {""}, + {""}, +#line 877 "HTMLCharacterReference.gperf" + {"circledS;", "Ⓢ"}, +#line 1392 "HTMLCharacterReference.gperf" + {"lozf;", "⧫"}, +#line 1670 "HTMLCharacterReference.gperf" + {"opar;", "⦷"}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 325 "HTMLCharacterReference.gperf" + {"Lstrok;", "Ł"}, +#line 1774 "HTMLCharacterReference.gperf" + {"questeq;", "≟"}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 1027 "HTMLCharacterReference.gperf" + {"elinters;", "⏧"}, +#line 2213 "HTMLCharacterReference.gperf" + {"xnis;", "⋻"}, +#line 1380 "HTMLCharacterReference.gperf" + {"longmapsto;", "⟼"}, + {""}, + {""}, + {""}, +#line 2145 "HTMLCharacterReference.gperf" + {"uuarr;", "⇈"}, +#line 1219 "HTMLCharacterReference.gperf" + {"imacr;", "ī"}, + {""}, +#line 298 "HTMLCharacterReference.gperf" + {"LeftUpVector;", "↿"}, + {""}, +#line 245 "HTMLCharacterReference.gperf" + {"Iopf;", "𝕀"}, +#line 261 "HTMLCharacterReference.gperf" + {"Kappa;", "Κ"}, + {""}, + {""}, +#line 1123 "HTMLCharacterReference.gperf" + {"gel;", "⋛"}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 2015 "HTMLCharacterReference.gperf" + {"supe;", "⊇"}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 2207 "HTMLCharacterReference.gperf" + {"xhArr;", "⟺"}, +#line 1970 "HTMLCharacterReference.gperf" + {"ssmile;", "⌣"}, + {""}, + {""}, +#line 1548 "HTMLCharacterReference.gperf" + {"nopf;", "𝕟"}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 1674 "HTMLCharacterReference.gperf" + {"orarr;", "↻"}, + {""}, + {""}, #line 1694 "HTMLCharacterReference.gperf" - {"ouml;", "ö"}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, + {"ouml", "ö"}, +#line 1695 "HTMLCharacterReference.gperf" + {"ouml;", "ö"}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 1270 "HTMLCharacterReference.gperf" + {"khcy;", "х"}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 520 "HTMLCharacterReference.gperf" + {"Sigma;", "Σ"}, + {""}, +#line 2086 "HTMLCharacterReference.gperf" + {"triplus;", "⨹"}, +#line 1469 "HTMLCharacterReference.gperf" + {"nGt;", "≫⃒"}, +#line 2228 "HTMLCharacterReference.gperf" + {"yacy;", "я"}, + {""}, +#line 2055 "HTMLCharacterReference.gperf" + {"thinsp;", " "}, + {""}, + {""}, +#line 654 "HTMLCharacterReference.gperf" + {"Zopf;", "ℤ"}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 1071 "HTMLCharacterReference.gperf" + {"expectation;", "ℰ"}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 1689 "HTMLCharacterReference.gperf" + {"osol;", "⊘"}, + {""}, + {""}, + {""}, +#line 987 "HTMLCharacterReference.gperf" + {"downarrow;", "↓"}, + {""}, + {""}, + {""}, +#line 555 "HTMLCharacterReference.gperf" + {"Tcy;", "Т"}, + {""}, +#line 948 "HTMLCharacterReference.gperf" + {"dblac;", "˝"}, +#line 1110 "HTMLCharacterReference.gperf" + {"frown;", "⌢"}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 235 "HTMLCharacterReference.gperf" + {"Im;", "ℑ"}, +#line 1859 "HTMLCharacterReference.gperf" + {"ropar;", "⦆"}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 271 "HTMLCharacterReference.gperf" + {"Lambda;", "Λ"}, + {""}, +#line 438 "HTMLCharacterReference.gperf" + {"Pcy;", "П"}, + {""}, +#line 1344 "HTMLCharacterReference.gperf" + {"lessapprox;", "⪅"}, + {""}, + {""}, +#line 1636 "HTMLCharacterReference.gperf" + {"oS;", "Ⓢ"}, + {""}, + {""}, + {""}, + {""}, +#line 2242 "HTMLCharacterReference.gperf" + {"zcy;", "з"}, +#line 1657 "HTMLCharacterReference.gperf" + {"ohm;", "Ω"}, +#line 821 "HTMLCharacterReference.gperf" + {"boxvh;", "┼"}, +#line 150 "HTMLCharacterReference.gperf" + {"Dstrok;", "Đ"}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 2115 "HTMLCharacterReference.gperf" + {"uharr;", "↾"}, + {""}, +#line 2042 "HTMLCharacterReference.gperf" + {"tcaron;", "ť"}, + {""}, +#line 886 "HTMLCharacterReference.gperf" + {"clubsuit;", "♣"}, + {""}, + {""}, +#line 1307 "HTMLCharacterReference.gperf" + {"lbrack;", "["}, + {""}, + {""}, +#line 500 "HTMLCharacterReference.gperf" + {"Ropf;", "ℝ"}, + {""}, +#line 1459 "HTMLCharacterReference.gperf" + {"mnplus;", "∓"}, + {""}, +#line 2040 "HTMLCharacterReference.gperf" + {"tau;", "τ"}, + {""}, + {""}, +#line 578 "HTMLCharacterReference.gperf" + {"Udblac;", "Ű"}, + {""}, + {""}, +#line 1481 "HTMLCharacterReference.gperf" + {"nang;", "∠⃒"}, + {""}, + {""}, + {""}, + {""}, +#line 169 "HTMLCharacterReference.gperf" + {"Eopf;", "𝔼"}, +#line 2205 "HTMLCharacterReference.gperf" + {"xdtri;", "▽"}, + {""}, + {""}, +#line 62 "HTMLCharacterReference.gperf" + {"COPY", "©"}, +#line 63 "HTMLCharacterReference.gperf" + {"COPY;", "©"}, + {""}, +#line 836 "HTMLCharacterReference.gperf" + {"bullet;", "•"}, + {""}, + {""}, +#line 2215 "HTMLCharacterReference.gperf" + {"xopf;", "𝕩"}, + {""}, #line 1269 "HTMLCharacterReference.gperf" - {"khcy;", "х"}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 519 "HTMLCharacterReference.gperf" - {"Sigma;", "Σ"}, - {""}, -#line 2085 "HTMLCharacterReference.gperf" - {"triplus;", "⨹"}, + {"kgreen;", "ĸ"}, +#line 1824 "HTMLCharacterReference.gperf" + {"real;", "ℜ"}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 1140 "HTMLCharacterReference.gperf" + {"glE;", "⪒"}, + {""}, + {""}, + {""}, + {""}, +#line 1917 "HTMLCharacterReference.gperf" + {"shortparallel;", "∥"}, +#line 201 "HTMLCharacterReference.gperf" + {"Gopf;", "𝔾"}, +#line 1503 "HTMLCharacterReference.gperf" + {"neArr;", "⇗"}, + {""}, +#line 2111 "HTMLCharacterReference.gperf" + {"ufr;", "𝔲"}, #line 1468 "HTMLCharacterReference.gperf" - {"nGt;", "≫⃒"}, -#line 2227 "HTMLCharacterReference.gperf" - {"yacy;", "я"}, - {""}, -#line 2054 "HTMLCharacterReference.gperf" - {"thinsp;", " "}, - {""}, - {""}, -#line 653 "HTMLCharacterReference.gperf" - {"Zopf;", "ℤ"}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 1070 "HTMLCharacterReference.gperf" - {"expectation;", "ℰ"}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 1688 "HTMLCharacterReference.gperf" - {"osol;", "⊘"}, - {""}, - {""}, - {""}, -#line 986 "HTMLCharacterReference.gperf" - {"downarrow;", "↓"}, - {""}, - {""}, - {""}, -#line 554 "HTMLCharacterReference.gperf" - {"Tcy;", "Т"}, - {""}, -#line 947 "HTMLCharacterReference.gperf" - {"dblac;", "˝"}, -#line 1109 "HTMLCharacterReference.gperf" - {"frown;", "⌢"}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 234 "HTMLCharacterReference.gperf" - {"Im;", "ℑ"}, -#line 1858 "HTMLCharacterReference.gperf" - {"ropar;", "⦆"}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 270 "HTMLCharacterReference.gperf" - {"Lambda;", "Λ"}, - {""}, -#line 437 "HTMLCharacterReference.gperf" - {"Pcy;", "П"}, - {""}, -#line 1343 "HTMLCharacterReference.gperf" - {"lessapprox;", "⪅"}, - {""}, - {""}, -#line 1635 "HTMLCharacterReference.gperf" - {"oS;", "Ⓢ"}, - {""}, - {""}, - {""}, - {""}, -#line 2241 "HTMLCharacterReference.gperf" - {"zcy;", "з"}, -#line 1656 "HTMLCharacterReference.gperf" - {"ohm;", "Ω"}, -#line 820 "HTMLCharacterReference.gperf" - {"boxvh;", "┼"}, -#line 149 "HTMLCharacterReference.gperf" - {"Dstrok;", "Đ"}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, + {"nGg;", "⋙̸"}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 1286 "HTMLCharacterReference.gperf" + {"langd;", "⦑"}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 745 "HTMLCharacterReference.gperf" + {"beth;", "ℶ"}, + {""}, + {""}, + {""}, +#line 2098 "HTMLCharacterReference.gperf" + {"uHar;", "⥣"}, + {""}, + {""}, +#line 102 "HTMLCharacterReference.gperf" + {"Dagger;", "‡"}, +#line 917 "HTMLCharacterReference.gperf" + {"cularrp;", "⤽"}, +#line 611 "HTMLCharacterReference.gperf" + {"Vcy;", "В"}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 2124 "HTMLCharacterReference.gperf" + {"uogon;", "ų"}, + {""}, + {""}, + {""}, + {""}, #line 2114 "HTMLCharacterReference.gperf" - {"uharr;", "↾"}, - {""}, -#line 2041 "HTMLCharacterReference.gperf" - {"tcaron;", "ť"}, - {""}, -#line 885 "HTMLCharacterReference.gperf" - {"clubsuit;", "♣"}, - {""}, - {""}, -#line 1306 "HTMLCharacterReference.gperf" - {"lbrack;", "["}, - {""}, - {""}, -#line 499 "HTMLCharacterReference.gperf" - {"Ropf;", "ℝ"}, - {""}, -#line 1458 "HTMLCharacterReference.gperf" - {"mnplus;", "∓"}, - {""}, -#line 2039 "HTMLCharacterReference.gperf" - {"tau;", "τ"}, - {""}, - {""}, -#line 577 "HTMLCharacterReference.gperf" - {"Udblac;", "Ű"}, - {""}, - {""}, -#line 1480 "HTMLCharacterReference.gperf" - {"nang;", "∠⃒"}, - {""}, - {""}, - {""}, - {""}, -#line 168 "HTMLCharacterReference.gperf" - {"Eopf;", "𝔼"}, -#line 2204 "HTMLCharacterReference.gperf" - {"xdtri;", "▽"}, - {""}, - {""}, -#line 61 "HTMLCharacterReference.gperf" - {"COPY", "©"}, -#line 62 "HTMLCharacterReference.gperf" - {"COPY;", "©"}, - {""}, -#line 835 "HTMLCharacterReference.gperf" - {"bullet;", "•"}, - {""}, - {""}, -#line 2214 "HTMLCharacterReference.gperf" - {"xopf;", "𝕩"}, - {""}, -#line 1268 "HTMLCharacterReference.gperf" - {"kgreen;", "ĸ"}, -#line 1823 "HTMLCharacterReference.gperf" - {"real;", "ℜ"}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 1139 "HTMLCharacterReference.gperf" - {"glE;", "⪒"}, - {""}, - {""}, - {""}, - {""}, -#line 1916 "HTMLCharacterReference.gperf" - {"shortparallel;", "∥"}, -#line 200 "HTMLCharacterReference.gperf" - {"Gopf;", "𝔾"}, -#line 1502 "HTMLCharacterReference.gperf" - {"neArr;", "⇗"}, - {""}, -#line 2110 "HTMLCharacterReference.gperf" - {"ufr;", "𝔲"}, -#line 1467 "HTMLCharacterReference.gperf" - {"nGg;", "⋙̸"}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 1285 "HTMLCharacterReference.gperf" - {"langd;", "⦑"}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 744 "HTMLCharacterReference.gperf" - {"beth;", "ℶ"}, - {""}, - {""}, - {""}, -#line 2097 "HTMLCharacterReference.gperf" - {"uHar;", "⥣"}, - {""}, - {""}, -#line 101 "HTMLCharacterReference.gperf" - {"Dagger;", "‡"}, -#line 916 "HTMLCharacterReference.gperf" - {"cularrp;", "⤽"}, -#line 610 "HTMLCharacterReference.gperf" - {"Vcy;", "В"}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 2123 "HTMLCharacterReference.gperf" - {"uogon;", "ų"}, - {""}, - {""}, - {""}, - {""}, -#line 2113 "HTMLCharacterReference.gperf" - {"uharl;", "↿"}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 1447 "HTMLCharacterReference.gperf" - {"mid;", "∣"}, -#line 1896 "HTMLCharacterReference.gperf" - {"sdot;", "⋅"}, -#line 70 "HTMLCharacterReference.gperf" - {"Ccirc;", "Ĉ"}, - {""}, - {""}, - {""}, -#line 545 "HTMLCharacterReference.gperf" - {"THORN", "Þ"}, + {"uharl;", "↿"}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 1448 "HTMLCharacterReference.gperf" + {"mid;", "∣"}, +#line 1897 "HTMLCharacterReference.gperf" + {"sdot;", "⋅"}, +#line 71 "HTMLCharacterReference.gperf" + {"Ccirc;", "Ĉ"}, + {""}, + {""}, + {""}, #line 546 "HTMLCharacterReference.gperf" - {"THORN;", "Þ"}, -#line 1439 "HTMLCharacterReference.gperf" - {"mcomma;", "⨩"}, - {""}, - {""}, -#line 559 "HTMLCharacterReference.gperf" - {"ThinSpace;", " "}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 572 "HTMLCharacterReference.gperf" - {"Ubrcy;", "Ў"}, - {""}, - {""}, -#line 1650 "HTMLCharacterReference.gperf" - {"ofr;", "𝔬"}, - {""}, -#line 1551 "HTMLCharacterReference.gperf" - {"notinE;", "⋹̸"}, - {""}, - {""}, -#line 2090 "HTMLCharacterReference.gperf" - {"tscy;", "ц"}, - {""}, - {""}, - {""}, - {""}, -#line 464 "HTMLCharacterReference.gperf" - {"Rang;", "⟫"}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 1828 "HTMLCharacterReference.gperf" - {"reg", "®"}, - {""}, - {""}, - {""}, -#line 1457 "HTMLCharacterReference.gperf" - {"mldr;", "…"}, - {""}, + {"THORN", "Þ"}, +#line 547 "HTMLCharacterReference.gperf" + {"THORN;", "Þ"}, +#line 1440 "HTMLCharacterReference.gperf" + {"mcomma;", "⨩"}, + {""}, + {""}, +#line 560 "HTMLCharacterReference.gperf" + {"ThinSpace;", " "}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 573 "HTMLCharacterReference.gperf" + {"Ubrcy;", "Ў"}, + {""}, + {""}, +#line 1651 "HTMLCharacterReference.gperf" + {"ofr;", "𝔬"}, + {""}, +#line 1552 "HTMLCharacterReference.gperf" + {"notinE;", "⋹̸"}, + {""}, + {""}, +#line 2091 "HTMLCharacterReference.gperf" + {"tscy;", "ц"}, + {""}, + {""}, + {""}, + {""}, +#line 465 "HTMLCharacterReference.gperf" + {"Rang;", "⟫"}, + {""}, + {""}, + {""}, + {""}, + {""}, #line 1829 "HTMLCharacterReference.gperf" - {"reg;", "®"}, - {""}, - {""}, - {""}, -#line 88 "HTMLCharacterReference.gperf" - {"ContourIntegral;", "∮"}, - {""}, - {""}, - {""}, - {""}, -#line 2049 "HTMLCharacterReference.gperf" - {"theta;", "θ"}, - {""}, - {""}, -#line 1854 "HTMLCharacterReference.gperf" - {"rnmid;", "⫮"}, - {""}, -#line 800 "HTMLCharacterReference.gperf" - {"boxdL;", "╕"}, -#line 1670 "HTMLCharacterReference.gperf" - {"operp;", "⦹"}, -#line 697 "HTMLCharacterReference.gperf" - {"angrtvb;", "⊾"}, -#line 2229 "HTMLCharacterReference.gperf" - {"ycy;", "ы"}, - {""}, -#line 1478 "HTMLCharacterReference.gperf" - {"nabla;", "∇"}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 1963 "HTMLCharacterReference.gperf" - {"square;", "□"}, - {""}, - {""}, -#line 350 "HTMLCharacterReference.gperf" - {"Nopf;", "ℕ"}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 1347 "HTMLCharacterReference.gperf" - {"lessgtr;", "≶"}, - {""}, - {""}, - {""}, -#line 1388 "HTMLCharacterReference.gperf" - {"lowbar;", "_"}, -#line 1390 "HTMLCharacterReference.gperf" - {"lozenge;", "◊"}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 1625 "HTMLCharacterReference.gperf" - {"nvlt;", "<⃒"}, - {""}, -#line 1286 "HTMLCharacterReference.gperf" - {"langle;", "⟨"}, - {""}, - {""}, -#line 2211 "HTMLCharacterReference.gperf" - {"xmap;", "⟼"}, - {""}, - {""}, -#line 1869 "HTMLCharacterReference.gperf" - {"rsqb;", "]"}, - {""}, -#line 1967 "HTMLCharacterReference.gperf" - {"sscr;", "𝓈"}, - {""}, -#line 938 "HTMLCharacterReference.gperf" - {"cylcty;", "⌭"}, - {""}, -#line 1377 "HTMLCharacterReference.gperf" - {"longleftarrow;", "⟵"}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 2140 "HTMLCharacterReference.gperf" - {"utdot;", "⋰"}, - {""}, -#line 887 "HTMLCharacterReference.gperf" - {"colone;", "≔"}, - {""}, - {""}, -#line 2083 "HTMLCharacterReference.gperf" - {"trie;", "≜"}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 235 "HTMLCharacterReference.gperf" - {"Imacr;", "Ī"}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 972 "HTMLCharacterReference.gperf" - {"divideontimes;", "⋇"}, - {""}, -#line 787 "HTMLCharacterReference.gperf" - {"boxHu;", "╧"}, - {""}, -#line 518 "HTMLCharacterReference.gperf" - {"ShortUpArrow;", "↑"}, - {""}, -#line 38 "HTMLCharacterReference.gperf" - {"Aopf;", "𝔸"}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 1826 "HTMLCharacterReference.gperf" - {"reals;", "ℝ"}, - {""}, - {""}, -#line 1175 "HTMLCharacterReference.gperf" - {"hardcy;", "ъ"}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 429 "HTMLCharacterReference.gperf" - {"Otimes;", "⨷"}, -#line 1837 "HTMLCharacterReference.gperf" - {"rhov;", "ϱ"}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 796 "HTMLCharacterReference.gperf" - {"boxVh;", "╫"}, - {""}, -#line 292 "HTMLCharacterReference.gperf" - {"LeftTriangle;", "⊲"}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 1674 "HTMLCharacterReference.gperf" - {"ord;", "⩝"}, -#line 676 "HTMLCharacterReference.gperf" - {"amalg;", "⨿"}, - {""}, -#line 1882 "HTMLCharacterReference.gperf" - {"sc;", "≻"}, - {""}, -#line 1679 "HTMLCharacterReference.gperf" - {"ordm", "º"}, + {"reg", "®"}, + {""}, + {""}, + {""}, +#line 1458 "HTMLCharacterReference.gperf" + {"mldr;", "…"}, + {""}, +#line 1830 "HTMLCharacterReference.gperf" + {"reg;", "®"}, + {""}, + {""}, + {""}, +#line 89 "HTMLCharacterReference.gperf" + {"ContourIntegral;", "∮"}, + {""}, + {""}, + {""}, + {""}, +#line 2050 "HTMLCharacterReference.gperf" + {"theta;", "θ"}, + {""}, + {""}, +#line 1855 "HTMLCharacterReference.gperf" + {"rnmid;", "⫮"}, + {""}, +#line 801 "HTMLCharacterReference.gperf" + {"boxdL;", "╕"}, +#line 1671 "HTMLCharacterReference.gperf" + {"operp;", "⦹"}, +#line 698 "HTMLCharacterReference.gperf" + {"angrtvb;", "⊾"}, +#line 2230 "HTMLCharacterReference.gperf" + {"ycy;", "ы"}, + {""}, +#line 1479 "HTMLCharacterReference.gperf" + {"nabla;", "∇"}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 1964 "HTMLCharacterReference.gperf" + {"square;", "□"}, + {""}, + {""}, +#line 351 "HTMLCharacterReference.gperf" + {"Nopf;", "ℕ"}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 1348 "HTMLCharacterReference.gperf" + {"lessgtr;", "≶"}, + {""}, + {""}, + {""}, +#line 1389 "HTMLCharacterReference.gperf" + {"lowbar;", "_"}, +#line 1391 "HTMLCharacterReference.gperf" + {"lozenge;", "◊"}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 1626 "HTMLCharacterReference.gperf" + {"nvlt;", "<⃒"}, + {""}, +#line 1287 "HTMLCharacterReference.gperf" + {"langle;", "⟨"}, + {""}, + {""}, +#line 2212 "HTMLCharacterReference.gperf" + {"xmap;", "⟼"}, + {""}, + {""}, +#line 1870 "HTMLCharacterReference.gperf" + {"rsqb;", "]"}, + {""}, +#line 1968 "HTMLCharacterReference.gperf" + {"sscr;", "𝓈"}, + {""}, +#line 939 "HTMLCharacterReference.gperf" + {"cylcty;", "⌭"}, + {""}, +#line 1378 "HTMLCharacterReference.gperf" + {"longleftarrow;", "⟵"}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 2141 "HTMLCharacterReference.gperf" + {"utdot;", "⋰"}, + {""}, +#line 888 "HTMLCharacterReference.gperf" + {"colone;", "≔"}, + {""}, + {""}, +#line 2084 "HTMLCharacterReference.gperf" + {"trie;", "≜"}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 236 "HTMLCharacterReference.gperf" + {"Imacr;", "Ī"}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 973 "HTMLCharacterReference.gperf" + {"divideontimes;", "⋇"}, + {""}, +#line 788 "HTMLCharacterReference.gperf" + {"boxHu;", "╧"}, + {""}, +#line 519 "HTMLCharacterReference.gperf" + {"ShortUpArrow;", "↑"}, + {""}, +#line 39 "HTMLCharacterReference.gperf" + {"Aopf;", "𝔸"}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 1827 "HTMLCharacterReference.gperf" + {"reals;", "ℝ"}, + {""}, + {""}, +#line 1176 "HTMLCharacterReference.gperf" + {"hardcy;", "ъ"}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 430 "HTMLCharacterReference.gperf" + {"Otimes;", "⨷"}, +#line 1838 "HTMLCharacterReference.gperf" + {"rhov;", "ϱ"}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 797 "HTMLCharacterReference.gperf" + {"boxVh;", "╫"}, + {""}, +#line 293 "HTMLCharacterReference.gperf" + {"LeftTriangle;", "⊲"}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 1675 "HTMLCharacterReference.gperf" + {"ord;", "⩝"}, +#line 677 "HTMLCharacterReference.gperf" + {"amalg;", "⨿"}, + {""}, +#line 1883 "HTMLCharacterReference.gperf" + {"sc;", "≻"}, + {""}, #line 1680 "HTMLCharacterReference.gperf" - {"ordm;", "º"}, - {""}, - {""}, - {""}, - {""}, -#line 1898 "HTMLCharacterReference.gperf" - {"sdote;", "⩦"}, - {""}, - {""}, -#line 106 "HTMLCharacterReference.gperf" - {"Del;", "∇"}, - {""}, - {""}, - {""}, - {""}, -#line 1440 "HTMLCharacterReference.gperf" - {"mcy;", "м"}, - {""}, -#line 807 "HTMLCharacterReference.gperf" - {"boxhd;", "┬"}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 147 "HTMLCharacterReference.gperf" - {"Downarrow;", "⇓"}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 1894 "HTMLCharacterReference.gperf" - {"scsim;", "≿"}, - {""}, - {""}, -#line 262 "HTMLCharacterReference.gperf" - {"Kcy;", "К"}, -#line 1994 "HTMLCharacterReference.gperf" - {"succ;", "≻"}, - {""}, - {""}, -#line 294 "HTMLCharacterReference.gperf" - {"LeftTriangleEqual;", "⊴"}, - {""}, - {""}, -#line 364 "HTMLCharacterReference.gperf" - {"NotGreaterSlantEqual;", "⩾̸"}, - {""}, - {""}, - {""}, - {""}, -#line 1812 "HTMLCharacterReference.gperf" - {"rbrkslu;", "⦐"}, -#line 210 "HTMLCharacterReference.gperf" - {"HARDcy;", "Ъ"}, - {""}, -#line 1833 "HTMLCharacterReference.gperf" - {"rhard;", "⇁"}, - {""}, - {""}, -#line 738 "HTMLCharacterReference.gperf" - {"becaus;", "∵"}, - {""}, -#line 1976 "HTMLCharacterReference.gperf" - {"sub;", "⊂"}, - {""}, - {""}, -#line 2132 "HTMLCharacterReference.gperf" - {"upsilon;", "υ"}, - {""}, - {""}, - {""}, -#line 92 "HTMLCharacterReference.gperf" - {"Cross;", "⨯"}, - {""}, - {""}, -#line 2250 "HTMLCharacterReference.gperf" - {"zwj;", "‍"}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 1363 "HTMLCharacterReference.gperf" - {"lltri;", "◺"}, - {""}, - {""}, - {""}, -#line 361 "HTMLCharacterReference.gperf" - {"NotGreaterFullEqual;", "≧̸"}, - {""}, + {"ordm", "º"}, +#line 1681 "HTMLCharacterReference.gperf" + {"ordm;", "º"}, + {""}, + {""}, + {""}, + {""}, +#line 1899 "HTMLCharacterReference.gperf" + {"sdote;", "⩦"}, + {""}, + {""}, +#line 107 "HTMLCharacterReference.gperf" + {"Del;", "∇"}, + {""}, + {""}, + {""}, + {""}, +#line 1441 "HTMLCharacterReference.gperf" + {"mcy;", "м"}, + {""}, +#line 808 "HTMLCharacterReference.gperf" + {"boxhd;", "┬"}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 148 "HTMLCharacterReference.gperf" + {"Downarrow;", "⇓"}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 1895 "HTMLCharacterReference.gperf" + {"scsim;", "≿"}, + {""}, + {""}, +#line 263 "HTMLCharacterReference.gperf" + {"Kcy;", "К"}, +#line 1995 "HTMLCharacterReference.gperf" + {"succ;", "≻"}, + {""}, + {""}, +#line 295 "HTMLCharacterReference.gperf" + {"LeftTriangleEqual;", "⊴"}, + {""}, + {""}, +#line 365 "HTMLCharacterReference.gperf" + {"NotGreaterSlantEqual;", "⩾̸"}, + {""}, + {""}, + {""}, + {""}, +#line 1813 "HTMLCharacterReference.gperf" + {"rbrkslu;", "⦐"}, +#line 211 "HTMLCharacterReference.gperf" + {"HARDcy;", "Ъ"}, + {""}, +#line 1834 "HTMLCharacterReference.gperf" + {"rhard;", "⇁"}, + {""}, + {""}, +#line 739 "HTMLCharacterReference.gperf" + {"becaus;", "∵"}, + {""}, +#line 1977 "HTMLCharacterReference.gperf" + {"sub;", "⊂"}, + {""}, + {""}, +#line 2133 "HTMLCharacterReference.gperf" + {"upsilon;", "υ"}, + {""}, + {""}, + {""}, +#line 93 "HTMLCharacterReference.gperf" + {"Cross;", "⨯"}, + {""}, + {""}, +#line 2251 "HTMLCharacterReference.gperf" + {"zwj;", "‍"}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 1364 "HTMLCharacterReference.gperf" + {"lltri;", "◺"}, + {""}, + {""}, + {""}, +#line 362 "HTMLCharacterReference.gperf" + {"NotGreaterFullEqual;", "≧̸"}, + {""}, +#line 1886 "HTMLCharacterReference.gperf" + {"scaron;", "š"}, + {""}, +#line 876 "HTMLCharacterReference.gperf" + {"circledR;", "®"}, #line 1885 "HTMLCharacterReference.gperf" - {"scaron;", "š"}, - {""}, -#line 875 "HTMLCharacterReference.gperf" - {"circledR;", "®"}, -#line 1884 "HTMLCharacterReference.gperf" - {"scap;", "⪸"}, -#line 626 "HTMLCharacterReference.gperf" - {"Wedge;", "⋀"}, - {""}, - {""}, -#line 757 "HTMLCharacterReference.gperf" - {"biguplus;", "⨄"}, - {""}, - {""}, -#line 2185 "HTMLCharacterReference.gperf" - {"vsubnE;", "⫋︀"}, - {""}, -#line 1683 "HTMLCharacterReference.gperf" - {"orslope;", "⩗"}, - {""}, -#line 164 "HTMLCharacterReference.gperf" - {"Emacr;", "Ē"}, - {""}, - {""}, -#line 239 "HTMLCharacterReference.gperf" - {"Integral;", "∫"}, -#line 48 "HTMLCharacterReference.gperf" - {"Backslash;", "∖"}, - {""}, - {""}, - {""}, - {""}, -#line 126 "HTMLCharacterReference.gperf" - {"DoubleLongLeftArrow;", "⟸"}, - {""}, - {""}, - {""}, -#line 1472 "HTMLCharacterReference.gperf" - {"nLl;", "⋘̸"}, + {"scap;", "⪸"}, +#line 627 "HTMLCharacterReference.gperf" + {"Wedge;", "⋀"}, + {""}, + {""}, +#line 758 "HTMLCharacterReference.gperf" + {"biguplus;", "⨄"}, + {""}, + {""}, +#line 2186 "HTMLCharacterReference.gperf" + {"vsubnE;", "⫋︀"}, + {""}, +#line 1684 "HTMLCharacterReference.gperf" + {"orslope;", "⩗"}, + {""}, +#line 165 "HTMLCharacterReference.gperf" + {"Emacr;", "Ē"}, + {""}, + {""}, +#line 240 "HTMLCharacterReference.gperf" + {"Integral;", "∫"}, +#line 49 "HTMLCharacterReference.gperf" + {"Backslash;", "∖"}, + {""}, + {""}, + {""}, + {""}, #line 127 "HTMLCharacterReference.gperf" - {"DoubleLongLeftRightArrow;", "⟺"}, -#line 123 "HTMLCharacterReference.gperf" - {"DoubleLeftArrow;", "⇐"}, - {""}, - {""}, -#line 1125 "HTMLCharacterReference.gperf" - {"geqslant;", "⩾"}, - {""}, - {""}, -#line 1113 "HTMLCharacterReference.gperf" - {"gacute;", "ǵ"}, - {""}, -#line 1335 "HTMLCharacterReference.gperf" - {"leqslant;", "⩽"}, -#line 628 "HTMLCharacterReference.gperf" - {"Wopf;", "𝕎"}, - {""}, -#line 1280 "HTMLCharacterReference.gperf" - {"lacute;", "ĺ"}, - {""}, - {""}, - {""}, -#line 1007 "HTMLCharacterReference.gperf" - {"eacute", "é"}, + {"DoubleLongLeftArrow;", "⟸"}, + {""}, + {""}, + {""}, +#line 1473 "HTMLCharacterReference.gperf" + {"nLl;", "⋘̸"}, +#line 128 "HTMLCharacterReference.gperf" + {"DoubleLongLeftRightArrow;", "⟺"}, +#line 124 "HTMLCharacterReference.gperf" + {"DoubleLeftArrow;", "⇐"}, + {""}, + {""}, +#line 1126 "HTMLCharacterReference.gperf" + {"geqslant;", "⩾"}, + {""}, + {""}, +#line 1114 "HTMLCharacterReference.gperf" + {"gacute;", "ǵ"}, + {""}, +#line 1336 "HTMLCharacterReference.gperf" + {"leqslant;", "⩽"}, +#line 629 "HTMLCharacterReference.gperf" + {"Wopf;", "𝕎"}, + {""}, +#line 1281 "HTMLCharacterReference.gperf" + {"lacute;", "ĺ"}, + {""}, + {""}, + {""}, #line 1008 "HTMLCharacterReference.gperf" - {"eacute;", "é"}, - {""}, - {""}, - {""}, - {""}, -#line 840 "HTMLCharacterReference.gperf" - {"cacute;", "ć"}, - {""}, - {""}, - {""}, -#line 655 "HTMLCharacterReference.gperf" - {"aacute", "á"}, + {"eacute", "é"}, +#line 1009 "HTMLCharacterReference.gperf" + {"eacute;", "é"}, + {""}, + {""}, + {""}, + {""}, +#line 841 "HTMLCharacterReference.gperf" + {"cacute;", "ć"}, + {""}, + {""}, + {""}, #line 656 "HTMLCharacterReference.gperf" - {"aacute;", "á"}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 568 "HTMLCharacterReference.gperf" - {"Uacute", "Ú"}, + {"aacute", "á"}, +#line 657 "HTMLCharacterReference.gperf" + {"aacute;", "á"}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, #line 569 "HTMLCharacterReference.gperf" - {"Uacute;", "Ú"}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 424 "HTMLCharacterReference.gperf" - {"Oscr;", "𝒪"}, - {""}, - {""}, - {""}, -#line 936 "HTMLCharacterReference.gperf" - {"cwconint;", "∲"}, - {""}, - {""}, - {""}, - {""}, -#line 722 "HTMLCharacterReference.gperf" - {"awconint;", "∳"}, -#line 345 "HTMLCharacterReference.gperf" - {"NestedLessLess;", "≪"}, -#line 769 "HTMLCharacterReference.gperf" - {"blk14;", "░"}, - {""}, - {""}, -#line 398 "HTMLCharacterReference.gperf" - {"NotTilde;", "≁"}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, + {"Uacute", "Ú"}, +#line 570 "HTMLCharacterReference.gperf" + {"Uacute;", "Ú"}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 425 "HTMLCharacterReference.gperf" + {"Oscr;", "𝒪"}, + {""}, + {""}, + {""}, +#line 937 "HTMLCharacterReference.gperf" + {"cwconint;", "∲"}, + {""}, + {""}, + {""}, + {""}, +#line 723 "HTMLCharacterReference.gperf" + {"awconint;", "∳"}, +#line 346 "HTMLCharacterReference.gperf" + {"NestedLessLess;", "≪"}, #line 770 "HTMLCharacterReference.gperf" - {"blk34;", "▓"}, - {""}, - {""}, - {""}, -#line 1654 "HTMLCharacterReference.gperf" - {"ogt;", "⧁"}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 2178 "HTMLCharacterReference.gperf" - {"vltri;", "⊲"}, - {""}, - {""}, - {""}, -#line 564 "HTMLCharacterReference.gperf" - {"Topf;", "𝕋"}, -#line 1950 "HTMLCharacterReference.gperf" - {"sqcap;", "⊓"}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 1736 "HTMLCharacterReference.gperf" - {"pr;", "≺"}, - {""}, - {""}, - {""}, - {""}, -#line 316 "HTMLCharacterReference.gperf" - {"Longleftarrow;", "⟸"}, - {""}, -#line 768 "HTMLCharacterReference.gperf" - {"blk12;", "▒"}, -#line 1029 "HTMLCharacterReference.gperf" - {"elsdot;", "⪗"}, - {""}, - {""}, -#line 1320 "HTMLCharacterReference.gperf" - {"ldsh;", "↲"}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 888 "HTMLCharacterReference.gperf" - {"coloneq;", "≔"}, - {""}, -#line 443 "HTMLCharacterReference.gperf" - {"Popf;", "ℙ"}, - {""}, - {""}, - {""}, - {""}, -#line 1469 "HTMLCharacterReference.gperf" - {"nGtv;", "≫̸"}, - {""}, - {""}, -#line 911 "HTMLCharacterReference.gperf" - {"cudarrl;", "⤸"}, - {""}, -#line 2248 "HTMLCharacterReference.gperf" - {"zopf;", "𝕫"}, - {""}, -#line 2224 "HTMLCharacterReference.gperf" - {"xwedge;", "⋀"}, - {""}, - {""}, -#line 1873 "HTMLCharacterReference.gperf" - {"rtimes;", "⋊"}, -#line 1651 "HTMLCharacterReference.gperf" - {"ogon;", "˛"}, -#line 1760 "HTMLCharacterReference.gperf" - {"prsim;", "≾"}, - {""}, - {""}, -#line 1270 "HTMLCharacterReference.gperf" - {"kjcy;", "ќ"}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, + {"blk14;", "░"}, + {""}, + {""}, +#line 399 "HTMLCharacterReference.gperf" + {"NotTilde;", "≁"}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 771 "HTMLCharacterReference.gperf" + {"blk34;", "▓"}, + {""}, + {""}, + {""}, +#line 1655 "HTMLCharacterReference.gperf" + {"ogt;", "⧁"}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 2179 "HTMLCharacterReference.gperf" + {"vltri;", "⊲"}, + {""}, + {""}, + {""}, +#line 565 "HTMLCharacterReference.gperf" + {"Topf;", "𝕋"}, #line 1951 "HTMLCharacterReference.gperf" - {"sqcaps;", "⊓︀"}, - {""}, -#line 1887 "HTMLCharacterReference.gperf" - {"sce;", "⪰"}, -#line 86 "HTMLCharacterReference.gperf" - {"Congruent;", "≡"}, -#line 1427 "HTMLCharacterReference.gperf" - {"mDDot;", "∺"}, - {""}, -#line 761 "HTMLCharacterReference.gperf" - {"blacklozenge;", "⧫"}, -#line 1855 "HTMLCharacterReference.gperf" - {"roang;", "⟭"}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 1076 "HTMLCharacterReference.gperf" - {"fflig;", "ff"}, - {""}, -#line 1555 "HTMLCharacterReference.gperf" - {"notinvc;", "⋶"}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 1863 "HTMLCharacterReference.gperf" - {"rpargt;", "⦔"}, -#line 1758 "HTMLCharacterReference.gperf" - {"prop;", "∝"}, - {""}, - {""}, -#line 51 "HTMLCharacterReference.gperf" - {"Bcy;", "Б"}, - {""}, -#line 1881 "HTMLCharacterReference.gperf" - {"sbquo;", "‚"}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 1738 "HTMLCharacterReference.gperf" - {"prap;", "⪷"}, - {""}, - {""}, - {""}, -#line 634 "HTMLCharacterReference.gperf" - {"YAcy;", "Я"}, - {""}, -#line 2160 "HTMLCharacterReference.gperf" - {"varrho;", "ϱ"}, - {""}, -#line 1903 "HTMLCharacterReference.gperf" - {"sect", "§"}, + {"sqcap;", "⊓"}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 1737 "HTMLCharacterReference.gperf" + {"pr;", "≺"}, + {""}, + {""}, + {""}, + {""}, +#line 317 "HTMLCharacterReference.gperf" + {"Longleftarrow;", "⟸"}, + {""}, +#line 769 "HTMLCharacterReference.gperf" + {"blk12;", "▒"}, +#line 1030 "HTMLCharacterReference.gperf" + {"elsdot;", "⪗"}, + {""}, + {""}, +#line 1321 "HTMLCharacterReference.gperf" + {"ldsh;", "↲"}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 889 "HTMLCharacterReference.gperf" + {"coloneq;", "≔"}, + {""}, +#line 444 "HTMLCharacterReference.gperf" + {"Popf;", "ℙ"}, + {""}, + {""}, + {""}, + {""}, +#line 1470 "HTMLCharacterReference.gperf" + {"nGtv;", "≫̸"}, + {""}, + {""}, +#line 912 "HTMLCharacterReference.gperf" + {"cudarrl;", "⤸"}, + {""}, +#line 2249 "HTMLCharacterReference.gperf" + {"zopf;", "𝕫"}, + {""}, +#line 2225 "HTMLCharacterReference.gperf" + {"xwedge;", "⋀"}, + {""}, + {""}, +#line 1874 "HTMLCharacterReference.gperf" + {"rtimes;", "⋊"}, +#line 1652 "HTMLCharacterReference.gperf" + {"ogon;", "˛"}, +#line 1761 "HTMLCharacterReference.gperf" + {"prsim;", "≾"}, + {""}, + {""}, +#line 1271 "HTMLCharacterReference.gperf" + {"kjcy;", "ќ"}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 1952 "HTMLCharacterReference.gperf" + {"sqcaps;", "⊓︀"}, + {""}, +#line 1888 "HTMLCharacterReference.gperf" + {"sce;", "⪰"}, +#line 87 "HTMLCharacterReference.gperf" + {"Congruent;", "≡"}, +#line 1428 "HTMLCharacterReference.gperf" + {"mDDot;", "∺"}, + {""}, +#line 762 "HTMLCharacterReference.gperf" + {"blacklozenge;", "⧫"}, +#line 1856 "HTMLCharacterReference.gperf" + {"roang;", "⟭"}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 1077 "HTMLCharacterReference.gperf" + {"fflig;", "ff"}, + {""}, +#line 1556 "HTMLCharacterReference.gperf" + {"notinvc;", "⋶"}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 1864 "HTMLCharacterReference.gperf" + {"rpargt;", "⦔"}, +#line 1759 "HTMLCharacterReference.gperf" + {"prop;", "∝"}, + {""}, + {""}, +#line 52 "HTMLCharacterReference.gperf" + {"Bcy;", "Б"}, + {""}, +#line 1882 "HTMLCharacterReference.gperf" + {"sbquo;", "‚"}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 1739 "HTMLCharacterReference.gperf" + {"prap;", "⪷"}, + {""}, + {""}, + {""}, +#line 635 "HTMLCharacterReference.gperf" + {"YAcy;", "Я"}, + {""}, +#line 2161 "HTMLCharacterReference.gperf" + {"varrho;", "ϱ"}, + {""}, #line 1904 "HTMLCharacterReference.gperf" - {"sect;", "§"}, - {""}, - {""}, -#line 145 "HTMLCharacterReference.gperf" - {"DownTee;", "⊤"}, - {""}, -#line 1914 "HTMLCharacterReference.gperf" - {"shcy;", "ш"}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 1628 "HTMLCharacterReference.gperf" - {"nvrtrie;", "⊵⃒"}, -#line 538 "HTMLCharacterReference.gperf" - {"SucceedsTilde;", "≿"}, - {""}, -#line 1897 "HTMLCharacterReference.gperf" - {"sdotb;", "⊡"}, -#line 1942 "HTMLCharacterReference.gperf" - {"softcy;", "ь"}, - {""}, - {""}, - {""}, -#line 35 "HTMLCharacterReference.gperf" - {"Amacr;", "Ā"}, -#line 533 "HTMLCharacterReference.gperf" - {"Subset;", "⋐"}, - {""}, - {""}, -#line 622 "HTMLCharacterReference.gperf" - {"Vopf;", "𝕍"}, -#line 1861 "HTMLCharacterReference.gperf" - {"rotimes;", "⨵"}, - {""}, - {""}, - {""}, - {""}, -#line 1811 "HTMLCharacterReference.gperf" - {"rbrksld;", "⦎"}, -#line 1997 "HTMLCharacterReference.gperf" - {"succeq;", "⪰"}, -#line 2018 "HTMLCharacterReference.gperf" - {"suplarr;", "⥻"}, -#line 442 "HTMLCharacterReference.gperf" - {"Poincareplane;", "ℌ"}, -#line 944 "HTMLCharacterReference.gperf" - {"dash;", "‐"}, -#line 1199 "HTMLCharacterReference.gperf" - {"iacute", "í"}, -#line 1200 "HTMLCharacterReference.gperf" - {"iacute;", "í"}, - {""}, - {""}, - {""}, + {"sect", "§"}, +#line 1905 "HTMLCharacterReference.gperf" + {"sect;", "§"}, + {""}, + {""}, +#line 146 "HTMLCharacterReference.gperf" + {"DownTee;", "⊤"}, + {""}, +#line 1915 "HTMLCharacterReference.gperf" + {"shcy;", "ш"}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 1629 "HTMLCharacterReference.gperf" + {"nvrtrie;", "⊵⃒"}, +#line 539 "HTMLCharacterReference.gperf" + {"SucceedsTilde;", "≿"}, + {""}, +#line 1898 "HTMLCharacterReference.gperf" + {"sdotb;", "⊡"}, +#line 1943 "HTMLCharacterReference.gperf" + {"softcy;", "ь"}, + {""}, + {""}, + {""}, +#line 36 "HTMLCharacterReference.gperf" + {"Amacr;", "Ā"}, +#line 534 "HTMLCharacterReference.gperf" + {"Subset;", "⋐"}, + {""}, + {""}, +#line 623 "HTMLCharacterReference.gperf" + {"Vopf;", "𝕍"}, +#line 1862 "HTMLCharacterReference.gperf" + {"rotimes;", "⨵"}, + {""}, + {""}, + {""}, + {""}, +#line 1812 "HTMLCharacterReference.gperf" + {"rbrksld;", "⦎"}, +#line 1998 "HTMLCharacterReference.gperf" + {"succeq;", "⪰"}, +#line 2019 "HTMLCharacterReference.gperf" + {"suplarr;", "⥻"}, +#line 443 "HTMLCharacterReference.gperf" + {"Poincareplane;", "ℌ"}, #line 945 "HTMLCharacterReference.gperf" - {"dashv;", "⊣"}, - {""}, - {""}, -#line 734 "HTMLCharacterReference.gperf" - {"bbrktbrk;", "⎶"}, - {""}, -#line 1983 "HTMLCharacterReference.gperf" - {"subne;", "⊊"}, -#line 882 "HTMLCharacterReference.gperf" - {"cirmid;", "⫯"}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 1759 "HTMLCharacterReference.gperf" - {"propto;", "∝"}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 1531 "HTMLCharacterReference.gperf" - {"nlE;", "≦̸"}, - {""}, -#line 933 "HTMLCharacterReference.gperf" - {"curvearrowright;", "↷"}, - {""}, - {""}, - {""}, - {""}, -#line 637 "HTMLCharacterReference.gperf" - {"Yacute", "Ý"}, -#line 638 "HTMLCharacterReference.gperf" - {"Yacute;", "Ý"}, - {""}, -#line 1608 "HTMLCharacterReference.gperf" - {"ntriangleleft;", "⋪"}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 1958 "HTMLCharacterReference.gperf" - {"sqsup;", "⊐"}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 536 "HTMLCharacterReference.gperf" - {"SucceedsEqual;", "⪰"}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 269 "HTMLCharacterReference.gperf" - {"Lacute;", "Ĺ"}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 2234 "HTMLCharacterReference.gperf" - {"yopf;", "𝕪"}, - {""}, -#line 1160 "HTMLCharacterReference.gperf" - {"gtlPar;", "⦕"}, -#line 1917 "HTMLCharacterReference.gperf" - {"shy", "­"}, - {""}, - {""}, -#line 1263 "HTMLCharacterReference.gperf" - {"kappa;", "κ"}, - {""}, -#line 1867 "HTMLCharacterReference.gperf" - {"rscr;", "𝓇"}, + {"dash;", "‐"}, +#line 1200 "HTMLCharacterReference.gperf" + {"iacute", "í"}, +#line 1201 "HTMLCharacterReference.gperf" + {"iacute;", "í"}, + {""}, + {""}, + {""}, +#line 946 "HTMLCharacterReference.gperf" + {"dashv;", "⊣"}, + {""}, + {""}, +#line 735 "HTMLCharacterReference.gperf" + {"bbrktbrk;", "⎶"}, + {""}, +#line 1984 "HTMLCharacterReference.gperf" + {"subne;", "⊊"}, +#line 883 "HTMLCharacterReference.gperf" + {"cirmid;", "⫯"}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 1760 "HTMLCharacterReference.gperf" + {"propto;", "∝"}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 1532 "HTMLCharacterReference.gperf" + {"nlE;", "≦̸"}, + {""}, +#line 934 "HTMLCharacterReference.gperf" + {"curvearrowright;", "↷"}, + {""}, + {""}, + {""}, + {""}, +#line 638 "HTMLCharacterReference.gperf" + {"Yacute", "Ý"}, +#line 639 "HTMLCharacterReference.gperf" + {"Yacute;", "Ý"}, + {""}, +#line 1609 "HTMLCharacterReference.gperf" + {"ntriangleleft;", "⋪"}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 1959 "HTMLCharacterReference.gperf" + {"sqsup;", "⊐"}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 537 "HTMLCharacterReference.gperf" + {"SucceedsEqual;", "⪰"}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 270 "HTMLCharacterReference.gperf" + {"Lacute;", "Ĺ"}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 2235 "HTMLCharacterReference.gperf" + {"yopf;", "𝕪"}, + {""}, +#line 1161 "HTMLCharacterReference.gperf" + {"gtlPar;", "⦕"}, #line 1918 "HTMLCharacterReference.gperf" - {"shy;", "­"}, -#line 1327 "HTMLCharacterReference.gperf" - {"leftrightarrow;", "↔"}, + {"shy", "­"}, + {""}, + {""}, +#line 1264 "HTMLCharacterReference.gperf" + {"kappa;", "κ"}, + {""}, +#line 1868 "HTMLCharacterReference.gperf" + {"rscr;", "𝓇"}, +#line 1919 "HTMLCharacterReference.gperf" + {"shy;", "­"}, #line 1328 "HTMLCharacterReference.gperf" - {"leftrightarrows;", "⇆"}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 666 "HTMLCharacterReference.gperf" - {"aelig", "æ"}, + {"leftrightarrow;", "↔"}, +#line 1329 "HTMLCharacterReference.gperf" + {"leftrightarrows;", "⇆"}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, #line 667 "HTMLCharacterReference.gperf" - {"aelig;", "æ"}, - {""}, - {""}, - {""}, -#line 1808 "HTMLCharacterReference.gperf" - {"rbrace;", "}"}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 1168 "HTMLCharacterReference.gperf" - {"gtrsim;", "≳"}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 977 "HTMLCharacterReference.gperf" - {"dollar;", "$"}, - {""}, -#line 1804 "HTMLCharacterReference.gperf" - {"ratio;", "∶"}, -#line 1740 "HTMLCharacterReference.gperf" - {"pre;", "⪯"}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 473 "HTMLCharacterReference.gperf" - {"ReverseUpEquilibrium;", "⥯"}, - {""}, -#line 1554 "HTMLCharacterReference.gperf" - {"notinvb;", "⋷"}, - {""}, -#line 222 "HTMLCharacterReference.gperf" - {"IEcy;", "Е"}, -#line 1982 "HTMLCharacterReference.gperf" - {"subnE;", "⫋"}, -#line 567 "HTMLCharacterReference.gperf" - {"Tstrok;", "Ŧ"}, - {""}, - {""}, -#line 1445 "HTMLCharacterReference.gperf" - {"micro", "µ"}, + {"aelig", "æ"}, +#line 668 "HTMLCharacterReference.gperf" + {"aelig;", "æ"}, + {""}, + {""}, + {""}, +#line 1809 "HTMLCharacterReference.gperf" + {"rbrace;", "}"}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 1169 "HTMLCharacterReference.gperf" + {"gtrsim;", "≳"}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 978 "HTMLCharacterReference.gperf" + {"dollar;", "$"}, + {""}, +#line 1805 "HTMLCharacterReference.gperf" + {"ratio;", "∶"}, +#line 1741 "HTMLCharacterReference.gperf" + {"pre;", "⪯"}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 474 "HTMLCharacterReference.gperf" + {"ReverseUpEquilibrium;", "⥯"}, + {""}, +#line 1555 "HTMLCharacterReference.gperf" + {"notinvb;", "⋷"}, + {""}, +#line 223 "HTMLCharacterReference.gperf" + {"IEcy;", "Е"}, +#line 1983 "HTMLCharacterReference.gperf" + {"subnE;", "⫋"}, +#line 568 "HTMLCharacterReference.gperf" + {"Tstrok;", "Ŧ"}, + {""}, + {""}, #line 1446 "HTMLCharacterReference.gperf" - {"micro;", "µ"}, - {""}, - {""}, - {""}, -#line 1610 "HTMLCharacterReference.gperf" - {"ntriangleright;", "⋫"}, - {""}, + {"micro", "µ"}, +#line 1447 "HTMLCharacterReference.gperf" + {"micro;", "µ"}, + {""}, + {""}, + {""}, #line 1611 "HTMLCharacterReference.gperf" - {"ntrianglerighteq;", "⋭"}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 1891 "HTMLCharacterReference.gperf" - {"scnap;", "⪺"}, - {""}, -#line 1791 "HTMLCharacterReference.gperf" - {"rarr;", "→"}, - {""}, - {""}, - {""}, -#line 1448 "HTMLCharacterReference.gperf" - {"midast;", "*"}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 1456 "HTMLCharacterReference.gperf" - {"mlcp;", "⫛"}, - {""}, -#line 1195 "HTMLCharacterReference.gperf" - {"hslash;", "ℏ"}, - {""}, - {""}, - {""}, -#line 368 "HTMLCharacterReference.gperf" - {"NotLeftTriangle;", "⋪"}, - {""}, - {""}, + {"ntriangleright;", "⋫"}, + {""}, +#line 1612 "HTMLCharacterReference.gperf" + {"ntrianglerighteq;", "⋭"}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 1892 "HTMLCharacterReference.gperf" + {"scnap;", "⪺"}, + {""}, +#line 1792 "HTMLCharacterReference.gperf" + {"rarr;", "→"}, + {""}, + {""}, + {""}, +#line 1449 "HTMLCharacterReference.gperf" + {"midast;", "*"}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 1457 "HTMLCharacterReference.gperf" + {"mlcp;", "⫛"}, + {""}, +#line 1196 "HTMLCharacterReference.gperf" + {"hslash;", "ℏ"}, + {""}, + {""}, + {""}, #line 369 "HTMLCharacterReference.gperf" - {"NotLeftTriangleBar;", "⧏̸"}, - {""}, + {"NotLeftTriangle;", "⋪"}, + {""}, + {""}, #line 370 "HTMLCharacterReference.gperf" - {"NotLeftTriangleEqual;", "⋬"}, -#line 1362 "HTMLCharacterReference.gperf" - {"llhard;", "⥫"}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 300 "HTMLCharacterReference.gperf" - {"LeftVectorBar;", "⥒"}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 1801 "HTMLCharacterReference.gperf" - {"rarrtl;", "↣"}, -#line 415 "HTMLCharacterReference.gperf" - {"Ograve", "Ò"}, + {"NotLeftTriangleBar;", "⧏̸"}, + {""}, +#line 371 "HTMLCharacterReference.gperf" + {"NotLeftTriangleEqual;", "⋬"}, +#line 1363 "HTMLCharacterReference.gperf" + {"llhard;", "⥫"}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 301 "HTMLCharacterReference.gperf" + {"LeftVectorBar;", "⥒"}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 1802 "HTMLCharacterReference.gperf" + {"rarrtl;", "↣"}, #line 416 "HTMLCharacterReference.gperf" - {"Ograve;", "Ò"}, -#line 1329 "HTMLCharacterReference.gperf" - {"leftrightharpoons;", "⇋"}, - {""}, -#line 1709 "HTMLCharacterReference.gperf" - {"pfr;", "𝔭"}, -#line 877 "HTMLCharacterReference.gperf" - {"circledast;", "⊛"}, -#line 1616 "HTMLCharacterReference.gperf" - {"nvDash;", "⊭"}, - {""}, - {""}, -#line 1460 "HTMLCharacterReference.gperf" - {"mopf;", "𝕞"}, - {""}, -#line 328 "HTMLCharacterReference.gperf" - {"MediumSpace;", " "}, -#line 1691 "HTMLCharacterReference.gperf" - {"otimes;", "⊗"}, - {""}, -#line 1527 "HTMLCharacterReference.gperf" - {"nisd;", "⋺"}, - {""}, - {""}, -#line 1984 "HTMLCharacterReference.gperf" - {"subplus;", "⪿"}, -#line 1323 "HTMLCharacterReference.gperf" - {"leftarrowtail;", "↢"}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 1807 "HTMLCharacterReference.gperf" - {"rbbrk;", "❳"}, -#line 264 "HTMLCharacterReference.gperf" - {"Kopf;", "𝕂"}, - {""}, - {""}, - {""}, - {""}, -#line 1813 "HTMLCharacterReference.gperf" - {"rcaron;", "ř"}, -#line 171 "HTMLCharacterReference.gperf" - {"EqualTilde;", "≂"}, - {""}, -#line 739 "HTMLCharacterReference.gperf" - {"because;", "∵"}, - {""}, - {""}, -#line 1609 "HTMLCharacterReference.gperf" - {"ntrianglelefteq;", "⋬"}, - {""}, - {""}, - {""}, - {""}, -#line 2136 "HTMLCharacterReference.gperf" - {"urcrop;", "⌎"}, -#line 1911 "HTMLCharacterReference.gperf" - {"sfrown;", "⌢"}, - {""}, - {""}, - {""}, -#line 1166 "HTMLCharacterReference.gperf" - {"gtreqqless;", "⪌"}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 381 "HTMLCharacterReference.gperf" - {"NotPrecedesSlantEqual;", "⋠"}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 2015 "HTMLCharacterReference.gperf" - {"supedot;", "⫄"}, - {""}, -#line 1799 "HTMLCharacterReference.gperf" - {"rarrpl;", "⥅"}, -#line 2134 "HTMLCharacterReference.gperf" - {"urcorn;", "⌝"}, - {""}, - {""}, - {""}, -#line 2233 "HTMLCharacterReference.gperf" - {"yicy;", "ї"}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 1646 "HTMLCharacterReference.gperf" - {"odot;", "⊙"}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 2065 "HTMLCharacterReference.gperf" - {"tint;", "∭"}, -#line 1741 "HTMLCharacterReference.gperf" - {"prec;", "≺"}, - {""}, - {""}, - {""}, -#line 2106 "HTMLCharacterReference.gperf" - {"udarr;", "⇅"}, - {""}, -#line 1578 "HTMLCharacterReference.gperf" - {"nsccue;", "⋡"}, -#line 1297 "HTMLCharacterReference.gperf" - {"larrsim;", "⥳"}, - {""}, -#line 1798 "HTMLCharacterReference.gperf" - {"rarrlp;", "↬"}, - {""}, - {""}, -#line 926 "HTMLCharacterReference.gperf" - {"curlyeqprec;", "⋞"}, - {""}, - {""}, - {""}, -#line 508 "HTMLCharacterReference.gperf" - {"Sacute;", "Ś"}, -#line 1761 "HTMLCharacterReference.gperf" - {"prurel;", "⊰"}, -#line 2139 "HTMLCharacterReference.gperf" - {"uscr;", "𝓊"}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 348 "HTMLCharacterReference.gperf" - {"NoBreak;", "⁠"}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 1544 "HTMLCharacterReference.gperf" - {"nltri;", "⋪"}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 1960 "HTMLCharacterReference.gperf" - {"sqsupset;", "⊐"}, + {"Ograve", "Ò"}, +#line 417 "HTMLCharacterReference.gperf" + {"Ograve;", "Ò"}, +#line 1330 "HTMLCharacterReference.gperf" + {"leftrightharpoons;", "⇋"}, + {""}, +#line 1710 "HTMLCharacterReference.gperf" + {"pfr;", "𝔭"}, +#line 878 "HTMLCharacterReference.gperf" + {"circledast;", "⊛"}, +#line 1617 "HTMLCharacterReference.gperf" + {"nvDash;", "⊭"}, + {""}, + {""}, +#line 1461 "HTMLCharacterReference.gperf" + {"mopf;", "𝕞"}, + {""}, +#line 329 "HTMLCharacterReference.gperf" + {"MediumSpace;", " "}, #line 1692 "HTMLCharacterReference.gperf" - {"otimesas;", "⨶"}, + {"otimes;", "⊗"}, + {""}, +#line 1528 "HTMLCharacterReference.gperf" + {"nisd;", "⋺"}, + {""}, + {""}, +#line 1985 "HTMLCharacterReference.gperf" + {"subplus;", "⪿"}, +#line 1324 "HTMLCharacterReference.gperf" + {"leftarrowtail;", "↢"}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 1808 "HTMLCharacterReference.gperf" + {"rbbrk;", "❳"}, +#line 265 "HTMLCharacterReference.gperf" + {"Kopf;", "𝕂"}, + {""}, + {""}, + {""}, + {""}, +#line 1814 "HTMLCharacterReference.gperf" + {"rcaron;", "ř"}, +#line 172 "HTMLCharacterReference.gperf" + {"EqualTilde;", "≂"}, + {""}, +#line 740 "HTMLCharacterReference.gperf" + {"because;", "∵"}, + {""}, + {""}, +#line 1610 "HTMLCharacterReference.gperf" + {"ntrianglelefteq;", "⋬"}, + {""}, + {""}, + {""}, + {""}, +#line 2137 "HTMLCharacterReference.gperf" + {"urcrop;", "⌎"}, +#line 1912 "HTMLCharacterReference.gperf" + {"sfrown;", "⌢"}, + {""}, + {""}, + {""}, +#line 1167 "HTMLCharacterReference.gperf" + {"gtreqqless;", "⪌"}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 382 "HTMLCharacterReference.gperf" + {"NotPrecedesSlantEqual;", "⋠"}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 2016 "HTMLCharacterReference.gperf" + {"supedot;", "⫄"}, + {""}, +#line 1800 "HTMLCharacterReference.gperf" + {"rarrpl;", "⥅"}, +#line 2135 "HTMLCharacterReference.gperf" + {"urcorn;", "⌝"}, + {""}, + {""}, + {""}, +#line 2234 "HTMLCharacterReference.gperf" + {"yicy;", "ї"}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 1647 "HTMLCharacterReference.gperf" + {"odot;", "⊙"}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 2066 "HTMLCharacterReference.gperf" + {"tint;", "∭"}, +#line 1742 "HTMLCharacterReference.gperf" + {"prec;", "≺"}, + {""}, + {""}, + {""}, +#line 2107 "HTMLCharacterReference.gperf" + {"udarr;", "⇅"}, + {""}, +#line 1579 "HTMLCharacterReference.gperf" + {"nsccue;", "⋡"}, +#line 1298 "HTMLCharacterReference.gperf" + {"larrsim;", "⥳"}, + {""}, +#line 1799 "HTMLCharacterReference.gperf" + {"rarrlp;", "↬"}, + {""}, + {""}, +#line 927 "HTMLCharacterReference.gperf" + {"curlyeqprec;", "⋞"}, + {""}, + {""}, + {""}, +#line 509 "HTMLCharacterReference.gperf" + {"Sacute;", "Ś"}, +#line 1762 "HTMLCharacterReference.gperf" + {"prurel;", "⊰"}, +#line 2140 "HTMLCharacterReference.gperf" + {"uscr;", "𝓊"}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 349 "HTMLCharacterReference.gperf" + {"NoBreak;", "⁠"}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 1545 "HTMLCharacterReference.gperf" + {"nltri;", "⋪"}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, #line 1961 "HTMLCharacterReference.gperf" - {"sqsupseteq;", "⊒"}, -#line 1713 "HTMLCharacterReference.gperf" - {"phone;", "☎"}, - {""}, -#line 1857 "HTMLCharacterReference.gperf" - {"robrk;", "⟧"}, - {""}, -#line 1638 "HTMLCharacterReference.gperf" - {"oast;", "⊛"}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 1752 "HTMLCharacterReference.gperf" - {"prnap;", "⪹"}, - {""}, - {""}, - {""}, -#line 225 "HTMLCharacterReference.gperf" - {"Iacute", "Í"}, + {"sqsupset;", "⊐"}, +#line 1693 "HTMLCharacterReference.gperf" + {"otimesas;", "⨶"}, +#line 1962 "HTMLCharacterReference.gperf" + {"sqsupseteq;", "⊒"}, +#line 1714 "HTMLCharacterReference.gperf" + {"phone;", "☎"}, + {""}, +#line 1858 "HTMLCharacterReference.gperf" + {"robrk;", "⟧"}, + {""}, +#line 1639 "HTMLCharacterReference.gperf" + {"oast;", "⊛"}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 1753 "HTMLCharacterReference.gperf" + {"prnap;", "⪹"}, + {""}, + {""}, + {""}, #line 226 "HTMLCharacterReference.gperf" - {"Iacute;", "Í"}, - {""}, - {""}, -#line 302 "HTMLCharacterReference.gperf" - {"Leftrightarrow;", "⇔"}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 749 "HTMLCharacterReference.gperf" - {"bigcup;", "⋃"}, - {""}, -#line 1130 "HTMLCharacterReference.gperf" - {"gesdotol;", "⪄"}, - {""}, -#line 103 "HTMLCharacterReference.gperf" - {"Dashv;", "⫤"}, - {""}, -#line 2247 "HTMLCharacterReference.gperf" - {"zigrarr;", "⇝"}, - {""}, -#line 1624 "HTMLCharacterReference.gperf" - {"nvle;", "≤⃒"}, -#line 2074 "HTMLCharacterReference.gperf" - {"trade;", "™"}, -#line 1479 "HTMLCharacterReference.gperf" - {"nacute;", "ń"}, - {""}, -#line 698 "HTMLCharacterReference.gperf" - {"angrtvbd;", "⦝"}, - {""}, - {""}, -#line 726 "HTMLCharacterReference.gperf" - {"backepsilon;", "϶"}, - {""}, -#line 125 "HTMLCharacterReference.gperf" - {"DoubleLeftTee;", "⫤"}, - {""}, - {""}, - {""}, - {""}, -#line 2100 "HTMLCharacterReference.gperf" - {"uarr;", "↑"}, - {""}, -#line 1685 "HTMLCharacterReference.gperf" - {"oscr;", "ℴ"}, - {""}, -#line 927 "HTMLCharacterReference.gperf" - {"curlyeqsucc;", "⋟"}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 1795 "HTMLCharacterReference.gperf" - {"rarrc;", "⤳"}, -#line 373 "HTMLCharacterReference.gperf" - {"NotLessGreater;", "≸"}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 879 "HTMLCharacterReference.gperf" - {"circleddash;", "⊝"}, - {""}, - {""}, - {""}, - {""}, -#line 646 "HTMLCharacterReference.gperf" - {"Zacute;", "Ź"}, - {""}, - {""}, - {""}, -#line 956 "HTMLCharacterReference.gperf" - {"delta;", "δ"}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 296 "HTMLCharacterReference.gperf" - {"LeftUpTeeVector;", "⥠"}, - {""}, - {""}, - {""}, - {""}, -#line 1954 "HTMLCharacterReference.gperf" - {"sqsub;", "⊏"}, - {""}, - {""}, - {""}, - {""}, -#line 298 "HTMLCharacterReference.gperf" - {"LeftUpVectorBar;", "⥘"}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 320 "HTMLCharacterReference.gperf" - {"LowerLeftArrow;", "↙"}, - {""}, - {""}, - {""}, -#line 1999 "HTMLCharacterReference.gperf" - {"succneqq;", "⪶"}, -#line 587 "HTMLCharacterReference.gperf" - {"UnionPlus;", "⊎"}, - {""}, - {""}, -#line 1827 "HTMLCharacterReference.gperf" - {"rect;", "▭"}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 121 "HTMLCharacterReference.gperf" - {"DoubleDot;", "¨"}, -#line 1224 "HTMLCharacterReference.gperf" - {"imped;", "Ƶ"}, - {""}, -#line 1177 "HTMLCharacterReference.gperf" - {"harrcir;", "⥈"}, - {""}, - {""}, - {""}, - {""}, -#line 1764 "HTMLCharacterReference.gperf" - {"puncsp;", " "}, - {""}, -#line 56 "HTMLCharacterReference.gperf" - {"Bopf;", "𝔹"}, - {""}, - {""}, -#line 951 "HTMLCharacterReference.gperf" - {"ddagger;", "‡"}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 266 "HTMLCharacterReference.gperf" - {"LJcy;", "Љ"}, - {""}, - {""}, - {""}, - {""}, -#line 89 "HTMLCharacterReference.gperf" - {"Copf;", "ℂ"}, -#line 1080 "HTMLCharacterReference.gperf" - {"fjlig;", "fj"}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 1186 "HTMLCharacterReference.gperf" - {"hksearow;", "⤥"}, -#line 353 "HTMLCharacterReference.gperf" - {"NotCupCap;", "≭"}, - {""}, -#line 463 "HTMLCharacterReference.gperf" - {"Racute;", "Ŕ"}, - {""}, - {""}, - {""}, -#line 1707 "HTMLCharacterReference.gperf" - {"perp;", "⊥"}, - {""}, - {""}, -#line 1870 "HTMLCharacterReference.gperf" - {"rsquo;", "’"}, -#line 1871 "HTMLCharacterReference.gperf" - {"rsquor;", "’"}, - {""}, - {""}, - {""}, - {""}, -#line 287 "HTMLCharacterReference.gperf" - {"LeftRightArrow;", "↔"}, -#line 153 "HTMLCharacterReference.gperf" - {"Eacute", "É"}, -#line 154 "HTMLCharacterReference.gperf" - {"Eacute;", "É"}, - {""}, - {""}, - {""}, - {""}, -#line 1675 "HTMLCharacterReference.gperf" - {"order;", "ℴ"}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 1979 "HTMLCharacterReference.gperf" - {"sube;", "⊆"}, - {""}, -#line 1434 "HTMLCharacterReference.gperf" - {"mapsto;", "↦"}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 2135 "HTMLCharacterReference.gperf" - {"urcorner;", "⌝"}, -#line 1266 "HTMLCharacterReference.gperf" - {"kcy;", "к"}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 1043 "HTMLCharacterReference.gperf" - {"eplus;", "⩱"}, - {""}, - {""}, -#line 1922 "HTMLCharacterReference.gperf" - {"sim;", "∼"}, - {""}, -#line 741 "HTMLCharacterReference.gperf" - {"bepsi;", "϶"}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 2069 "HTMLCharacterReference.gperf" - {"topcir;", "⫱"}, - {""}, -#line 1378 "HTMLCharacterReference.gperf" - {"longleftrightarrow;", "⟷"}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 701 "HTMLCharacterReference.gperf" - {"angzarr;", "⍼"}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 946 "HTMLCharacterReference.gperf" - {"dbkarow;", "⤏"}, -#line 1815 "HTMLCharacterReference.gperf" - {"rceil;", "⌉"}, - {""}, - {""}, -#line 1476 "HTMLCharacterReference.gperf" - {"nVDash;", "⊯"}, - {""}, - {""}, - {""}, -#line 1972 "HTMLCharacterReference.gperf" - {"starf;", "★"}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 2147 "HTMLCharacterReference.gperf" - {"uwangle;", "⦧"}, - {""}, - {""}, - {""}, -#line 784 "HTMLCharacterReference.gperf" - {"boxHD;", "╦"}, - {""}, -#line 1744 "HTMLCharacterReference.gperf" - {"preceq;", "⪯"}, - {""}, -#line 1928 "HTMLCharacterReference.gperf" - {"siml;", "⪝"}, - {""}, -#line 1649 "HTMLCharacterReference.gperf" - {"ofcir;", "⦿"}, - {""}, -#line 447 "HTMLCharacterReference.gperf" - {"PrecedesSlantEqual;", "≼"}, -#line 81 "HTMLCharacterReference.gperf" - {"ClockwiseContourIntegral;", "∲"}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 97 "HTMLCharacterReference.gperf" - {"DDotrahd;", "⤑"}, -#line 1482 "HTMLCharacterReference.gperf" - {"napE;", "⩰̸"}, -#line 1217 "HTMLCharacterReference.gperf" - {"ijlig;", "ij"}, - {""}, - {""}, - {""}, -#line 1581 "HTMLCharacterReference.gperf" - {"nshortmid;", "∤"}, - {""}, -#line 878 "HTMLCharacterReference.gperf" - {"circledcirc;", "⊚"}, - {""}, - {""}, - {""}, -#line 806 "HTMLCharacterReference.gperf" - {"boxhU;", "╨"}, - {""}, - {""}, - {""}, + {"Iacute", "Í"}, +#line 227 "HTMLCharacterReference.gperf" + {"Iacute;", "Í"}, + {""}, + {""}, +#line 303 "HTMLCharacterReference.gperf" + {"Leftrightarrow;", "⇔"}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 750 "HTMLCharacterReference.gperf" + {"bigcup;", "⋃"}, + {""}, +#line 1131 "HTMLCharacterReference.gperf" + {"gesdotol;", "⪄"}, + {""}, +#line 104 "HTMLCharacterReference.gperf" + {"Dashv;", "⫤"}, + {""}, +#line 2248 "HTMLCharacterReference.gperf" + {"zigrarr;", "⇝"}, + {""}, +#line 1625 "HTMLCharacterReference.gperf" + {"nvle;", "≤⃒"}, +#line 2075 "HTMLCharacterReference.gperf" + {"trade;", "™"}, +#line 1480 "HTMLCharacterReference.gperf" + {"nacute;", "ń"}, + {""}, +#line 699 "HTMLCharacterReference.gperf" + {"angrtvbd;", "⦝"}, + {""}, + {""}, +#line 727 "HTMLCharacterReference.gperf" + {"backepsilon;", "϶"}, + {""}, +#line 126 "HTMLCharacterReference.gperf" + {"DoubleLeftTee;", "⫤"}, + {""}, + {""}, + {""}, + {""}, +#line 2101 "HTMLCharacterReference.gperf" + {"uarr;", "↑"}, + {""}, +#line 1686 "HTMLCharacterReference.gperf" + {"oscr;", "ℴ"}, + {""}, +#line 928 "HTMLCharacterReference.gperf" + {"curlyeqsucc;", "⋟"}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 1796 "HTMLCharacterReference.gperf" + {"rarrc;", "⤳"}, +#line 374 "HTMLCharacterReference.gperf" + {"NotLessGreater;", "≸"}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 880 "HTMLCharacterReference.gperf" + {"circleddash;", "⊝"}, + {""}, + {""}, + {""}, + {""}, +#line 647 "HTMLCharacterReference.gperf" + {"Zacute;", "Ź"}, + {""}, + {""}, + {""}, +#line 957 "HTMLCharacterReference.gperf" + {"delta;", "δ"}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 297 "HTMLCharacterReference.gperf" + {"LeftUpTeeVector;", "⥠"}, + {""}, + {""}, + {""}, + {""}, +#line 1955 "HTMLCharacterReference.gperf" + {"sqsub;", "⊏"}, + {""}, + {""}, + {""}, + {""}, +#line 299 "HTMLCharacterReference.gperf" + {"LeftUpVectorBar;", "⥘"}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 321 "HTMLCharacterReference.gperf" + {"LowerLeftArrow;", "↙"}, + {""}, + {""}, + {""}, +#line 2000 "HTMLCharacterReference.gperf" + {"succneqq;", "⪶"}, +#line 588 "HTMLCharacterReference.gperf" + {"UnionPlus;", "⊎"}, + {""}, + {""}, +#line 1828 "HTMLCharacterReference.gperf" + {"rect;", "▭"}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 122 "HTMLCharacterReference.gperf" + {"DoubleDot;", "¨"}, +#line 1225 "HTMLCharacterReference.gperf" + {"imped;", "Ƶ"}, + {""}, +#line 1178 "HTMLCharacterReference.gperf" + {"harrcir;", "⥈"}, + {""}, + {""}, + {""}, + {""}, +#line 1765 "HTMLCharacterReference.gperf" + {"puncsp;", " "}, + {""}, +#line 57 "HTMLCharacterReference.gperf" + {"Bopf;", "𝔹"}, + {""}, + {""}, +#line 952 "HTMLCharacterReference.gperf" + {"ddagger;", "‡"}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 267 "HTMLCharacterReference.gperf" + {"LJcy;", "Љ"}, + {""}, + {""}, + {""}, + {""}, +#line 90 "HTMLCharacterReference.gperf" + {"Copf;", "ℂ"}, +#line 1081 "HTMLCharacterReference.gperf" + {"fjlig;", "fj"}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 1187 "HTMLCharacterReference.gperf" + {"hksearow;", "⤥"}, +#line 354 "HTMLCharacterReference.gperf" + {"NotCupCap;", "≭"}, + {""}, +#line 464 "HTMLCharacterReference.gperf" + {"Racute;", "Ŕ"}, + {""}, + {""}, + {""}, +#line 1708 "HTMLCharacterReference.gperf" + {"perp;", "⊥"}, + {""}, + {""}, +#line 1871 "HTMLCharacterReference.gperf" + {"rsquo;", "’"}, +#line 1872 "HTMLCharacterReference.gperf" + {"rsquor;", "’"}, + {""}, + {""}, + {""}, + {""}, +#line 288 "HTMLCharacterReference.gperf" + {"LeftRightArrow;", "↔"}, +#line 154 "HTMLCharacterReference.gperf" + {"Eacute", "É"}, +#line 155 "HTMLCharacterReference.gperf" + {"Eacute;", "É"}, + {""}, + {""}, + {""}, + {""}, +#line 1676 "HTMLCharacterReference.gperf" + {"order;", "ℴ"}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 1980 "HTMLCharacterReference.gperf" + {"sube;", "⊆"}, + {""}, +#line 1435 "HTMLCharacterReference.gperf" + {"mapsto;", "↦"}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 2136 "HTMLCharacterReference.gperf" + {"urcorner;", "⌝"}, +#line 1267 "HTMLCharacterReference.gperf" + {"kcy;", "к"}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 1044 "HTMLCharacterReference.gperf" + {"eplus;", "⩱"}, + {""}, + {""}, +#line 1923 "HTMLCharacterReference.gperf" + {"sim;", "∼"}, + {""}, +#line 742 "HTMLCharacterReference.gperf" + {"bepsi;", "϶"}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 2070 "HTMLCharacterReference.gperf" + {"topcir;", "⫱"}, + {""}, +#line 1379 "HTMLCharacterReference.gperf" + {"longleftrightarrow;", "⟷"}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 702 "HTMLCharacterReference.gperf" + {"angzarr;", "⍼"}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 947 "HTMLCharacterReference.gperf" + {"dbkarow;", "⤏"}, +#line 1816 "HTMLCharacterReference.gperf" + {"rceil;", "⌉"}, + {""}, + {""}, +#line 1477 "HTMLCharacterReference.gperf" + {"nVDash;", "⊯"}, + {""}, + {""}, + {""}, +#line 1973 "HTMLCharacterReference.gperf" + {"starf;", "★"}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 2148 "HTMLCharacterReference.gperf" + {"uwangle;", "⦧"}, + {""}, + {""}, + {""}, +#line 785 "HTMLCharacterReference.gperf" + {"boxHD;", "╦"}, + {""}, +#line 1745 "HTMLCharacterReference.gperf" + {"preceq;", "⪯"}, + {""}, +#line 1929 "HTMLCharacterReference.gperf" + {"siml;", "⪝"}, + {""}, +#line 1650 "HTMLCharacterReference.gperf" + {"ofcir;", "⦿"}, + {""}, +#line 448 "HTMLCharacterReference.gperf" + {"PrecedesSlantEqual;", "≼"}, +#line 82 "HTMLCharacterReference.gperf" + {"ClockwiseContourIntegral;", "∲"}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, #line 98 "HTMLCharacterReference.gperf" - {"DJcy;", "Ђ"}, -#line 122 "HTMLCharacterReference.gperf" - {"DoubleDownArrow;", "⇓"}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 2153 "HTMLCharacterReference.gperf" - {"varepsilon;", "ϵ"}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 2168 "HTMLCharacterReference.gperf" - {"vartriangleright;", "⊳"}, - {""}, -#line 1793 "HTMLCharacterReference.gperf" - {"rarrb;", "⇥"}, - {""}, -#line 1933 "HTMLCharacterReference.gperf" - {"slarr;", "←"}, - {""}, -#line 1055 "HTMLCharacterReference.gperf" - {"equivDD;", "⩸"}, - {""}, -#line 2115 "HTMLCharacterReference.gperf" - {"uhblk;", "▀"}, -#line 2102 "HTMLCharacterReference.gperf" - {"ubreve;", "ŭ"}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 1647 "HTMLCharacterReference.gperf" - {"odsold;", "⦼"}, - {""}, - {""}, - {""}, -#line 336 "HTMLCharacterReference.gperf" - {"Nacute;", "Ń"}, - {""}, -#line 1915 "HTMLCharacterReference.gperf" - {"shortmid;", "∣"}, - {""}, - {""}, - {""}, - {""}, -#line 1141 "HTMLCharacterReference.gperf" - {"glj;", "⪤"}, -#line 204 "HTMLCharacterReference.gperf" - {"GreaterGreater;", "⪢"}, -#line 547 "HTMLCharacterReference.gperf" - {"TRADE;", "™"}, -#line 1959 "HTMLCharacterReference.gperf" - {"sqsupe;", "⊒"}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 2111 "HTMLCharacterReference.gperf" - {"ugrave", "ù"}, + {"DDotrahd;", "⤑"}, +#line 1483 "HTMLCharacterReference.gperf" + {"napE;", "⩰̸"}, +#line 1218 "HTMLCharacterReference.gperf" + {"ijlig;", "ij"}, + {""}, + {""}, + {""}, +#line 1582 "HTMLCharacterReference.gperf" + {"nshortmid;", "∤"}, + {""}, +#line 879 "HTMLCharacterReference.gperf" + {"circledcirc;", "⊚"}, + {""}, + {""}, + {""}, +#line 807 "HTMLCharacterReference.gperf" + {"boxhU;", "╨"}, + {""}, + {""}, + {""}, +#line 99 "HTMLCharacterReference.gperf" + {"DJcy;", "Ђ"}, +#line 123 "HTMLCharacterReference.gperf" + {"DoubleDownArrow;", "⇓"}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 2154 "HTMLCharacterReference.gperf" + {"varepsilon;", "ϵ"}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 2169 "HTMLCharacterReference.gperf" + {"vartriangleright;", "⊳"}, + {""}, +#line 1794 "HTMLCharacterReference.gperf" + {"rarrb;", "⇥"}, + {""}, +#line 1934 "HTMLCharacterReference.gperf" + {"slarr;", "←"}, + {""}, +#line 1056 "HTMLCharacterReference.gperf" + {"equivDD;", "⩸"}, + {""}, +#line 2116 "HTMLCharacterReference.gperf" + {"uhblk;", "▀"}, +#line 2103 "HTMLCharacterReference.gperf" + {"ubreve;", "ŭ"}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 1648 "HTMLCharacterReference.gperf" + {"odsold;", "⦼"}, + {""}, + {""}, + {""}, +#line 337 "HTMLCharacterReference.gperf" + {"Nacute;", "Ń"}, + {""}, +#line 1916 "HTMLCharacterReference.gperf" + {"shortmid;", "∣"}, + {""}, + {""}, + {""}, + {""}, +#line 1142 "HTMLCharacterReference.gperf" + {"glj;", "⪤"}, +#line 205 "HTMLCharacterReference.gperf" + {"GreaterGreater;", "⪢"}, +#line 548 "HTMLCharacterReference.gperf" + {"TRADE;", "™"}, +#line 1960 "HTMLCharacterReference.gperf" + {"sqsupe;", "⊒"}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, #line 2112 "HTMLCharacterReference.gperf" - {"ugrave;", "ù"}, - {""}, - {""}, - {""}, - {""}, -#line 2055 "HTMLCharacterReference.gperf" - {"thkap;", "≈"}, - {""}, - {""}, - {""}, -#line 1540 "HTMLCharacterReference.gperf" - {"nles;", "⩽̸"}, -#line 427 "HTMLCharacterReference.gperf" - {"Otilde", "Õ"}, + {"ugrave", "ù"}, +#line 2113 "HTMLCharacterReference.gperf" + {"ugrave;", "ù"}, + {""}, + {""}, + {""}, + {""}, +#line 2056 "HTMLCharacterReference.gperf" + {"thkap;", "≈"}, + {""}, + {""}, + {""}, +#line 1541 "HTMLCharacterReference.gperf" + {"nles;", "⩽̸"}, #line 428 "HTMLCharacterReference.gperf" - {"Otilde;", "Õ"}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 1705 "HTMLCharacterReference.gperf" - {"period;", "."}, - {""}, -#line 2133 "HTMLCharacterReference.gperf" - {"upuparrows;", "⇈"}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 25 "HTMLCharacterReference.gperf" - {"Aacute", "Á"}, + {"Otilde", "Õ"}, +#line 429 "HTMLCharacterReference.gperf" + {"Otilde;", "Õ"}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 1706 "HTMLCharacterReference.gperf" + {"period;", "."}, + {""}, +#line 2134 "HTMLCharacterReference.gperf" + {"upuparrows;", "⇈"}, + {""}, + {""}, + {""}, + {""}, + {""}, #line 26 "HTMLCharacterReference.gperf" - {"Aacute;", "Á"}, - {""}, -#line 1956 "HTMLCharacterReference.gperf" - {"sqsubset;", "⊏"}, -#line 2003 "HTMLCharacterReference.gperf" - {"sung;", "♪"}, + {"Aacute", "Á"}, +#line 27 "HTMLCharacterReference.gperf" + {"Aacute;", "Á"}, + {""}, #line 1957 "HTMLCharacterReference.gperf" - {"sqsubseteq;", "⊑"}, - {""}, - {""}, - {""}, -#line 1330 "HTMLCharacterReference.gperf" - {"leftrightsquigarrow;", "↭"}, -#line 1889 "HTMLCharacterReference.gperf" - {"scirc;", "ŝ"}, -#line 929 "HTMLCharacterReference.gperf" - {"curlywedge;", "⋏"}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 1365 "HTMLCharacterReference.gperf" - {"lmoust;", "⎰"}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 1437 "HTMLCharacterReference.gperf" - {"mapstoup;", "↥"}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 1763 "HTMLCharacterReference.gperf" - {"psi;", "ψ"}, -#line 84 "HTMLCharacterReference.gperf" - {"Colon;", "∷"}, -#line 303 "HTMLCharacterReference.gperf" - {"LessEqualGreater;", "⋚"}, - {""}, - {""}, - {""}, - {""}, -#line 1652 "HTMLCharacterReference.gperf" - {"ograve", "ò"}, + {"sqsubset;", "⊏"}, +#line 2004 "HTMLCharacterReference.gperf" + {"sung;", "♪"}, +#line 1958 "HTMLCharacterReference.gperf" + {"sqsubseteq;", "⊑"}, + {""}, + {""}, + {""}, +#line 1331 "HTMLCharacterReference.gperf" + {"leftrightsquigarrow;", "↭"}, +#line 1890 "HTMLCharacterReference.gperf" + {"scirc;", "ŝ"}, +#line 930 "HTMLCharacterReference.gperf" + {"curlywedge;", "⋏"}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 1366 "HTMLCharacterReference.gperf" + {"lmoust;", "⎰"}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 1438 "HTMLCharacterReference.gperf" + {"mapstoup;", "↥"}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 1764 "HTMLCharacterReference.gperf" + {"psi;", "ψ"}, +#line 85 "HTMLCharacterReference.gperf" + {"Colon;", "∷"}, +#line 304 "HTMLCharacterReference.gperf" + {"LessEqualGreater;", "⋚"}, + {""}, + {""}, + {""}, + {""}, #line 1653 "HTMLCharacterReference.gperf" - {"ograve;", "ò"}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 365 "HTMLCharacterReference.gperf" - {"NotGreaterTilde;", "≵"}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 778 "HTMLCharacterReference.gperf" - {"bowtie;", "⋈"}, - {""}, -#line 1783 "HTMLCharacterReference.gperf" - {"radic;", "√"}, - {""}, -#line 107 "HTMLCharacterReference.gperf" - {"Delta;", "Δ"}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 1275 "HTMLCharacterReference.gperf" - {"lAtail;", "⤛"}, - {""}, - {""}, -#line 2137 "HTMLCharacterReference.gperf" - {"uring;", "ů"}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 1947 "HTMLCharacterReference.gperf" - {"spades;", "♠"}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 1930 "HTMLCharacterReference.gperf" - {"simne;", "≆"}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 317 "HTMLCharacterReference.gperf" - {"Longleftrightarrow;", "⟺"}, - {""}, - {""}, -#line 1075 "HTMLCharacterReference.gperf" - {"ffilig;", "ffi"}, - {""}, - {""}, - {""}, - {""}, -#line 2052 "HTMLCharacterReference.gperf" - {"thickapprox;", "≈"}, - {""}, -#line 2043 "HTMLCharacterReference.gperf" - {"tcy;", "т"}, - {""}, -#line 293 "HTMLCharacterReference.gperf" - {"LeftTriangleBar;", "⧏"}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 2012 "HTMLCharacterReference.gperf" - {"supdot;", "⪾"}, - {""}, - {""}, -#line 1710 "HTMLCharacterReference.gperf" - {"phi;", "φ"}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 396 "HTMLCharacterReference.gperf" - {"NotSuperset;", "⊃⃒"}, - {""}, - {""}, - {""}, - {""}, -#line 1749 "HTMLCharacterReference.gperf" - {"prime;", "′"}, -#line 342 "HTMLCharacterReference.gperf" - {"NegativeThinSpace;", "​"}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 1452 "HTMLCharacterReference.gperf" - {"minus;", "−"}, - {""}, - {""}, - {""}, - {""}, -#line 1995 "HTMLCharacterReference.gperf" - {"succapprox;", "⪸"}, - {""}, - {""}, -#line 753 "HTMLCharacterReference.gperf" - {"bigsqcup;", "⨆"}, - {""}, - {""}, - {""}, -#line 1704 "HTMLCharacterReference.gperf" - {"percnt;", "%"}, - {""}, - {""}, -#line 1929 "HTMLCharacterReference.gperf" - {"simlE;", "⪟"}, - {""}, - {""}, - {""}, - {""}, -#line 1069 "HTMLCharacterReference.gperf" - {"exist;", "∃"}, - {""}, - {""}, - {""}, -#line 74 "HTMLCharacterReference.gperf" - {"CenterDot;", "·"}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 548 "HTMLCharacterReference.gperf" - {"TSHcy;", "Ћ"}, - {""}, - {""}, - {""}, -#line 1541 "HTMLCharacterReference.gperf" - {"nless;", "≮"}, - {""}, + {"ograve", "ò"}, +#line 1654 "HTMLCharacterReference.gperf" + {"ograve;", "ò"}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 366 "HTMLCharacterReference.gperf" + {"NotGreaterTilde;", "≵"}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 779 "HTMLCharacterReference.gperf" + {"bowtie;", "⋈"}, + {""}, +#line 1784 "HTMLCharacterReference.gperf" + {"radic;", "√"}, + {""}, +#line 108 "HTMLCharacterReference.gperf" + {"Delta;", "Δ"}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 1276 "HTMLCharacterReference.gperf" + {"lAtail;", "⤛"}, + {""}, + {""}, +#line 2138 "HTMLCharacterReference.gperf" + {"uring;", "ů"}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 1948 "HTMLCharacterReference.gperf" + {"spades;", "♠"}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 1931 "HTMLCharacterReference.gperf" + {"simne;", "≆"}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 318 "HTMLCharacterReference.gperf" + {"Longleftrightarrow;", "⟺"}, + {""}, + {""}, +#line 1076 "HTMLCharacterReference.gperf" + {"ffilig;", "ffi"}, + {""}, + {""}, + {""}, + {""}, +#line 2053 "HTMLCharacterReference.gperf" + {"thickapprox;", "≈"}, + {""}, +#line 2044 "HTMLCharacterReference.gperf" + {"tcy;", "т"}, + {""}, +#line 294 "HTMLCharacterReference.gperf" + {"LeftTriangleBar;", "⧏"}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 2013 "HTMLCharacterReference.gperf" + {"supdot;", "⪾"}, + {""}, + {""}, +#line 1711 "HTMLCharacterReference.gperf" + {"phi;", "φ"}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 397 "HTMLCharacterReference.gperf" + {"NotSuperset;", "⊃⃒"}, + {""}, + {""}, + {""}, + {""}, #line 1750 "HTMLCharacterReference.gperf" - {"primes;", "ℙ"}, - {""}, - {""}, - {""}, -#line 1825 "HTMLCharacterReference.gperf" - {"realpart;", "ℜ"}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 66 "HTMLCharacterReference.gperf" - {"Cayleys;", "ℭ"}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 1734 "HTMLCharacterReference.gperf" - {"pound", "£"}, + {"prime;", "′"}, +#line 343 "HTMLCharacterReference.gperf" + {"NegativeThinSpace;", "​"}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 1453 "HTMLCharacterReference.gperf" + {"minus;", "−"}, + {""}, + {""}, + {""}, + {""}, +#line 1996 "HTMLCharacterReference.gperf" + {"succapprox;", "⪸"}, + {""}, + {""}, +#line 754 "HTMLCharacterReference.gperf" + {"bigsqcup;", "⨆"}, + {""}, + {""}, + {""}, +#line 1705 "HTMLCharacterReference.gperf" + {"percnt;", "%"}, + {""}, + {""}, +#line 1930 "HTMLCharacterReference.gperf" + {"simlE;", "⪟"}, + {""}, + {""}, + {""}, + {""}, +#line 1070 "HTMLCharacterReference.gperf" + {"exist;", "∃"}, + {""}, + {""}, + {""}, +#line 75 "HTMLCharacterReference.gperf" + {"CenterDot;", "·"}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 549 "HTMLCharacterReference.gperf" + {"TSHcy;", "Ћ"}, + {""}, + {""}, + {""}, +#line 1542 "HTMLCharacterReference.gperf" + {"nless;", "≮"}, + {""}, +#line 1751 "HTMLCharacterReference.gperf" + {"primes;", "ℙ"}, + {""}, + {""}, + {""}, +#line 1826 "HTMLCharacterReference.gperf" + {"realpart;", "ℜ"}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 67 "HTMLCharacterReference.gperf" + {"Cayleys;", "ℭ"}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, #line 1735 "HTMLCharacterReference.gperf" - {"pound;", "£"}, - {""}, - {""}, -#line 188 "HTMLCharacterReference.gperf" - {"GJcy;", "Ѓ"}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 1655 "HTMLCharacterReference.gperf" - {"ohbar;", "⦵"}, - {""}, -#line 1027 "HTMLCharacterReference.gperf" - {"ell;", "ℓ"}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 1545 "HTMLCharacterReference.gperf" - {"nltrie;", "⋬"}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 410 "HTMLCharacterReference.gperf" - {"Ocirc", "Ô"}, + {"pound", "£"}, +#line 1736 "HTMLCharacterReference.gperf" + {"pound;", "£"}, + {""}, + {""}, +#line 189 "HTMLCharacterReference.gperf" + {"GJcy;", "Ѓ"}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 1656 "HTMLCharacterReference.gperf" + {"ohbar;", "⦵"}, + {""}, +#line 1028 "HTMLCharacterReference.gperf" + {"ell;", "ℓ"}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 1546 "HTMLCharacterReference.gperf" + {"nltrie;", "⋬"}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, #line 411 "HTMLCharacterReference.gperf" - {"Ocirc;", "Ô"}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 306 "HTMLCharacterReference.gperf" - {"LessLess;", "⪡"}, - {""}, -#line 673 "HTMLCharacterReference.gperf" - {"aleph;", "ℵ"}, -#line 1695 "HTMLCharacterReference.gperf" - {"ovbar;", "⌽"}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 452 "HTMLCharacterReference.gperf" - {"Proportional;", "∝"}, - {""}, - {""}, -#line 873 "HTMLCharacterReference.gperf" - {"circlearrowleft;", "↺"}, -#line 2239 "HTMLCharacterReference.gperf" - {"zacute;", "ź"}, -#line 1816 "HTMLCharacterReference.gperf" - {"rcub;", "}"}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 471 "HTMLCharacterReference.gperf" - {"ReverseElement;", "∋"}, - {""}, - {""}, -#line 731 "HTMLCharacterReference.gperf" - {"barwed;", "⌅"}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 343 "HTMLCharacterReference.gperf" - {"NegativeVeryThinSpace;", "​"}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 1931 "HTMLCharacterReference.gperf" - {"simplus;", "⨤"}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 1746 "HTMLCharacterReference.gperf" - {"precneqq;", "⪵"}, - {""}, - {""}, - {""}, - {""}, -#line 870 "HTMLCharacterReference.gperf" - {"cirE;", "⧃"}, - {""}, - {""}, - {""}, - {""}, -#line 1561 "HTMLCharacterReference.gperf" - {"nparallel;", "∦"}, - {""}, -#line 1851 "HTMLCharacterReference.gperf" - {"rlm;", "‏"}, -#line 71 "HTMLCharacterReference.gperf" - {"Cconint;", "∰"}, - {""}, - {""}, - {""}, - {""}, -#line 1557 "HTMLCharacterReference.gperf" - {"notniva;", "∌"}, - {""}, -#line 1271 "HTMLCharacterReference.gperf" - {"kopf;", "𝕜"}, - {""}, -#line 1955 "HTMLCharacterReference.gperf" - {"sqsube;", "⊑"}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 290 "HTMLCharacterReference.gperf" - {"LeftTeeArrow;", "↤"}, - {""}, -#line 335 "HTMLCharacterReference.gperf" - {"NJcy;", "Њ"}, - {""}, - {""}, - {""}, -#line 745 "HTMLCharacterReference.gperf" - {"between;", "≬"}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 1762 "HTMLCharacterReference.gperf" - {"pscr;", "𝓅"}, - {""}, - {""}, - {""}, -#line 130 "HTMLCharacterReference.gperf" - {"DoubleRightTee;", "⊨"}, - {""}, - {""}, -#line 590 "HTMLCharacterReference.gperf" - {"UpArrow;", "↑"}, - {""}, -#line 565 "HTMLCharacterReference.gperf" - {"TripleDot;", "⃛"}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 1794 "HTMLCharacterReference.gperf" - {"rarrbfs;", "⤠"}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 1868 "HTMLCharacterReference.gperf" - {"rsh;", "↱"}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 1530 "HTMLCharacterReference.gperf" - {"nlArr;", "⇍"}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 1876 "HTMLCharacterReference.gperf" - {"rtrif;", "▸"}, - {""}, - {""}, -#line 1454 "HTMLCharacterReference.gperf" - {"minusd;", "∸"}, - {""}, - {""}, - {""}, -#line 355 "HTMLCharacterReference.gperf" - {"NotElement;", "∉"}, - {""}, - {""}, -#line 1849 "HTMLCharacterReference.gperf" - {"rlarr;", "⇄"}, - {""}, - {""}, - {""}, - {""}, - {""}, + {"Ocirc", "Ô"}, +#line 412 "HTMLCharacterReference.gperf" + {"Ocirc;", "Ô"}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 307 "HTMLCharacterReference.gperf" + {"LessLess;", "⪡"}, + {""}, +#line 674 "HTMLCharacterReference.gperf" + {"aleph;", "ℵ"}, #line 1696 "HTMLCharacterReference.gperf" - {"par;", "∥"}, -#line 1702 "HTMLCharacterReference.gperf" - {"part;", "∂"}, - {""}, - {""}, - {""}, - {""}, -#line 1952 "HTMLCharacterReference.gperf" - {"sqcup;", "⊔"}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 1435 "HTMLCharacterReference.gperf" - {"mapstodown;", "↧"}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 2225 "HTMLCharacterReference.gperf" - {"yacute", "ý"}, -#line 2226 "HTMLCharacterReference.gperf" - {"yacute;", "ý"}, - {""}, - {""}, - {""}, -#line 1470 "HTMLCharacterReference.gperf" - {"nLeftarrow;", "⇍"}, - {""}, - {""}, - {""}, -#line 2060 "HTMLCharacterReference.gperf" - {"times", "×"}, -#line 2061 "HTMLCharacterReference.gperf" - {"times;", "×"}, -#line 2082 "HTMLCharacterReference.gperf" - {"tridot;", "◬"}, - {""}, -#line 620 "HTMLCharacterReference.gperf" - {"VeryThinSpace;", " "}, -#line 39 "HTMLCharacterReference.gperf" - {"ApplyFunction;", "⁡"}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 2141 "HTMLCharacterReference.gperf" - {"utilde;", "ũ"}, - {""}, -#line 2034 "HTMLCharacterReference.gperf" - {"swarrow;", "↙"}, - {""}, - {""}, - {""}, + {"ovbar;", "⌽"}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 453 "HTMLCharacterReference.gperf" + {"Proportional;", "∝"}, + {""}, + {""}, +#line 874 "HTMLCharacterReference.gperf" + {"circlearrowleft;", "↺"}, +#line 2240 "HTMLCharacterReference.gperf" + {"zacute;", "ź"}, +#line 1817 "HTMLCharacterReference.gperf" + {"rcub;", "}"}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 472 "HTMLCharacterReference.gperf" + {"ReverseElement;", "∋"}, + {""}, + {""}, +#line 732 "HTMLCharacterReference.gperf" + {"barwed;", "⌅"}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 344 "HTMLCharacterReference.gperf" + {"NegativeVeryThinSpace;", "​"}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 1932 "HTMLCharacterReference.gperf" + {"simplus;", "⨤"}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 1747 "HTMLCharacterReference.gperf" + {"precneqq;", "⪵"}, + {""}, + {""}, + {""}, + {""}, +#line 871 "HTMLCharacterReference.gperf" + {"cirE;", "⧃"}, + {""}, + {""}, + {""}, + {""}, +#line 1562 "HTMLCharacterReference.gperf" + {"nparallel;", "∦"}, + {""}, +#line 1852 "HTMLCharacterReference.gperf" + {"rlm;", "‏"}, +#line 72 "HTMLCharacterReference.gperf" + {"Cconint;", "∰"}, + {""}, + {""}, + {""}, + {""}, +#line 1558 "HTMLCharacterReference.gperf" + {"notniva;", "∌"}, + {""}, +#line 1272 "HTMLCharacterReference.gperf" + {"kopf;", "𝕜"}, + {""}, +#line 1956 "HTMLCharacterReference.gperf" + {"sqsube;", "⊑"}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 291 "HTMLCharacterReference.gperf" + {"LeftTeeArrow;", "↤"}, + {""}, +#line 336 "HTMLCharacterReference.gperf" + {"NJcy;", "Њ"}, + {""}, + {""}, + {""}, +#line 746 "HTMLCharacterReference.gperf" + {"between;", "≬"}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 1763 "HTMLCharacterReference.gperf" + {"pscr;", "𝓅"}, + {""}, + {""}, + {""}, +#line 131 "HTMLCharacterReference.gperf" + {"DoubleRightTee;", "⊨"}, + {""}, + {""}, +#line 591 "HTMLCharacterReference.gperf" + {"UpArrow;", "↑"}, + {""}, +#line 566 "HTMLCharacterReference.gperf" + {"TripleDot;", "⃛"}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 1795 "HTMLCharacterReference.gperf" + {"rarrbfs;", "⤠"}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 1869 "HTMLCharacterReference.gperf" + {"rsh;", "↱"}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 1531 "HTMLCharacterReference.gperf" + {"nlArr;", "⇍"}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 1877 "HTMLCharacterReference.gperf" + {"rtrif;", "▸"}, + {""}, + {""}, +#line 1455 "HTMLCharacterReference.gperf" + {"minusd;", "∸"}, + {""}, + {""}, + {""}, +#line 356 "HTMLCharacterReference.gperf" + {"NotElement;", "∉"}, + {""}, + {""}, +#line 1850 "HTMLCharacterReference.gperf" + {"rlarr;", "⇄"}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 1697 "HTMLCharacterReference.gperf" + {"par;", "∥"}, +#line 1703 "HTMLCharacterReference.gperf" + {"part;", "∂"}, + {""}, + {""}, + {""}, + {""}, #line 1953 "HTMLCharacterReference.gperf" - {"sqcups;", "⊔︀"}, -#line 2019 "HTMLCharacterReference.gperf" - {"supmult;", "⫂"}, -#line 1895 "HTMLCharacterReference.gperf" - {"scy;", "с"}, -#line 985 "HTMLCharacterReference.gperf" - {"doublebarwedge;", "⌆"}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 1311 "HTMLCharacterReference.gperf" - {"lcedil;", "ļ"}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 401 "HTMLCharacterReference.gperf" - {"NotTildeTilde;", "≉"}, - {""}, -#line 852 "HTMLCharacterReference.gperf" - {"ccedil", "ç"}, + {"sqcup;", "⊔"}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 1436 "HTMLCharacterReference.gperf" + {"mapstodown;", "↧"}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 2226 "HTMLCharacterReference.gperf" + {"yacute", "ý"}, +#line 2227 "HTMLCharacterReference.gperf" + {"yacute;", "ý"}, + {""}, + {""}, + {""}, +#line 1471 "HTMLCharacterReference.gperf" + {"nLeftarrow;", "⇍"}, + {""}, + {""}, + {""}, +#line 2061 "HTMLCharacterReference.gperf" + {"times", "×"}, +#line 2062 "HTMLCharacterReference.gperf" + {"times;", "×"}, +#line 2083 "HTMLCharacterReference.gperf" + {"tridot;", "◬"}, + {""}, +#line 621 "HTMLCharacterReference.gperf" + {"VeryThinSpace;", " "}, +#line 40 "HTMLCharacterReference.gperf" + {"ApplyFunction;", "⁡"}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 2142 "HTMLCharacterReference.gperf" + {"utilde;", "ũ"}, + {""}, +#line 2035 "HTMLCharacterReference.gperf" + {"swarrow;", "↙"}, + {""}, + {""}, + {""}, +#line 1954 "HTMLCharacterReference.gperf" + {"sqcups;", "⊔︀"}, +#line 2020 "HTMLCharacterReference.gperf" + {"supmult;", "⫂"}, +#line 1896 "HTMLCharacterReference.gperf" + {"scy;", "с"}, +#line 986 "HTMLCharacterReference.gperf" + {"doublebarwedge;", "⌆"}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 1312 "HTMLCharacterReference.gperf" + {"lcedil;", "ļ"}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 402 "HTMLCharacterReference.gperf" + {"NotTildeTilde;", "≉"}, + {""}, #line 853 "HTMLCharacterReference.gperf" - {"ccedil;", "ç"}, -#line 1985 "HTMLCharacterReference.gperf" - {"subrarr;", "⥹"}, - {""}, - {""}, -#line 1879 "HTMLCharacterReference.gperf" - {"rx;", "℞"}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 172 "HTMLCharacterReference.gperf" - {"Equilibrium;", "⇌"}, - {""}, - {""}, - {""}, -#line 2151 "HTMLCharacterReference.gperf" - {"vDash;", "⊨"}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 2023 "HTMLCharacterReference.gperf" - {"supset;", "⊃"}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 2209 "HTMLCharacterReference.gperf" - {"xlArr;", "⟸"}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 1689 "HTMLCharacterReference.gperf" - {"otilde", "õ"}, + {"ccedil", "ç"}, +#line 854 "HTMLCharacterReference.gperf" + {"ccedil;", "ç"}, +#line 1986 "HTMLCharacterReference.gperf" + {"subrarr;", "⥹"}, + {""}, + {""}, +#line 1880 "HTMLCharacterReference.gperf" + {"rx;", "℞"}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 173 "HTMLCharacterReference.gperf" + {"Equilibrium;", "⇌"}, + {""}, + {""}, + {""}, +#line 2152 "HTMLCharacterReference.gperf" + {"vDash;", "⊨"}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 2024 "HTMLCharacterReference.gperf" + {"supset;", "⊃"}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 2210 "HTMLCharacterReference.gperf" + {"xlArr;", "⟸"}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, #line 1690 "HTMLCharacterReference.gperf" - {"otilde;", "õ"}, - {""}, -#line 1792 "HTMLCharacterReference.gperf" - {"rarrap;", "⥵"}, -#line 344 "HTMLCharacterReference.gperf" - {"NestedGreaterGreater;", "≫"}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 1639 "HTMLCharacterReference.gperf" - {"ocir;", "⊚"}, - {""}, - {""}, -#line 399 "HTMLCharacterReference.gperf" - {"NotTildeEqual;", "≄"}, - {""}, - {""}, - {""}, -#line 1589 "HTMLCharacterReference.gperf" - {"nsqsupe;", "⋣"}, - {""}, -#line 987 "HTMLCharacterReference.gperf" - {"downdownarrows;", "⇊"}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 321 "HTMLCharacterReference.gperf" - {"LowerRightArrow;", "↘"}, - {""}, -#line 1818 "HTMLCharacterReference.gperf" - {"rdca;", "⤷"}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 2084 "HTMLCharacterReference.gperf" - {"triminus;", "⨺"}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 1004 "HTMLCharacterReference.gperf" - {"dzigrarr;", "⟿"}, -#line 1924 "HTMLCharacterReference.gperf" - {"sime;", "≃"}, + {"otilde", "õ"}, +#line 1691 "HTMLCharacterReference.gperf" + {"otilde;", "õ"}, + {""}, +#line 1793 "HTMLCharacterReference.gperf" + {"rarrap;", "⥵"}, +#line 345 "HTMLCharacterReference.gperf" + {"NestedGreaterGreater;", "≫"}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 1640 "HTMLCharacterReference.gperf" + {"ocir;", "⊚"}, + {""}, + {""}, +#line 400 "HTMLCharacterReference.gperf" + {"NotTildeEqual;", "≄"}, + {""}, + {""}, + {""}, +#line 1590 "HTMLCharacterReference.gperf" + {"nsqsupe;", "⋣"}, + {""}, +#line 988 "HTMLCharacterReference.gperf" + {"downdownarrows;", "⇊"}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 322 "HTMLCharacterReference.gperf" + {"LowerRightArrow;", "↘"}, + {""}, +#line 1819 "HTMLCharacterReference.gperf" + {"rdca;", "⤷"}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 2085 "HTMLCharacterReference.gperf" + {"triminus;", "⨺"}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 1005 "HTMLCharacterReference.gperf" + {"dzigrarr;", "⟿"}, #line 1925 "HTMLCharacterReference.gperf" - {"simeq;", "≃"}, - {""}, - {""}, - {""}, -#line 307 "HTMLCharacterReference.gperf" - {"LessSlantEqual;", "⩽"}, -#line 2155 "HTMLCharacterReference.gperf" - {"varnothing;", "∅"}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 2138 "HTMLCharacterReference.gperf" - {"urtri;", "◹"}, -#line 1471 "HTMLCharacterReference.gperf" - {"nLeftrightarrow;", "⇎"}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 2143 "HTMLCharacterReference.gperf" - {"utrif;", "▴"}, - {""}, - {""}, - {""}, - {""}, -#line 2070 "HTMLCharacterReference.gperf" - {"topf;", "𝕥"}, - {""}, - {""}, - {""}, -#line 1325 "HTMLCharacterReference.gperf" - {"leftharpoonup;", "↼"}, - {""}, -#line 1219 "HTMLCharacterReference.gperf" - {"image;", "ℑ"}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 1697 "HTMLCharacterReference.gperf" - {"para", "¶"}, + {"sime;", "≃"}, +#line 1926 "HTMLCharacterReference.gperf" + {"simeq;", "≃"}, + {""}, + {""}, + {""}, +#line 308 "HTMLCharacterReference.gperf" + {"LessSlantEqual;", "⩽"}, +#line 2156 "HTMLCharacterReference.gperf" + {"varnothing;", "∅"}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 2139 "HTMLCharacterReference.gperf" + {"urtri;", "◹"}, +#line 1472 "HTMLCharacterReference.gperf" + {"nLeftrightarrow;", "⇎"}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 2144 "HTMLCharacterReference.gperf" + {"utrif;", "▴"}, + {""}, + {""}, + {""}, + {""}, +#line 2071 "HTMLCharacterReference.gperf" + {"topf;", "𝕥"}, + {""}, + {""}, + {""}, +#line 1326 "HTMLCharacterReference.gperf" + {"leftharpoonup;", "↼"}, + {""}, +#line 1220 "HTMLCharacterReference.gperf" + {"image;", "ℑ"}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, #line 1698 "HTMLCharacterReference.gperf" - {"para;", "¶"}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 85 "HTMLCharacterReference.gperf" - {"Colone;", "⩴"}, - {""}, - {""}, -#line 1662 "HTMLCharacterReference.gperf" - {"olt;", "⧀"}, - {""}, - {""}, -#line 1712 "HTMLCharacterReference.gperf" - {"phmmat;", "ℳ"}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 1998 "HTMLCharacterReference.gperf" - {"succnapprox;", "⪺"}, - {""}, - {""}, -#line 1331 "HTMLCharacterReference.gperf" - {"leftthreetimes;", "⋋"}, - {""}, - {""}, - {""}, -#line 756 "HTMLCharacterReference.gperf" - {"bigtriangleup;", "△"}, -#line 140 "HTMLCharacterReference.gperf" - {"DownLeftVector;", "↽"}, - {""}, - {""}, + {"para", "¶"}, +#line 1699 "HTMLCharacterReference.gperf" + {"para;", "¶"}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 86 "HTMLCharacterReference.gperf" + {"Colone;", "⩴"}, + {""}, + {""}, +#line 1663 "HTMLCharacterReference.gperf" + {"olt;", "⧀"}, + {""}, + {""}, +#line 1713 "HTMLCharacterReference.gperf" + {"phmmat;", "ℳ"}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 1999 "HTMLCharacterReference.gperf" + {"succnapprox;", "⪺"}, + {""}, + {""}, +#line 1332 "HTMLCharacterReference.gperf" + {"leftthreetimes;", "⋋"}, + {""}, + {""}, + {""}, +#line 757 "HTMLCharacterReference.gperf" + {"bigtriangleup;", "△"}, #line 141 "HTMLCharacterReference.gperf" - {"DownLeftVectorBar;", "⥖"}, -#line 1907 "HTMLCharacterReference.gperf" - {"setminus;", "∖"}, -#line 1380 "HTMLCharacterReference.gperf" - {"longrightarrow;", "⟶"}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 2031 "HTMLCharacterReference.gperf" - {"swArr;", "⇙"}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 2064 "HTMLCharacterReference.gperf" - {"timesd;", "⨰"}, - {""}, - {""}, - {""}, -#line 808 "HTMLCharacterReference.gperf" - {"boxhu;", "┴"}, - {""}, - {""}, -#line 412 "HTMLCharacterReference.gperf" - {"Ocy;", "О"}, - {""}, -#line 1711 "HTMLCharacterReference.gperf" - {"phiv;", "ϕ"}, - {""}, -#line 728 "HTMLCharacterReference.gperf" - {"backsim;", "∽"}, - {""}, - {""}, - {""}, -#line 1658 "HTMLCharacterReference.gperf" - {"olarr;", "↺"}, -#line 220 "HTMLCharacterReference.gperf" - {"HumpDownHump;", "≎"}, -#line 537 "HTMLCharacterReference.gperf" - {"SucceedsSlantEqual;", "≽"}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 2126 "HTMLCharacterReference.gperf" - {"updownarrow;", "↕"}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 2103 "HTMLCharacterReference.gperf" - {"ucirc", "û"}, + {"DownLeftVector;", "↽"}, + {""}, + {""}, +#line 142 "HTMLCharacterReference.gperf" + {"DownLeftVectorBar;", "⥖"}, +#line 1908 "HTMLCharacterReference.gperf" + {"setminus;", "∖"}, +#line 1381 "HTMLCharacterReference.gperf" + {"longrightarrow;", "⟶"}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 2032 "HTMLCharacterReference.gperf" + {"swArr;", "⇙"}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 2065 "HTMLCharacterReference.gperf" + {"timesd;", "⨰"}, + {""}, + {""}, + {""}, +#line 809 "HTMLCharacterReference.gperf" + {"boxhu;", "┴"}, + {""}, + {""}, +#line 413 "HTMLCharacterReference.gperf" + {"Ocy;", "О"}, + {""}, +#line 1712 "HTMLCharacterReference.gperf" + {"phiv;", "ϕ"}, + {""}, +#line 729 "HTMLCharacterReference.gperf" + {"backsim;", "∽"}, + {""}, + {""}, + {""}, +#line 1659 "HTMLCharacterReference.gperf" + {"olarr;", "↺"}, +#line 221 "HTMLCharacterReference.gperf" + {"HumpDownHump;", "≎"}, +#line 538 "HTMLCharacterReference.gperf" + {"SucceedsSlantEqual;", "≽"}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 2127 "HTMLCharacterReference.gperf" + {"updownarrow;", "↕"}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, #line 2104 "HTMLCharacterReference.gperf" - {"ucirc;", "û"}, -#line 1657 "HTMLCharacterReference.gperf" - {"oint;", "∮"}, - {""}, - {""}, -#line 1676 "HTMLCharacterReference.gperf" - {"orderof;", "ℴ"}, - {""}, - {""}, - {""}, - {""}, -#line 1834 "HTMLCharacterReference.gperf" - {"rharu;", "⇀"}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 1742 "HTMLCharacterReference.gperf" - {"precapprox;", "⪷"}, - {""}, - {""}, -#line 1431 "HTMLCharacterReference.gperf" - {"malt;", "✠"}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, + {"ucirc", "û"}, +#line 2105 "HTMLCharacterReference.gperf" + {"ucirc;", "û"}, +#line 1658 "HTMLCharacterReference.gperf" + {"oint;", "∮"}, + {""}, + {""}, +#line 1677 "HTMLCharacterReference.gperf" + {"orderof;", "ℴ"}, + {""}, + {""}, + {""}, + {""}, #line 1835 "HTMLCharacterReference.gperf" - {"rharul;", "⥬"}, - {""}, -#line 275 "HTMLCharacterReference.gperf" - {"Lcedil;", "Ļ"}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 752 "HTMLCharacterReference.gperf" - {"bigotimes;", "⨂"}, - {""}, - {""}, - {""}, -#line 1820 "HTMLCharacterReference.gperf" - {"rdquo;", "”"}, + {"rharu;", "⇀"}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 1743 "HTMLCharacterReference.gperf" + {"precapprox;", "⪷"}, + {""}, + {""}, +#line 1432 "HTMLCharacterReference.gperf" + {"malt;", "✠"}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 1836 "HTMLCharacterReference.gperf" + {"rharul;", "⥬"}, + {""}, +#line 276 "HTMLCharacterReference.gperf" + {"Lcedil;", "Ļ"}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 753 "HTMLCharacterReference.gperf" + {"bigotimes;", "⨂"}, + {""}, + {""}, + {""}, #line 1821 "HTMLCharacterReference.gperf" - {"rdquor;", "”"}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 313 "HTMLCharacterReference.gperf" - {"LongLeftArrow;", "⟵"}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 1860 "HTMLCharacterReference.gperf" - {"roplus;", "⨮"}, - {""}, - {""}, -#line 1980 "HTMLCharacterReference.gperf" - {"subedot;", "⫃"}, - {""}, - {""}, - {""}, - {""}, -#line 748 "HTMLCharacterReference.gperf" - {"bigcirc;", "◯"}, - {""}, -#line 1607 "HTMLCharacterReference.gperf" - {"ntlg;", "≸"}, -#line 1640 "HTMLCharacterReference.gperf" - {"ocirc", "ô"}, + {"rdquo;", "”"}, +#line 1822 "HTMLCharacterReference.gperf" + {"rdquor;", "”"}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 314 "HTMLCharacterReference.gperf" + {"LongLeftArrow;", "⟵"}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 1861 "HTMLCharacterReference.gperf" + {"roplus;", "⨮"}, + {""}, + {""}, +#line 1981 "HTMLCharacterReference.gperf" + {"subedot;", "⫃"}, + {""}, + {""}, + {""}, + {""}, +#line 749 "HTMLCharacterReference.gperf" + {"bigcirc;", "◯"}, + {""}, +#line 1608 "HTMLCharacterReference.gperf" + {"ntlg;", "≸"}, #line 1641 "HTMLCharacterReference.gperf" - {"ocirc;", "ô"}, - {""}, -#line 2024 "HTMLCharacterReference.gperf" - {"supseteq;", "⊇"}, + {"ocirc", "ô"}, +#line 1642 "HTMLCharacterReference.gperf" + {"ocirc;", "ô"}, + {""}, #line 2025 "HTMLCharacterReference.gperf" - {"supseteqq;", "⫆"}, - {""}, -#line 1183 "HTMLCharacterReference.gperf" - {"hellip;", "…"}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 1623 "HTMLCharacterReference.gperf" - {"nvlArr;", "⤂"}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 1877 "HTMLCharacterReference.gperf" - {"rtriltri;", "⧎"}, -#line 1789 "HTMLCharacterReference.gperf" - {"raquo", "»"}, -#line 1790 "HTMLCharacterReference.gperf" - {"raquo;", "»"}, - {""}, - {""}, -#line 63 "HTMLCharacterReference.gperf" - {"Cacute;", "Ć"}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 1112 "HTMLCharacterReference.gperf" - {"gEl;", "⪌"}, - {""}, - {""}, -#line 1262 "HTMLCharacterReference.gperf" - {"jukcy;", "є"}, - {""}, - {""}, - {""}, - {""}, -#line 2174 "HTMLCharacterReference.gperf" - {"vellip;", "⋮"}, - {""}, -#line 1883 "HTMLCharacterReference.gperf" - {"scE;", "⪴"}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 1455 "HTMLCharacterReference.gperf" - {"minusdu;", "⨪"}, - {""}, - {""}, - {""}, -#line 942 "HTMLCharacterReference.gperf" - {"daleth;", "ℸ"}, - {""}, -#line 119 "HTMLCharacterReference.gperf" - {"DotEqual;", "≐"}, -#line 1866 "HTMLCharacterReference.gperf" - {"rsaquo;", "›"}, -#line 128 "HTMLCharacterReference.gperf" - {"DoubleLongRightArrow;", "⟹"}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 137 "HTMLCharacterReference.gperf" - {"DownBreve;", "̑"}, - {""}, -#line 2092 "HTMLCharacterReference.gperf" - {"tstrok;", "ŧ"}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 2087 "HTMLCharacterReference.gperf" - {"tritime;", "⨻"}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 729 "HTMLCharacterReference.gperf" - {"backsimeq;", "⋍"}, - {""}, - {""}, -#line 1902 "HTMLCharacterReference.gperf" - {"searrow;", "↘"}, - {""}, - {""}, -#line 1645 "HTMLCharacterReference.gperf" - {"odiv;", "⨸"}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 2086 "HTMLCharacterReference.gperf" - {"trisb;", "⧍"}, -#line 178 "HTMLCharacterReference.gperf" - {"Exists;", "∃"}, -#line 282 "HTMLCharacterReference.gperf" - {"LeftDoubleBracket;", "⟦"}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 1802 "HTMLCharacterReference.gperf" - {"rarrw;", "↝"}, -#line 732 "HTMLCharacterReference.gperf" - {"barwedge;", "⌅"}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 1817 "HTMLCharacterReference.gperf" - {"rcy;", "р"}, - {""}, - {""}, -#line 318 "HTMLCharacterReference.gperf" - {"Longrightarrow;", "⟹"}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 1946 "HTMLCharacterReference.gperf" - {"sopf;", "𝕤"}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 511 "HTMLCharacterReference.gperf" - {"Scedil;", "Ş"}, - {""}, - {""}, - {""}, - {""}, -#line 295 "HTMLCharacterReference.gperf" - {"LeftUpDownVector;", "⥑"}, - {""}, - {""}, -#line 259 "HTMLCharacterReference.gperf" - {"KJcy;", "Ќ"}, - {""}, -#line 380 "HTMLCharacterReference.gperf" - {"NotPrecedesEqual;", "⪯̸"}, - {""}, -#line 2166 "HTMLCharacterReference.gperf" - {"vartheta;", "ϑ"}, - {""}, - {""}, - {""}, - {""}, -#line 539 "HTMLCharacterReference.gperf" - {"SuchThat;", "∋"}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 773 "HTMLCharacterReference.gperf" - {"bnequiv;", "≡⃥"}, - {""}, - {""}, - {""}, -#line 1252 "HTMLCharacterReference.gperf" - {"iukcy;", "і"}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 1965 "HTMLCharacterReference.gperf" - {"squf;", "▪"}, - {""}, -#line 1495 "HTMLCharacterReference.gperf" - {"ncedil;", "ņ"}, - {""}, - {""}, -#line 1737 "HTMLCharacterReference.gperf" - {"prE;", "⪳"}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 136 "HTMLCharacterReference.gperf" - {"DownArrowUpArrow;", "⇵"}, - {""}, - {""}, - {""}, -#line 1919 "HTMLCharacterReference.gperf" - {"sigma;", "σ"}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 1079 "HTMLCharacterReference.gperf" - {"filig;", "fi"}, - {""}, - {""}, - {""}, -#line 315 "HTMLCharacterReference.gperf" - {"LongRightArrow;", "⟶"}, - {""}, -#line 1449 "HTMLCharacterReference.gperf" - {"midcir;", "⫰"}, - {""}, - {""}, -#line 1872 "HTMLCharacterReference.gperf" - {"rthree;", "⋌"}, - {""}, -#line 2038 "HTMLCharacterReference.gperf" - {"target;", "⌖"}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 1424 "HTMLCharacterReference.gperf" - {"luruhar;", "⥦"}, - {""}, - {""}, -#line 1082 "HTMLCharacterReference.gperf" - {"fllig;", "fl"}, - {""}, - {""}, - {""}, - {""}, -#line 1450 "HTMLCharacterReference.gperf" - {"middot", "·"}, -#line 1451 "HTMLCharacterReference.gperf" - {"middot;", "·"}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 1348 "HTMLCharacterReference.gperf" - {"lesssim;", "≲"}, - {""}, - {""}, - {""}, - {""}, -#line 754 "HTMLCharacterReference.gperf" - {"bigstar;", "★"}, -#line 1939 "HTMLCharacterReference.gperf" - {"smt;", "⪪"}, + {"supseteq;", "⊇"}, #line 2026 "HTMLCharacterReference.gperf" - {"supsetneq;", "⊋"}, -#line 2027 "HTMLCharacterReference.gperf" - {"supsetneqq;", "⫌"}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 1899 "HTMLCharacterReference.gperf" - {"seArr;", "⇘"}, - {""}, -#line 52 "HTMLCharacterReference.gperf" - {"Because;", "∵"}, - {""}, - {""}, - {""}, - {""}, -#line 450 "HTMLCharacterReference.gperf" - {"Product;", "∏"}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 329 "HTMLCharacterReference.gperf" - {"Mellintrf;", "ℳ"}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 2125 "HTMLCharacterReference.gperf" - {"uparrow;", "↑"}, - {""}, - {""}, -#line 390 "HTMLCharacterReference.gperf" - {"NotSubset;", "⊂⃒"}, - {""}, -#line 2051 "HTMLCharacterReference.gperf" - {"thetav;", "ϑ"}, - {""}, -#line 1779 "HTMLCharacterReference.gperf" - {"rBarr;", "⤏"}, - {""}, -#line 288 "HTMLCharacterReference.gperf" - {"LeftRightVector;", "⥎"}, -#line 468 "HTMLCharacterReference.gperf" - {"Rcedil;", "Ŗ"}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 2105 "HTMLCharacterReference.gperf" - {"ucy;", "у"}, - {""}, -#line 2011 "HTMLCharacterReference.gperf" - {"supE;", "⫆"}, - {""}, - {""}, -#line 133 "HTMLCharacterReference.gperf" - {"DoubleVerticalBar;", "∥"}, -#line 524 "HTMLCharacterReference.gperf" - {"SquareIntersection;", "⊓"}, - {""}, - {""}, - {""}, -#line 1781 "HTMLCharacterReference.gperf" - {"race;", "∽̱"}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 1776 "HTMLCharacterReference.gperf" - {"rAarr;", "⇛"}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 1364 "HTMLCharacterReference.gperf" - {"lmidot;", "ŀ"}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 1777 "HTMLCharacterReference.gperf" - {"rArr;", "⇒"}, - {""}, - {""}, - {""}, -#line 194 "HTMLCharacterReference.gperf" - {"Gcedil;", "Ģ"}, - {""}, -#line 1943 "HTMLCharacterReference.gperf" - {"sol;", "/"}, -#line 420 "HTMLCharacterReference.gperf" - {"Oopf;", "𝕆"}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 124 "HTMLCharacterReference.gperf" - {"DoubleLeftRightArrow;", "⇔"}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 477 "HTMLCharacterReference.gperf" - {"RightArrow;", "→"}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 1453 "HTMLCharacterReference.gperf" - {"minusb;", "⊟"}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 1745 "HTMLCharacterReference.gperf" - {"precnapprox;", "⪹"}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 1642 "HTMLCharacterReference.gperf" - {"ocy;", "о"}, -#line 1258 "HTMLCharacterReference.gperf" - {"jmath;", "ȷ"}, -#line 1920 "HTMLCharacterReference.gperf" - {"sigmaf;", "ς"}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 1714 "HTMLCharacterReference.gperf" - {"pi;", "π"}, - {""}, - {""}, - {""}, -#line 2164 "HTMLCharacterReference.gperf" - {"varsupsetneq;", "⊋︀"}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 672 "HTMLCharacterReference.gperf" - {"alefsym;", "ℵ"}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 1754 "HTMLCharacterReference.gperf" - {"prod;", "∏"}, - {""}, -#line 1932 "HTMLCharacterReference.gperf" - {"simrarr;", "⥲"}, - {""}, -#line 1716 "HTMLCharacterReference.gperf" - {"piv;", "ϖ"}, - {""}, -#line 257 "HTMLCharacterReference.gperf" - {"Jukcy;", "Є"}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 372 "HTMLCharacterReference.gperf" - {"NotLessEqual;", "≰"}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 400 "HTMLCharacterReference.gperf" - {"NotTildeFullEqual;", "≇"}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 356 "HTMLCharacterReference.gperf" - {"NotEqual;", "≠"}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 1366 "HTMLCharacterReference.gperf" - {"lmoustache;", "⎰"}, -#line 338 "HTMLCharacterReference.gperf" - {"Ncedil;", "Ņ"}, - {""}, -#line 1234 "HTMLCharacterReference.gperf" - {"intlarhk;", "⨗"}, - {""}, - {""}, -#line 507 "HTMLCharacterReference.gperf" - {"SOFTcy;", "Ь"}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 1978 "HTMLCharacterReference.gperf" - {"subdot;", "⪽"}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 727 "HTMLCharacterReference.gperf" - {"backprime;", "‵"}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 674 "HTMLCharacterReference.gperf" - {"alpha;", "α"}, - {""}, + {"supseteqq;", "⫆"}, + {""}, +#line 1184 "HTMLCharacterReference.gperf" + {"hellip;", "…"}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 1624 "HTMLCharacterReference.gperf" + {"nvlArr;", "⤂"}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 1878 "HTMLCharacterReference.gperf" + {"rtriltri;", "⧎"}, +#line 1790 "HTMLCharacterReference.gperf" + {"raquo", "»"}, +#line 1791 "HTMLCharacterReference.gperf" + {"raquo;", "»"}, + {""}, + {""}, +#line 64 "HTMLCharacterReference.gperf" + {"Cacute;", "Ć"}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 1113 "HTMLCharacterReference.gperf" + {"gEl;", "⪌"}, + {""}, + {""}, +#line 1263 "HTMLCharacterReference.gperf" + {"jukcy;", "є"}, + {""}, + {""}, + {""}, + {""}, +#line 2175 "HTMLCharacterReference.gperf" + {"vellip;", "⋮"}, + {""}, +#line 1884 "HTMLCharacterReference.gperf" + {"scE;", "⪴"}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 1456 "HTMLCharacterReference.gperf" + {"minusdu;", "⨪"}, + {""}, + {""}, + {""}, +#line 943 "HTMLCharacterReference.gperf" + {"daleth;", "ℸ"}, + {""}, +#line 120 "HTMLCharacterReference.gperf" + {"DotEqual;", "≐"}, +#line 1867 "HTMLCharacterReference.gperf" + {"rsaquo;", "›"}, +#line 129 "HTMLCharacterReference.gperf" + {"DoubleLongRightArrow;", "⟹"}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 138 "HTMLCharacterReference.gperf" + {"DownBreve;", "̑"}, + {""}, +#line 2093 "HTMLCharacterReference.gperf" + {"tstrok;", "ŧ"}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 2088 "HTMLCharacterReference.gperf" + {"tritime;", "⨻"}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 730 "HTMLCharacterReference.gperf" + {"backsimeq;", "⋍"}, + {""}, + {""}, +#line 1903 "HTMLCharacterReference.gperf" + {"searrow;", "↘"}, + {""}, + {""}, +#line 1646 "HTMLCharacterReference.gperf" + {"odiv;", "⨸"}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 2087 "HTMLCharacterReference.gperf" + {"trisb;", "⧍"}, #line 179 "HTMLCharacterReference.gperf" - {"ExponentialE;", "ⅇ"}, - {""}, - {""}, -#line 2170 "HTMLCharacterReference.gperf" - {"vdash;", "⊢"}, - {""}, - {""}, - {""}, - {""}, -#line 1824 "HTMLCharacterReference.gperf" - {"realine;", "ℛ"}, - {""}, - {""}, - {""}, - {""}, - {""}, + {"Exists;", "∃"}, +#line 283 "HTMLCharacterReference.gperf" + {"LeftDoubleBracket;", "⟦"}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 1803 "HTMLCharacterReference.gperf" + {"rarrw;", "↝"}, +#line 733 "HTMLCharacterReference.gperf" + {"barwedge;", "⌅"}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 1818 "HTMLCharacterReference.gperf" + {"rcy;", "р"}, + {""}, + {""}, +#line 319 "HTMLCharacterReference.gperf" + {"Longrightarrow;", "⟹"}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 1947 "HTMLCharacterReference.gperf" + {"sopf;", "𝕤"}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 512 "HTMLCharacterReference.gperf" + {"Scedil;", "Ş"}, + {""}, + {""}, + {""}, + {""}, +#line 296 "HTMLCharacterReference.gperf" + {"LeftUpDownVector;", "⥑"}, + {""}, + {""}, +#line 260 "HTMLCharacterReference.gperf" + {"KJcy;", "Ќ"}, + {""}, +#line 381 "HTMLCharacterReference.gperf" + {"NotPrecedesEqual;", "⪯̸"}, + {""}, +#line 2167 "HTMLCharacterReference.gperf" + {"vartheta;", "ϑ"}, + {""}, + {""}, + {""}, + {""}, +#line 540 "HTMLCharacterReference.gperf" + {"SuchThat;", "∋"}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 774 "HTMLCharacterReference.gperf" + {"bnequiv;", "≡⃥"}, + {""}, + {""}, + {""}, +#line 1253 "HTMLCharacterReference.gperf" + {"iukcy;", "і"}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 1966 "HTMLCharacterReference.gperf" + {"squf;", "▪"}, + {""}, +#line 1496 "HTMLCharacterReference.gperf" + {"ncedil;", "ņ"}, + {""}, + {""}, +#line 1738 "HTMLCharacterReference.gperf" + {"prE;", "⪳"}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 137 "HTMLCharacterReference.gperf" + {"DownArrowUpArrow;", "⇵"}, + {""}, + {""}, + {""}, +#line 1920 "HTMLCharacterReference.gperf" + {"sigma;", "σ"}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 1080 "HTMLCharacterReference.gperf" + {"filig;", "fi"}, + {""}, + {""}, + {""}, +#line 316 "HTMLCharacterReference.gperf" + {"LongRightArrow;", "⟶"}, + {""}, +#line 1450 "HTMLCharacterReference.gperf" + {"midcir;", "⫰"}, + {""}, + {""}, +#line 1873 "HTMLCharacterReference.gperf" + {"rthree;", "⋌"}, + {""}, +#line 2039 "HTMLCharacterReference.gperf" + {"target;", "⌖"}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 1425 "HTMLCharacterReference.gperf" + {"luruhar;", "⥦"}, + {""}, + {""}, +#line 1083 "HTMLCharacterReference.gperf" + {"fllig;", "fl"}, + {""}, + {""}, + {""}, + {""}, +#line 1451 "HTMLCharacterReference.gperf" + {"middot", "·"}, +#line 1452 "HTMLCharacterReference.gperf" + {"middot;", "·"}, + {""}, + {""}, + {""}, + {""}, + {""}, #line 1349 "HTMLCharacterReference.gperf" - {"lfisht;", "⥼"}, -#line 1559 "HTMLCharacterReference.gperf" - {"notnivc;", "⋽"}, - {""}, - {""}, - {""}, -#line 203 "HTMLCharacterReference.gperf" - {"GreaterFullEqual;", "≧"}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 248 "HTMLCharacterReference.gperf" - {"Iukcy;", "І"}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 138 "HTMLCharacterReference.gperf" - {"DownLeftRightVector;", "⥐"}, - {""}, - {""}, - {""}, - {""}, -#line 1223 "HTMLCharacterReference.gperf" - {"imof;", "⊷"}, -#line 1324 "HTMLCharacterReference.gperf" - {"leftharpoondown;", "↽"}, -#line 958 "HTMLCharacterReference.gperf" - {"dfisht;", "⥿"}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 346 "HTMLCharacterReference.gperf" - {"NewLine;", "\n"}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 2096 "HTMLCharacterReference.gperf" - {"uArr;", "⇑"}, - {""}, -#line 1222 "HTMLCharacterReference.gperf" - {"imath;", "ı"}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 650 "HTMLCharacterReference.gperf" - {"ZeroWidthSpace;", "​"}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, + {"lesssim;", "≲"}, + {""}, + {""}, + {""}, + {""}, #line 755 "HTMLCharacterReference.gperf" - {"bigtriangledown;", "▽"}, - {""}, -#line 1859 "HTMLCharacterReference.gperf" - {"ropf;", "𝕣"}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 312 "HTMLCharacterReference.gperf" - {"Lmidot;", "Ŀ"}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 1432 "HTMLCharacterReference.gperf" - {"maltese;", "✠"}, - {""}, - {""}, - {""}, -#line 534 "HTMLCharacterReference.gperf" - {"SubsetEqual;", "⊆"}, -#line 2127 "HTMLCharacterReference.gperf" - {"upharpoonleft;", "↿"}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 354 "HTMLCharacterReference.gperf" - {"NotDoubleVerticalBar;", "∦"}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 1430 "HTMLCharacterReference.gperf" - {"male;", "♂"}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, + {"bigstar;", "★"}, +#line 1940 "HTMLCharacterReference.gperf" + {"smt;", "⪪"}, +#line 2027 "HTMLCharacterReference.gperf" + {"supsetneq;", "⊋"}, +#line 2028 "HTMLCharacterReference.gperf" + {"supsetneqq;", "⫌"}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 1900 "HTMLCharacterReference.gperf" + {"seArr;", "⇘"}, + {""}, +#line 53 "HTMLCharacterReference.gperf" + {"Because;", "∵"}, + {""}, + {""}, + {""}, + {""}, +#line 451 "HTMLCharacterReference.gperf" + {"Product;", "∏"}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 330 "HTMLCharacterReference.gperf" + {"Mellintrf;", "ℳ"}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 2126 "HTMLCharacterReference.gperf" + {"uparrow;", "↑"}, + {""}, + {""}, +#line 391 "HTMLCharacterReference.gperf" + {"NotSubset;", "⊂⃒"}, + {""}, +#line 2052 "HTMLCharacterReference.gperf" + {"thetav;", "ϑ"}, + {""}, +#line 1780 "HTMLCharacterReference.gperf" + {"rBarr;", "⤏"}, + {""}, +#line 289 "HTMLCharacterReference.gperf" + {"LeftRightVector;", "⥎"}, +#line 469 "HTMLCharacterReference.gperf" + {"Rcedil;", "Ŗ"}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 2106 "HTMLCharacterReference.gperf" + {"ucy;", "у"}, + {""}, +#line 2012 "HTMLCharacterReference.gperf" + {"supE;", "⫆"}, + {""}, + {""}, +#line 134 "HTMLCharacterReference.gperf" + {"DoubleVerticalBar;", "∥"}, +#line 525 "HTMLCharacterReference.gperf" + {"SquareIntersection;", "⊓"}, + {""}, + {""}, + {""}, +#line 1782 "HTMLCharacterReference.gperf" + {"race;", "∽̱"}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 1777 "HTMLCharacterReference.gperf" + {"rAarr;", "⇛"}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 1365 "HTMLCharacterReference.gperf" + {"lmidot;", "ŀ"}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 1778 "HTMLCharacterReference.gperf" + {"rArr;", "⇒"}, + {""}, + {""}, + {""}, +#line 195 "HTMLCharacterReference.gperf" + {"Gcedil;", "Ģ"}, + {""}, +#line 1944 "HTMLCharacterReference.gperf" + {"sol;", "/"}, +#line 421 "HTMLCharacterReference.gperf" + {"Oopf;", "𝕆"}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 125 "HTMLCharacterReference.gperf" + {"DoubleLeftRightArrow;", "⇔"}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 478 "HTMLCharacterReference.gperf" + {"RightArrow;", "→"}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 1454 "HTMLCharacterReference.gperf" + {"minusb;", "⊟"}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 1746 "HTMLCharacterReference.gperf" + {"precnapprox;", "⪹"}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 1643 "HTMLCharacterReference.gperf" + {"ocy;", "о"}, +#line 1259 "HTMLCharacterReference.gperf" + {"jmath;", "ȷ"}, +#line 1921 "HTMLCharacterReference.gperf" + {"sigmaf;", "ς"}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 1715 "HTMLCharacterReference.gperf" + {"pi;", "π"}, + {""}, + {""}, + {""}, #line 2165 "HTMLCharacterReference.gperf" - {"varsupsetneqq;", "⫌︀"}, - {""}, - {""}, -#line 2062 "HTMLCharacterReference.gperf" - {"timesb;", "⊠"}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 2131 "HTMLCharacterReference.gperf" - {"upsih;", "ϒ"}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 1546 "HTMLCharacterReference.gperf" - {"nmid;", "∤"}, - {""}, - {""}, -#line 201 "HTMLCharacterReference.gperf" - {"GreaterEqual;", "≥"}, - {""}, - {""}, -#line 417 "HTMLCharacterReference.gperf" - {"Omacr;", "Ō"}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 1558 "HTMLCharacterReference.gperf" - {"notnivb;", "⋾"}, + {"varsupsetneq;", "⊋︀"}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 673 "HTMLCharacterReference.gperf" + {"alefsym;", "ℵ"}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 1755 "HTMLCharacterReference.gperf" + {"prod;", "∏"}, + {""}, +#line 1933 "HTMLCharacterReference.gperf" + {"simrarr;", "⥲"}, + {""}, +#line 1717 "HTMLCharacterReference.gperf" + {"piv;", "ϖ"}, + {""}, +#line 258 "HTMLCharacterReference.gperf" + {"Jukcy;", "Є"}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 373 "HTMLCharacterReference.gperf" + {"NotLessEqual;", "≰"}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 401 "HTMLCharacterReference.gperf" + {"NotTildeFullEqual;", "≇"}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 357 "HTMLCharacterReference.gperf" + {"NotEqual;", "≠"}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 1367 "HTMLCharacterReference.gperf" + {"lmoustache;", "⎰"}, +#line 339 "HTMLCharacterReference.gperf" + {"Ncedil;", "Ņ"}, + {""}, +#line 1235 "HTMLCharacterReference.gperf" + {"intlarhk;", "⨗"}, + {""}, + {""}, +#line 508 "HTMLCharacterReference.gperf" + {"SOFTcy;", "Ь"}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 1979 "HTMLCharacterReference.gperf" + {"subdot;", "⪽"}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 728 "HTMLCharacterReference.gperf" + {"backprime;", "‵"}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 675 "HTMLCharacterReference.gperf" + {"alpha;", "α"}, + {""}, +#line 180 "HTMLCharacterReference.gperf" + {"ExponentialE;", "ⅇ"}, + {""}, + {""}, +#line 2171 "HTMLCharacterReference.gperf" + {"vdash;", "⊢"}, + {""}, + {""}, + {""}, + {""}, +#line 1825 "HTMLCharacterReference.gperf" + {"realine;", "ℛ"}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 1350 "HTMLCharacterReference.gperf" + {"lfisht;", "⥼"}, +#line 1560 "HTMLCharacterReference.gperf" + {"notnivc;", "⋽"}, + {""}, + {""}, + {""}, +#line 204 "HTMLCharacterReference.gperf" + {"GreaterFullEqual;", "≧"}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 249 "HTMLCharacterReference.gperf" + {"Iukcy;", "І"}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 139 "HTMLCharacterReference.gperf" + {"DownLeftRightVector;", "⥐"}, + {""}, + {""}, + {""}, + {""}, +#line 1224 "HTMLCharacterReference.gperf" + {"imof;", "⊷"}, +#line 1325 "HTMLCharacterReference.gperf" + {"leftharpoondown;", "↽"}, +#line 959 "HTMLCharacterReference.gperf" + {"dfisht;", "⥿"}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 347 "HTMLCharacterReference.gperf" + {"NewLine;", "\n"}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 2097 "HTMLCharacterReference.gperf" + {"uArr;", "⇑"}, + {""}, +#line 1223 "HTMLCharacterReference.gperf" + {"imath;", "ı"}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 651 "HTMLCharacterReference.gperf" + {"ZeroWidthSpace;", "​"}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 756 "HTMLCharacterReference.gperf" + {"bigtriangledown;", "▽"}, + {""}, +#line 1860 "HTMLCharacterReference.gperf" + {"ropf;", "𝕣"}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 313 "HTMLCharacterReference.gperf" + {"Lmidot;", "Ŀ"}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 1433 "HTMLCharacterReference.gperf" + {"maltese;", "✠"}, + {""}, + {""}, + {""}, +#line 535 "HTMLCharacterReference.gperf" + {"SubsetEqual;", "⊆"}, +#line 2128 "HTMLCharacterReference.gperf" + {"upharpoonleft;", "↿"}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 355 "HTMLCharacterReference.gperf" + {"NotDoubleVerticalBar;", "∦"}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 1431 "HTMLCharacterReference.gperf" + {"male;", "♂"}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 2166 "HTMLCharacterReference.gperf" + {"varsupsetneqq;", "⫌︀"}, + {""}, + {""}, #line 2063 "HTMLCharacterReference.gperf" - {"timesbar;", "⨱"}, - {""}, - {""}, - {""}, -#line 1805 "HTMLCharacterReference.gperf" - {"rationals;", "ℚ"}, - {""}, - {""}, - {""}, - {""}, -#line 1681 "HTMLCharacterReference.gperf" - {"origof;", "⊶"}, -#line 341 "HTMLCharacterReference.gperf" - {"NegativeThickSpace;", "​"}, - {""}, - {""}, -#line 1784 "HTMLCharacterReference.gperf" - {"raemptyv;", "⦳"}, + {"timesb;", "⊠"}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 2132 "HTMLCharacterReference.gperf" + {"upsih;", "ϒ"}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 1547 "HTMLCharacterReference.gperf" + {"nmid;", "∤"}, + {""}, + {""}, +#line 202 "HTMLCharacterReference.gperf" + {"GreaterEqual;", "≥"}, + {""}, + {""}, +#line 418 "HTMLCharacterReference.gperf" + {"Omacr;", "Ō"}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 1559 "HTMLCharacterReference.gperf" + {"notnivb;", "⋾"}, +#line 2064 "HTMLCharacterReference.gperf" + {"timesbar;", "⨱"}, + {""}, + {""}, + {""}, +#line 1806 "HTMLCharacterReference.gperf" + {"rationals;", "ℚ"}, + {""}, + {""}, + {""}, + {""}, +#line 1682 "HTMLCharacterReference.gperf" + {"origof;", "⊶"}, +#line 342 "HTMLCharacterReference.gperf" + {"NegativeThickSpace;", "​"}, + {""}, + {""}, #line 1785 "HTMLCharacterReference.gperf" - {"rang;", "⟩"}, - {""}, - {""}, - {""}, -#line 616 "HTMLCharacterReference.gperf" - {"VerticalBar;", "∣"}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 2128 "HTMLCharacterReference.gperf" - {"upharpoonright;", "↾"}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 1796 "HTMLCharacterReference.gperf" - {"rarrfs;", "⤞"}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 1864 "HTMLCharacterReference.gperf" - {"rppolint;", "⨒"}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 553 "HTMLCharacterReference.gperf" - {"Tcedil;", "Ţ"}, - {""}, - {""}, - {""}, -#line 805 "HTMLCharacterReference.gperf" - {"boxhD;", "╥"}, - {""}, -#line 1588 "HTMLCharacterReference.gperf" - {"nsqsube;", "⋢"}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 2118 "HTMLCharacterReference.gperf" - {"ulcrop;", "⌏"}, - {""}, - {""}, - {""}, - {""}, -#line 2167 "HTMLCharacterReference.gperf" - {"vartriangleleft;", "⊲"}, -#line 1661 "HTMLCharacterReference.gperf" - {"oline;", "‾"}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 608 "HTMLCharacterReference.gperf" - {"VDash;", "⊫"}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 2116 "HTMLCharacterReference.gperf" - {"ulcorn;", "⌜"}, - {""}, -#line 2013 "HTMLCharacterReference.gperf" - {"supdsub;", "⫘"}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 2124 "HTMLCharacterReference.gperf" - {"uopf;", "𝕦"}, - {""}, - {""}, -#line 1264 "HTMLCharacterReference.gperf" - {"kappav;", "ϰ"}, -#line 1981 "HTMLCharacterReference.gperf" - {"submult;", "⫁"}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 479 "HTMLCharacterReference.gperf" - {"RightArrowLeftArrow;", "⇄"}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 1507 "HTMLCharacterReference.gperf" - {"nequiv;", "≢"}, - {""}, - {""}, -#line 928 "HTMLCharacterReference.gperf" - {"curlyvee;", "⋎"}, -#line 1886 "HTMLCharacterReference.gperf" - {"sccue;", "≽"}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 1986 "HTMLCharacterReference.gperf" - {"subset;", "⊂"}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 1659 "HTMLCharacterReference.gperf" - {"olcir;", "⦾"}, -#line 237 "HTMLCharacterReference.gperf" - {"Implies;", "⇒"}, - {""}, -#line 284 "HTMLCharacterReference.gperf" - {"LeftDownVector;", "⇃"}, - {""}, - {""}, -#line 285 "HTMLCharacterReference.gperf" - {"LeftDownVectorBar;", "⥙"}, -#line 766 "HTMLCharacterReference.gperf" - {"blacktriangleright;", "▸"}, -#line 1803 "HTMLCharacterReference.gperf" - {"ratail;", "⤚"}, - {""}, -#line 1996 "HTMLCharacterReference.gperf" - {"succcurlyeq;", "≽"}, - {""}, - {""}, - {""}, -#line 1668 "HTMLCharacterReference.gperf" - {"oopf;", "𝕠"}, -#line 281 "HTMLCharacterReference.gperf" - {"LeftCeiling;", "⌈"}, - {""}, -#line 1787 "HTMLCharacterReference.gperf" - {"range;", "⦥"}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 1831 "HTMLCharacterReference.gperf" - {"rfloor;", "⌋"}, - {""}, - {""}, + {"raemptyv;", "⦳"}, +#line 1786 "HTMLCharacterReference.gperf" + {"rang;", "⟩"}, + {""}, + {""}, + {""}, #line 617 "HTMLCharacterReference.gperf" - {"VerticalLine;", "|"}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 1944 "HTMLCharacterReference.gperf" - {"solb;", "⧄"}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 1500 "HTMLCharacterReference.gperf" - {"ndash;", "–"}, -#line 1574 "HTMLCharacterReference.gperf" - {"nrightarrow;", "↛"}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 1631 "HTMLCharacterReference.gperf" - {"nwarhk;", "⤣"}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 402 "HTMLCharacterReference.gperf" - {"NotVerticalBar;", "∤"}, -#line 311 "HTMLCharacterReference.gperf" - {"Lleftarrow;", "⇚"}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 2045 "HTMLCharacterReference.gperf" - {"telrec;", "⌕"}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 2108 "HTMLCharacterReference.gperf" - {"udhar;", "⥮"}, - {""}, - {""}, -#line 1706 "HTMLCharacterReference.gperf" - {"permil;", "‰"}, - {""}, - {""}, - {""}, -#line 413 "HTMLCharacterReference.gperf" - {"Odblac;", "Ő"}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 50 "HTMLCharacterReference.gperf" - {"Barwed;", "⌆"}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 1934 "HTMLCharacterReference.gperf" - {"smallsetminus;", "∖"}, -#line 1940 "HTMLCharacterReference.gperf" - {"smte;", "⪬"}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 392 "HTMLCharacterReference.gperf" - {"NotSucceeds;", "⊁"}, - {""}, - {""}, - {""}, - {""}, -#line 1031 "HTMLCharacterReference.gperf" - {"empty;", "∅"}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 215 "HTMLCharacterReference.gperf" - {"HilbertSpace;", "ℋ"}, -#line 1221 "HTMLCharacterReference.gperf" - {"imagpart;", "ℑ"}, - {""}, - {""}, -#line 1739 "HTMLCharacterReference.gperf" - {"prcue;", "≼"}, + {"VerticalBar;", "∣"}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 2129 "HTMLCharacterReference.gperf" + {"upharpoonright;", "↾"}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 1797 "HTMLCharacterReference.gperf" + {"rarrfs;", "⤞"}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 1865 "HTMLCharacterReference.gperf" + {"rppolint;", "⨒"}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 554 "HTMLCharacterReference.gperf" + {"Tcedil;", "Ţ"}, + {""}, + {""}, + {""}, +#line 806 "HTMLCharacterReference.gperf" + {"boxhD;", "╥"}, + {""}, +#line 1589 "HTMLCharacterReference.gperf" + {"nsqsube;", "⋢"}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 2119 "HTMLCharacterReference.gperf" + {"ulcrop;", "⌏"}, + {""}, + {""}, + {""}, + {""}, +#line 2168 "HTMLCharacterReference.gperf" + {"vartriangleleft;", "⊲"}, +#line 1662 "HTMLCharacterReference.gperf" + {"oline;", "‾"}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 609 "HTMLCharacterReference.gperf" + {"VDash;", "⊫"}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, #line 2117 "HTMLCharacterReference.gperf" - {"ulcorner;", "⌜"}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 1703 "HTMLCharacterReference.gperf" - {"pcy;", "п"}, - {""}, - {""}, -#line 146 "HTMLCharacterReference.gperf" - {"DownTeeArrow;", "↧"}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 1926 "HTMLCharacterReference.gperf" - {"simg;", "⪞"}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 501 "HTMLCharacterReference.gperf" - {"Rrightarrow;", "⇛"}, - {""}, -#line 1465 "HTMLCharacterReference.gperf" - {"multimap;", "⊸"}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 472 "HTMLCharacterReference.gperf" - {"ReverseEquilibrium;", "⇋"}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 759 "HTMLCharacterReference.gperf" - {"bigwedge;", "⋀"}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 261 "HTMLCharacterReference.gperf" - {"Kcedil;", "Ķ"}, -#line 1622 "HTMLCharacterReference.gperf" - {"nvinfin;", "⧞"}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 763 "HTMLCharacterReference.gperf" - {"blacktriangle;", "▴"}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, + {"ulcorn;", "⌜"}, + {""}, +#line 2014 "HTMLCharacterReference.gperf" + {"supdsub;", "⫘"}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 2125 "HTMLCharacterReference.gperf" + {"uopf;", "𝕦"}, + {""}, + {""}, +#line 1265 "HTMLCharacterReference.gperf" + {"kappav;", "ϰ"}, +#line 1982 "HTMLCharacterReference.gperf" + {"submult;", "⫁"}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 480 "HTMLCharacterReference.gperf" + {"RightArrowLeftArrow;", "⇄"}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 1508 "HTMLCharacterReference.gperf" + {"nequiv;", "≢"}, + {""}, + {""}, +#line 929 "HTMLCharacterReference.gperf" + {"curlyvee;", "⋎"}, +#line 1887 "HTMLCharacterReference.gperf" + {"sccue;", "≽"}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, #line 1987 "HTMLCharacterReference.gperf" - {"subseteq;", "⊆"}, + {"subset;", "⊂"}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 1660 "HTMLCharacterReference.gperf" + {"olcir;", "⦾"}, +#line 238 "HTMLCharacterReference.gperf" + {"Implies;", "⇒"}, + {""}, +#line 285 "HTMLCharacterReference.gperf" + {"LeftDownVector;", "⇃"}, + {""}, + {""}, +#line 286 "HTMLCharacterReference.gperf" + {"LeftDownVectorBar;", "⥙"}, +#line 767 "HTMLCharacterReference.gperf" + {"blacktriangleright;", "▸"}, +#line 1804 "HTMLCharacterReference.gperf" + {"ratail;", "⤚"}, + {""}, +#line 1997 "HTMLCharacterReference.gperf" + {"succcurlyeq;", "≽"}, + {""}, + {""}, + {""}, +#line 1669 "HTMLCharacterReference.gperf" + {"oopf;", "𝕠"}, +#line 282 "HTMLCharacterReference.gperf" + {"LeftCeiling;", "⌈"}, + {""}, +#line 1788 "HTMLCharacterReference.gperf" + {"range;", "⦥"}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 1832 "HTMLCharacterReference.gperf" + {"rfloor;", "⌋"}, + {""}, + {""}, +#line 618 "HTMLCharacterReference.gperf" + {"VerticalLine;", "|"}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 1945 "HTMLCharacterReference.gperf" + {"solb;", "⧄"}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 1501 "HTMLCharacterReference.gperf" + {"ndash;", "–"}, +#line 1575 "HTMLCharacterReference.gperf" + {"nrightarrow;", "↛"}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 1632 "HTMLCharacterReference.gperf" + {"nwarhk;", "⤣"}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 403 "HTMLCharacterReference.gperf" + {"NotVerticalBar;", "∤"}, +#line 312 "HTMLCharacterReference.gperf" + {"Lleftarrow;", "⇚"}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 2046 "HTMLCharacterReference.gperf" + {"telrec;", "⌕"}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 2109 "HTMLCharacterReference.gperf" + {"udhar;", "⥮"}, + {""}, + {""}, +#line 1707 "HTMLCharacterReference.gperf" + {"permil;", "‰"}, + {""}, + {""}, + {""}, +#line 414 "HTMLCharacterReference.gperf" + {"Odblac;", "Ő"}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 51 "HTMLCharacterReference.gperf" + {"Barwed;", "⌆"}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 1935 "HTMLCharacterReference.gperf" + {"smallsetminus;", "∖"}, +#line 1941 "HTMLCharacterReference.gperf" + {"smte;", "⪬"}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 393 "HTMLCharacterReference.gperf" + {"NotSucceeds;", "⊁"}, + {""}, + {""}, + {""}, + {""}, +#line 1032 "HTMLCharacterReference.gperf" + {"empty;", "∅"}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 216 "HTMLCharacterReference.gperf" + {"HilbertSpace;", "ℋ"}, +#line 1222 "HTMLCharacterReference.gperf" + {"imagpart;", "ℑ"}, + {""}, + {""}, +#line 1740 "HTMLCharacterReference.gperf" + {"prcue;", "≼"}, +#line 2118 "HTMLCharacterReference.gperf" + {"ulcorner;", "⌜"}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 1704 "HTMLCharacterReference.gperf" + {"pcy;", "п"}, + {""}, + {""}, +#line 147 "HTMLCharacterReference.gperf" + {"DownTeeArrow;", "↧"}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 1927 "HTMLCharacterReference.gperf" + {"simg;", "⪞"}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 502 "HTMLCharacterReference.gperf" + {"Rrightarrow;", "⇛"}, + {""}, +#line 1466 "HTMLCharacterReference.gperf" + {"multimap;", "⊸"}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 473 "HTMLCharacterReference.gperf" + {"ReverseEquilibrium;", "⇋"}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 760 "HTMLCharacterReference.gperf" + {"bigwedge;", "⋀"}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 262 "HTMLCharacterReference.gperf" + {"Kcedil;", "Ķ"}, +#line 1623 "HTMLCharacterReference.gperf" + {"nvinfin;", "⧞"}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 764 "HTMLCharacterReference.gperf" + {"blacktriangle;", "▴"}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, #line 1988 "HTMLCharacterReference.gperf" - {"subseteqq;", "⫅"}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 1660 "HTMLCharacterReference.gperf" - {"olcross;", "⦻"}, - {""}, - {""}, -#line 202 "HTMLCharacterReference.gperf" - {"GreaterEqualLess;", "⋛"}, - {""}, - {""}, -#line 2120 "HTMLCharacterReference.gperf" - {"umacr;", "ū"}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 1880 "HTMLCharacterReference.gperf" - {"sacute;", "ś"}, - {""}, - {""}, -#line 1677 "HTMLCharacterReference.gperf" - {"ordf", "ª"}, + {"subseteq;", "⊆"}, +#line 1989 "HTMLCharacterReference.gperf" + {"subseteqq;", "⫅"}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 1661 "HTMLCharacterReference.gperf" + {"olcross;", "⦻"}, + {""}, + {""}, +#line 203 "HTMLCharacterReference.gperf" + {"GreaterEqualLess;", "⋛"}, + {""}, + {""}, +#line 2121 "HTMLCharacterReference.gperf" + {"umacr;", "ū"}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 1881 "HTMLCharacterReference.gperf" + {"sacute;", "ś"}, + {""}, + {""}, #line 1678 "HTMLCharacterReference.gperf" - {"ordf;", "ª"}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 129 "HTMLCharacterReference.gperf" - {"DoubleRightArrow;", "⇒"}, - {""}, - {""}, - {""}, -#line 1938 "HTMLCharacterReference.gperf" - {"smile;", "⌣"}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 1941 "HTMLCharacterReference.gperf" - {"smtes;", "⪬︀"}, -#line 1663 "HTMLCharacterReference.gperf" - {"omacr;", "ō"}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 1923 "HTMLCharacterReference.gperf" - {"simdot;", "⩪"}, - {""}, - {""}, - {""}, -#line 79 "HTMLCharacterReference.gperf" - {"CirclePlus;", "⊕"}, - {""}, - {""}, - {""}, -#line 1809 "HTMLCharacterReference.gperf" - {"rbrack;", "]"}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 1535 "HTMLCharacterReference.gperf" - {"nleftarrow;", "↚"}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, + {"ordf", "ª"}, +#line 1679 "HTMLCharacterReference.gperf" + {"ordf;", "ª"}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 130 "HTMLCharacterReference.gperf" + {"DoubleRightArrow;", "⇒"}, + {""}, + {""}, + {""}, +#line 1939 "HTMLCharacterReference.gperf" + {"smile;", "⌣"}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 1942 "HTMLCharacterReference.gperf" + {"smtes;", "⪬︀"}, +#line 1664 "HTMLCharacterReference.gperf" + {"omacr;", "ō"}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 1924 "HTMLCharacterReference.gperf" + {"simdot;", "⩪"}, + {""}, + {""}, + {""}, #line 80 "HTMLCharacterReference.gperf" - {"CircleTimes;", "⊗"}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 593 "HTMLCharacterReference.gperf" - {"UpDownArrow;", "↕"}, - {""}, -#line 1786 "HTMLCharacterReference.gperf" - {"rangd;", "⦒"}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 1032 "HTMLCharacterReference.gperf" - {"emptyset;", "∅"}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 68 "HTMLCharacterReference.gperf" - {"Ccedil", "Ç"}, + {"CirclePlus;", "⊕"}, + {""}, + {""}, + {""}, +#line 1810 "HTMLCharacterReference.gperf" + {"rbrack;", "]"}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 1536 "HTMLCharacterReference.gperf" + {"nleftarrow;", "↚"}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 81 "HTMLCharacterReference.gperf" + {"CircleTimes;", "⊗"}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 594 "HTMLCharacterReference.gperf" + {"UpDownArrow;", "↕"}, + {""}, +#line 1787 "HTMLCharacterReference.gperf" + {"rangd;", "⦒"}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 1033 "HTMLCharacterReference.gperf" + {"emptyset;", "∅"}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, #line 69 "HTMLCharacterReference.gperf" - {"Ccedil;", "Ç"}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 1927 "HTMLCharacterReference.gperf" - {"simgE;", "⪠"}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 34 "HTMLCharacterReference.gperf" - {"Alpha;", "Α"}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 408 "HTMLCharacterReference.gperf" - {"Oacute", "Ó"}, + {"Ccedil", "Ç"}, +#line 70 "HTMLCharacterReference.gperf" + {"Ccedil;", "Ç"}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 1928 "HTMLCharacterReference.gperf" + {"simgE;", "⪠"}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 35 "HTMLCharacterReference.gperf" + {"Alpha;", "Α"}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, #line 409 "HTMLCharacterReference.gperf" - {"Oacute;", "Ó"}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 1503 "HTMLCharacterReference.gperf" - {"nearhk;", "⤤"}, - {""}, -#line 142 "HTMLCharacterReference.gperf" - {"DownRightTeeVector;", "⥟"}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 1708 "HTMLCharacterReference.gperf" - {"pertenk;", "‱"}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 2162 "HTMLCharacterReference.gperf" - {"varsubsetneq;", "⊊︀"}, - {""}, - {""}, -#line 1536 "HTMLCharacterReference.gperf" - {"nleftrightarrow;", "↮"}, -#line 2156 "HTMLCharacterReference.gperf" - {"varphi;", "ϕ"}, -#line 1743 "HTMLCharacterReference.gperf" - {"preccurlyeq;", "≼"}, - {""}, -#line 1788 "HTMLCharacterReference.gperf" - {"rangle;", "⟩"}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 1989 "HTMLCharacterReference.gperf" - {"subsetneq;", "⊊"}, + {"Oacute", "Ó"}, +#line 410 "HTMLCharacterReference.gperf" + {"Oacute;", "Ó"}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 1504 "HTMLCharacterReference.gperf" + {"nearhk;", "⤤"}, + {""}, +#line 143 "HTMLCharacterReference.gperf" + {"DownRightTeeVector;", "⥟"}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 1709 "HTMLCharacterReference.gperf" + {"pertenk;", "‱"}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 2163 "HTMLCharacterReference.gperf" + {"varsubsetneq;", "⊊︀"}, + {""}, + {""}, +#line 1537 "HTMLCharacterReference.gperf" + {"nleftrightarrow;", "↮"}, +#line 2157 "HTMLCharacterReference.gperf" + {"varphi;", "ϕ"}, +#line 1744 "HTMLCharacterReference.gperf" + {"preccurlyeq;", "≼"}, + {""}, +#line 1789 "HTMLCharacterReference.gperf" + {"rangle;", "⟩"}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, #line 1990 "HTMLCharacterReference.gperf" - {"subsetneqq;", "⫋"}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 2107 "HTMLCharacterReference.gperf" - {"udblac;", "ű"}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 486 "HTMLCharacterReference.gperf" - {"RightTee;", "⊢"}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 217 "HTMLCharacterReference.gperf" - {"HorizontalLine;", "─"}, -#line 2028 "HTMLCharacterReference.gperf" - {"supsim;", "⫈"}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 488 "HTMLCharacterReference.gperf" - {"RightTeeVector;", "⥛"}, - {""}, - {""}, - {""}, - {""}, -#line 1948 "HTMLCharacterReference.gperf" - {"spadesuit;", "♠"}, - {""}, - {""}, - {""}, - {""}, -#line 1173 "HTMLCharacterReference.gperf" - {"half;", "½"}, - {""}, - {""}, - {""}, -#line 1893 "HTMLCharacterReference.gperf" - {"scpolint;", "⨓"}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 1977 "HTMLCharacterReference.gperf" - {"subE;", "⫅"}, - {""}, -#line 1215 "HTMLCharacterReference.gperf" - {"iinfin;", "⧜"}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 1644 "HTMLCharacterReference.gperf" - {"odblac;", "ő"}, - {""}, - {""}, - {""}, -#line 989 "HTMLCharacterReference.gperf" - {"downharpoonright;", "⇂"}, - {""}, - {""}, - {""}, -#line 1733 "HTMLCharacterReference.gperf" - {"popf;", "𝕡"}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 2030 "HTMLCharacterReference.gperf" - {"supsup;", "⫖"}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 433 "HTMLCharacterReference.gperf" - {"OverBrace;", "⏞"}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 2101 "HTMLCharacterReference.gperf" - {"ubrcy;", "ў"}, -#line 1294 "HTMLCharacterReference.gperf" - {"larrhk;", "↩"}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 498 "HTMLCharacterReference.gperf" - {"Rightarrow;", "⇒"}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 1890 "HTMLCharacterReference.gperf" - {"scnE;", "⪶"}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 139 "HTMLCharacterReference.gperf" - {"DownLeftTeeVector;", "⥞"}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 77 "HTMLCharacterReference.gperf" - {"CircleDot;", "⊙"}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 611 "HTMLCharacterReference.gperf" - {"Vdash;", "⊩"}, - {""}, - {""}, -#line 1731 "HTMLCharacterReference.gperf" - {"pm;", "±"}, - {""}, - {""}, -#line 1475 "HTMLCharacterReference.gperf" - {"nRightarrow;", "⇏"}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 1847 "HTMLCharacterReference.gperf" - {"ring;", "˚"}, - {""}, -#line 1782 "HTMLCharacterReference.gperf" - {"racute;", "ŕ"}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, + {"subsetneq;", "⊊"}, +#line 1991 "HTMLCharacterReference.gperf" + {"subsetneqq;", "⫋"}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 2108 "HTMLCharacterReference.gperf" + {"udblac;", "ű"}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 487 "HTMLCharacterReference.gperf" + {"RightTee;", "⊢"}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 218 "HTMLCharacterReference.gperf" + {"HorizontalLine;", "─"}, +#line 2029 "HTMLCharacterReference.gperf" + {"supsim;", "⫈"}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 489 "HTMLCharacterReference.gperf" + {"RightTeeVector;", "⥛"}, + {""}, + {""}, + {""}, + {""}, +#line 1949 "HTMLCharacterReference.gperf" + {"spadesuit;", "♠"}, + {""}, + {""}, + {""}, + {""}, +#line 1174 "HTMLCharacterReference.gperf" + {"half;", "½"}, + {""}, + {""}, + {""}, +#line 1894 "HTMLCharacterReference.gperf" + {"scpolint;", "⨓"}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 1978 "HTMLCharacterReference.gperf" + {"subE;", "⫅"}, + {""}, +#line 1216 "HTMLCharacterReference.gperf" + {"iinfin;", "⧜"}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 1645 "HTMLCharacterReference.gperf" + {"odblac;", "ő"}, + {""}, + {""}, + {""}, +#line 990 "HTMLCharacterReference.gperf" + {"downharpoonright;", "⇂"}, + {""}, + {""}, + {""}, +#line 1734 "HTMLCharacterReference.gperf" + {"popf;", "𝕡"}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 2031 "HTMLCharacterReference.gperf" + {"supsup;", "⫖"}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 434 "HTMLCharacterReference.gperf" + {"OverBrace;", "⏞"}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 2102 "HTMLCharacterReference.gperf" + {"ubrcy;", "ў"}, +#line 1295 "HTMLCharacterReference.gperf" + {"larrhk;", "↩"}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 499 "HTMLCharacterReference.gperf" + {"Rightarrow;", "⇒"}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 1891 "HTMLCharacterReference.gperf" + {"scnE;", "⪶"}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 140 "HTMLCharacterReference.gperf" + {"DownLeftTeeVector;", "⥞"}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 78 "HTMLCharacterReference.gperf" + {"CircleDot;", "⊙"}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, #line 612 "HTMLCharacterReference.gperf" - {"Vdashl;", "⫦"}, -#line 1626 "HTMLCharacterReference.gperf" - {"nvltrie;", "⊴⃒"}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 494 "HTMLCharacterReference.gperf" - {"RightUpVector;", "↾"}, -#line 21 "HTMLCharacterReference.gperf" - {"AElig", "Æ"}, + {"Vdash;", "⊩"}, + {""}, + {""}, +#line 1732 "HTMLCharacterReference.gperf" + {"pm;", "±"}, + {""}, + {""}, +#line 1476 "HTMLCharacterReference.gperf" + {"nRightarrow;", "⇏"}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 1848 "HTMLCharacterReference.gperf" + {"ring;", "˚"}, + {""}, +#line 1783 "HTMLCharacterReference.gperf" + {"racute;", "ŕ"}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 613 "HTMLCharacterReference.gperf" + {"Vdashl;", "⫦"}, +#line 1627 "HTMLCharacterReference.gperf" + {"nvltrie;", "⊴⃒"}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 495 "HTMLCharacterReference.gperf" + {"RightUpVector;", "↾"}, #line 22 "HTMLCharacterReference.gperf" - {"AElig;", "Æ"}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 2163 "HTMLCharacterReference.gperf" - {"varsubsetneqq;", "⫋︀"}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 2093 "HTMLCharacterReference.gperf" - {"twixt;", "≬"}, -#line 2001 "HTMLCharacterReference.gperf" - {"succsim;", "≿"}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 425 "HTMLCharacterReference.gperf" - {"Oslash", "Ø"}, + {"AElig", "Æ"}, +#line 23 "HTMLCharacterReference.gperf" + {"AElig;", "Æ"}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 2164 "HTMLCharacterReference.gperf" + {"varsubsetneqq;", "⫋︀"}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 2094 "HTMLCharacterReference.gperf" + {"twixt;", "≬"}, +#line 2002 "HTMLCharacterReference.gperf" + {"succsim;", "≿"}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, #line 426 "HTMLCharacterReference.gperf" - {"Oslash;", "Ø"}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 1822 "HTMLCharacterReference.gperf" - {"rdsh;", "↳"}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 520 "HTMLCharacterReference.gperf" - {"SmallCircle;", "∘"}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 478 "HTMLCharacterReference.gperf" - {"RightArrowBar;", "⇥"}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 53 "HTMLCharacterReference.gperf" - {"Bernoullis;", "ℬ"}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 619 "HTMLCharacterReference.gperf" - {"VerticalTilde;", "≀"}, - {""}, - {""}, - {""}, - {""}, -#line 357 "HTMLCharacterReference.gperf" - {"NotEqualTilde;", "≂̸"}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 397 "HTMLCharacterReference.gperf" - {"NotSupersetEqual;", "⊉"}, - {""}, - {""}, - {""}, -#line 1751 "HTMLCharacterReference.gperf" - {"prnE;", "⪵"}, - {""}, - {""}, -#line 1850 "HTMLCharacterReference.gperf" - {"rlhar;", "⇌"}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 1077 "HTMLCharacterReference.gperf" - {"ffllig;", "ffl"}, -#line 2035 "HTMLCharacterReference.gperf" - {"swnwar;", "⤪"}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 2053 "HTMLCharacterReference.gperf" - {"thicksim;", "∼"}, - {""}, - {""}, - {""}, -#line 2095 "HTMLCharacterReference.gperf" - {"twoheadrightarrow;", "↠"}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 2119 "HTMLCharacterReference.gperf" - {"ultri;", "◸"}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 434 "HTMLCharacterReference.gperf" - {"OverBracket;", "⎴"}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 1318 "HTMLCharacterReference.gperf" - {"ldrdhar;", "⥧"}, - {""}, - {""}, -#line 1441 "HTMLCharacterReference.gperf" - {"mdash;", "—"}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 1187 "HTMLCharacterReference.gperf" - {"hkswarow;", "⤦"}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 143 "HTMLCharacterReference.gperf" - {"DownRightVector;", "⇁"}, - {""}, -#line 419 "HTMLCharacterReference.gperf" - {"Omicron;", "Ο"}, + {"Oslash", "Ø"}, +#line 427 "HTMLCharacterReference.gperf" + {"Oslash;", "Ø"}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 1823 "HTMLCharacterReference.gperf" + {"rdsh;", "↳"}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 521 "HTMLCharacterReference.gperf" + {"SmallCircle;", "∘"}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 479 "HTMLCharacterReference.gperf" + {"RightArrowBar;", "⇥"}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 54 "HTMLCharacterReference.gperf" + {"Bernoullis;", "ℬ"}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 620 "HTMLCharacterReference.gperf" + {"VerticalTilde;", "≀"}, + {""}, + {""}, + {""}, + {""}, +#line 358 "HTMLCharacterReference.gperf" + {"NotEqualTilde;", "≂̸"}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 398 "HTMLCharacterReference.gperf" + {"NotSupersetEqual;", "⊉"}, + {""}, + {""}, + {""}, +#line 1752 "HTMLCharacterReference.gperf" + {"prnE;", "⪵"}, + {""}, + {""}, +#line 1851 "HTMLCharacterReference.gperf" + {"rlhar;", "⇌"}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 1078 "HTMLCharacterReference.gperf" + {"ffllig;", "ffl"}, +#line 2036 "HTMLCharacterReference.gperf" + {"swnwar;", "⤪"}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 2054 "HTMLCharacterReference.gperf" + {"thicksim;", "∼"}, + {""}, + {""}, + {""}, +#line 2096 "HTMLCharacterReference.gperf" + {"twoheadrightarrow;", "↠"}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 2120 "HTMLCharacterReference.gperf" + {"ultri;", "◸"}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 435 "HTMLCharacterReference.gperf" + {"OverBracket;", "⎴"}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 1319 "HTMLCharacterReference.gperf" + {"ldrdhar;", "⥧"}, + {""}, + {""}, +#line 1442 "HTMLCharacterReference.gperf" + {"mdash;", "—"}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 1188 "HTMLCharacterReference.gperf" + {"hkswarow;", "⤦"}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, #line 144 "HTMLCharacterReference.gperf" - {"DownRightVectorBar;", "⥗"}, -#line 2098 "HTMLCharacterReference.gperf" - {"uacute", "ú"}, + {"DownRightVector;", "⇁"}, + {""}, +#line 420 "HTMLCharacterReference.gperf" + {"Omicron;", "Ο"}, +#line 145 "HTMLCharacterReference.gperf" + {"DownRightVectorBar;", "⥗"}, #line 2099 "HTMLCharacterReference.gperf" - {"uacute;", "ú"}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 487 "HTMLCharacterReference.gperf" - {"RightTeeArrow;", "↦"}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 432 "HTMLCharacterReference.gperf" - {"OverBar;", "‾"}, -#line 1488 "HTMLCharacterReference.gperf" - {"naturals;", "ℕ"}, - {""}, - {""}, -#line 1265 "HTMLCharacterReference.gperf" - {"kcedil;", "ķ"}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 493 "HTMLCharacterReference.gperf" - {"RightUpTeeVector;", "⥜"}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 764 "HTMLCharacterReference.gperf" - {"blacktriangledown;", "▾"}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 358 "HTMLCharacterReference.gperf" - {"NotExists;", "∄"}, -#line 223 "HTMLCharacterReference.gperf" - {"IJlig;", "IJ"}, -#line 305 "HTMLCharacterReference.gperf" - {"LessGreater;", "≶"}, - {""}, - {""}, - {""}, - {""}, -#line 1636 "HTMLCharacterReference.gperf" - {"oacute", "ó"}, + {"uacute", "ú"}, +#line 2100 "HTMLCharacterReference.gperf" + {"uacute;", "ú"}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 488 "HTMLCharacterReference.gperf" + {"RightTeeArrow;", "↦"}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 433 "HTMLCharacterReference.gperf" + {"OverBar;", "‾"}, +#line 1489 "HTMLCharacterReference.gperf" + {"naturals;", "ℕ"}, + {""}, + {""}, +#line 1266 "HTMLCharacterReference.gperf" + {"kcedil;", "ķ"}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 494 "HTMLCharacterReference.gperf" + {"RightUpTeeVector;", "⥜"}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 765 "HTMLCharacterReference.gperf" + {"blacktriangledown;", "▾"}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 359 "HTMLCharacterReference.gperf" + {"NotExists;", "∄"}, +#line 224 "HTMLCharacterReference.gperf" + {"IJlig;", "IJ"}, +#line 306 "HTMLCharacterReference.gperf" + {"LessGreater;", "≶"}, + {""}, + {""}, + {""}, + {""}, #line 1637 "HTMLCharacterReference.gperf" - {"oacute;", "ó"}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 1757 "HTMLCharacterReference.gperf" - {"profsurf;", "⌓"}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 1701 "HTMLCharacterReference.gperf" - {"parsl;", "⫽"}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 277 "HTMLCharacterReference.gperf" - {"LeftAngleBracket;", "⟨"}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 1945 "HTMLCharacterReference.gperf" - {"solbar;", "⌿"}, - {""}, - {""}, - {""}, - {""}, -#line 78 "HTMLCharacterReference.gperf" - {"CircleMinus;", "⊖"}, - {""}, - {""}, - {""}, - {""}, -#line 91 "HTMLCharacterReference.gperf" - {"CounterClockwiseContourIntegral;", "∳"}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 2017 "HTMLCharacterReference.gperf" - {"suphsub;", "⫗"}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 883 "HTMLCharacterReference.gperf" - {"cirscir;", "⧂"}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 1487 "HTMLCharacterReference.gperf" - {"natural;", "♮"}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 110 "HTMLCharacterReference.gperf" - {"DiacriticalDot;", "˙"}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 236 "HTMLCharacterReference.gperf" - {"ImaginaryI;", "ⅈ"}, - {""}, - {""}, - {""}, - {""}, -#line 591 "HTMLCharacterReference.gperf" - {"UpArrowBar;", "⤒"}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 394 "HTMLCharacterReference.gperf" - {"NotSucceedsSlantEqual;", "⋡"}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 1906 "HTMLCharacterReference.gperf" - {"seswar;", "⤩"}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 422 "HTMLCharacterReference.gperf" - {"OpenCurlyQuote;", "‘"}, - {""}, - {""}, -#line 2071 "HTMLCharacterReference.gperf" - {"topfork;", "⫚"}, - {""}, - {""}, -#line 435 "HTMLCharacterReference.gperf" - {"OverParenthesis;", "⏜"}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, + {"oacute", "ó"}, +#line 1638 "HTMLCharacterReference.gperf" + {"oacute;", "ó"}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 1758 "HTMLCharacterReference.gperf" + {"profsurf;", "⌓"}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 1702 "HTMLCharacterReference.gperf" + {"parsl;", "⫽"}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 278 "HTMLCharacterReference.gperf" + {"LeftAngleBracket;", "⟨"}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 1946 "HTMLCharacterReference.gperf" + {"solbar;", "⌿"}, + {""}, + {""}, + {""}, + {""}, +#line 79 "HTMLCharacterReference.gperf" + {"CircleMinus;", "⊖"}, + {""}, + {""}, + {""}, + {""}, +#line 92 "HTMLCharacterReference.gperf" + {"CounterClockwiseContourIntegral;", "∳"}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 2018 "HTMLCharacterReference.gperf" + {"suphsub;", "⫗"}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 884 "HTMLCharacterReference.gperf" + {"cirscir;", "⧂"}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 1488 "HTMLCharacterReference.gperf" + {"natural;", "♮"}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, #line 111 "HTMLCharacterReference.gperf" - {"DiacriticalDoubleAcute;", "˝"}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 1800 "HTMLCharacterReference.gperf" - {"rarrsim;", "⥴"}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 2042 "HTMLCharacterReference.gperf" - {"tcedil;", "ţ"}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 1848 "HTMLCharacterReference.gperf" - {"risingdotseq;", "≓"}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 1648 "HTMLCharacterReference.gperf" - {"oelig;", "œ"}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 331 "HTMLCharacterReference.gperf" - {"MinusPlus;", "∓"}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 1913 "HTMLCharacterReference.gperf" - {"shchcy;", "щ"}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 1699 "HTMLCharacterReference.gperf" - {"parallel;", "∥"}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 476 "HTMLCharacterReference.gperf" - {"RightAngleBracket;", "⟩"}, - {""}, - {""}, - {""}, -#line 1686 "HTMLCharacterReference.gperf" - {"oslash", "ø"}, -#line 1687 "HTMLCharacterReference.gperf" - {"oslash;", "ø"}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 1748 "HTMLCharacterReference.gperf" - {"precsim;", "≾"}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 1582 "HTMLCharacterReference.gperf" - {"nshortparallel;", "∦"}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, + {"DiacriticalDot;", "˙"}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 237 "HTMLCharacterReference.gperf" + {"ImaginaryI;", "ⅈ"}, + {""}, + {""}, + {""}, + {""}, #line 592 "HTMLCharacterReference.gperf" - {"UpArrowDownArrow;", "⇅"}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 2000 "HTMLCharacterReference.gperf" - {"succnsim;", "⋩"}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 1665 "HTMLCharacterReference.gperf" - {"omicron;", "ο"}, - {""}, - {""}, - {""}, - {""}, -#line 881 "HTMLCharacterReference.gperf" - {"cirfnint;", "⨐"}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 391 "HTMLCharacterReference.gperf" - {"NotSubsetEqual;", "⊈"}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 2036 "HTMLCharacterReference.gperf" - {"szlig", "ß"}, + {"UpArrowBar;", "⤒"}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 395 "HTMLCharacterReference.gperf" + {"NotSucceedsSlantEqual;", "⋡"}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 1907 "HTMLCharacterReference.gperf" + {"seswar;", "⤩"}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 423 "HTMLCharacterReference.gperf" + {"OpenCurlyQuote;", "‘"}, + {""}, + {""}, +#line 2072 "HTMLCharacterReference.gperf" + {"topfork;", "⫚"}, + {""}, + {""}, +#line 436 "HTMLCharacterReference.gperf" + {"OverParenthesis;", "⏜"}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 112 "HTMLCharacterReference.gperf" + {"DiacriticalDoubleAcute;", "˝"}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 1801 "HTMLCharacterReference.gperf" + {"rarrsim;", "⥴"}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 2043 "HTMLCharacterReference.gperf" + {"tcedil;", "ţ"}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 1849 "HTMLCharacterReference.gperf" + {"risingdotseq;", "≓"}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 1649 "HTMLCharacterReference.gperf" + {"oelig;", "œ"}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 332 "HTMLCharacterReference.gperf" + {"MinusPlus;", "∓"}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 1914 "HTMLCharacterReference.gperf" + {"shchcy;", "щ"}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 1700 "HTMLCharacterReference.gperf" + {"parallel;", "∥"}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 477 "HTMLCharacterReference.gperf" + {"RightAngleBracket;", "⟩"}, + {""}, + {""}, + {""}, +#line 1687 "HTMLCharacterReference.gperf" + {"oslash", "ø"}, +#line 1688 "HTMLCharacterReference.gperf" + {"oslash;", "ø"}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 1749 "HTMLCharacterReference.gperf" + {"precsim;", "≾"}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 1583 "HTMLCharacterReference.gperf" + {"nshortparallel;", "∦"}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 593 "HTMLCharacterReference.gperf" + {"UpArrowDownArrow;", "⇅"}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 2001 "HTMLCharacterReference.gperf" + {"succnsim;", "⋩"}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 1666 "HTMLCharacterReference.gperf" + {"omicron;", "ο"}, + {""}, + {""}, + {""}, + {""}, +#line 882 "HTMLCharacterReference.gperf" + {"cirfnint;", "⨐"}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 392 "HTMLCharacterReference.gperf" + {"NotSubsetEqual;", "⊈"}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, #line 2037 "HTMLCharacterReference.gperf" - {"szlig;", "ß"}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 1921 "HTMLCharacterReference.gperf" - {"sigmav;", "ς"}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 1755 "HTMLCharacterReference.gperf" - {"profalar;", "⌮"}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 1732 "HTMLCharacterReference.gperf" - {"pointint;", "⨕"}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 1888 "HTMLCharacterReference.gperf" - {"scedil;", "ş"}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 314 "HTMLCharacterReference.gperf" - {"LongLeftRightArrow;", "⟷"}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 1852 "HTMLCharacterReference.gperf" - {"rmoust;", "⎱"}, - {""}, -#line 2091 "HTMLCharacterReference.gperf" - {"tshcy;", "ћ"}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 2129 "HTMLCharacterReference.gperf" - {"uplus;", "⊎"}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 1778 "HTMLCharacterReference.gperf" - {"rAtail;", "⤜"}, - {""}, -#line 2029 "HTMLCharacterReference.gperf" - {"supsub;", "⫔"}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 1072 "HTMLCharacterReference.gperf" - {"fallingdotseq;", "≒"}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 65 "HTMLCharacterReference.gperf" - {"CapitalDifferentialD;", "ⅅ"}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 1671 "HTMLCharacterReference.gperf" - {"oplus;", "⊕"}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 283 "HTMLCharacterReference.gperf" - {"LeftDownTeeVector;", "⥡"}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 1991 "HTMLCharacterReference.gperf" - {"subsim;", "⫇"}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 560 "HTMLCharacterReference.gperf" - {"Tilde;", "∼"}, - {""}, -#line 618 "HTMLCharacterReference.gperf" - {"VerticalSeparator;", "❘"}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 1436 "HTMLCharacterReference.gperf" - {"mapstoleft;", "↤"}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 492 "HTMLCharacterReference.gperf" - {"RightUpDownVector;", "⥏"}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 1993 "HTMLCharacterReference.gperf" - {"subsup;", "⫓"}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 1892 "HTMLCharacterReference.gperf" - {"scnsim;", "⋩"}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 1719 "HTMLCharacterReference.gperf" - {"plankv;", "ℏ"}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 2050 "HTMLCharacterReference.gperf" - {"thetasym;", "ϑ"}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 115 "HTMLCharacterReference.gperf" - {"DifferentialD;", "ⅆ"}, - {""}, -#line 366 "HTMLCharacterReference.gperf" - {"NotHumpDownHump;", "≎̸"}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 2161 "HTMLCharacterReference.gperf" - {"varsigma;", "ς"}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, + {"szlig", "ß"}, +#line 2038 "HTMLCharacterReference.gperf" + {"szlig;", "ß"}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 1922 "HTMLCharacterReference.gperf" + {"sigmav;", "ς"}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 1756 "HTMLCharacterReference.gperf" + {"profalar;", "⌮"}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 1733 "HTMLCharacterReference.gperf" + {"pointint;", "⨕"}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 1889 "HTMLCharacterReference.gperf" + {"scedil;", "ş"}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 315 "HTMLCharacterReference.gperf" + {"LongLeftRightArrow;", "⟷"}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 1853 "HTMLCharacterReference.gperf" + {"rmoust;", "⎱"}, + {""}, +#line 2092 "HTMLCharacterReference.gperf" + {"tshcy;", "ћ"}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 2130 "HTMLCharacterReference.gperf" + {"uplus;", "⊎"}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 1779 "HTMLCharacterReference.gperf" + {"rAtail;", "⤜"}, + {""}, +#line 2030 "HTMLCharacterReference.gperf" + {"supsub;", "⫔"}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 1073 "HTMLCharacterReference.gperf" + {"fallingdotseq;", "≒"}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 66 "HTMLCharacterReference.gperf" + {"CapitalDifferentialD;", "ⅅ"}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 1672 "HTMLCharacterReference.gperf" + {"oplus;", "⊕"}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 284 "HTMLCharacterReference.gperf" + {"LeftDownTeeVector;", "⥡"}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 1992 "HTMLCharacterReference.gperf" + {"subsim;", "⫇"}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 561 "HTMLCharacterReference.gperf" + {"Tilde;", "∼"}, + {""}, +#line 619 "HTMLCharacterReference.gperf" + {"VerticalSeparator;", "❘"}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 1437 "HTMLCharacterReference.gperf" + {"mapstoleft;", "↤"}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 493 "HTMLCharacterReference.gperf" + {"RightUpDownVector;", "⥏"}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 1994 "HTMLCharacterReference.gperf" + {"subsup;", "⫓"}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 1893 "HTMLCharacterReference.gperf" + {"scnsim;", "⋩"}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, #line 1720 "HTMLCharacterReference.gperf" - {"plus;", "+"}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 1727 "HTMLCharacterReference.gperf" - {"plusmn", "±"}, + {"plankv;", "ℏ"}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 2051 "HTMLCharacterReference.gperf" + {"thetasym;", "ϑ"}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 116 "HTMLCharacterReference.gperf" + {"DifferentialD;", "ⅆ"}, + {""}, +#line 367 "HTMLCharacterReference.gperf" + {"NotHumpDownHump;", "≎̸"}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 2162 "HTMLCharacterReference.gperf" + {"varsigma;", "ς"}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 1721 "HTMLCharacterReference.gperf" + {"plus;", "+"}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, #line 1728 "HTMLCharacterReference.gperf" - {"plusmn;", "±"}, - {""}, -#line 1747 "HTMLCharacterReference.gperf" - {"precnsim;", "⋨"}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 765 "HTMLCharacterReference.gperf" - {"blacktriangleleft;", "◂"}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 1753 "HTMLCharacterReference.gperf" - {"prnsim;", "⋨"}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 1814 "HTMLCharacterReference.gperf" - {"rcedil;", "ŗ"}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 1937 "HTMLCharacterReference.gperf" - {"smid;", "∣"}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 482 "HTMLCharacterReference.gperf" - {"RightDownTeeVector;", "⥝"}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 1726 "HTMLCharacterReference.gperf" - {"pluse;", "⩲"}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 1730 "HTMLCharacterReference.gperf" - {"plustwo;", "⨧"}, - {""}, - {""}, - {""}, - {""}, -#line 1845 "HTMLCharacterReference.gperf" - {"rightsquigarrow;", "↝"}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 436 "HTMLCharacterReference.gperf" - {"PartialD;", "∂"}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, + {"plusmn", "±"}, +#line 1729 "HTMLCharacterReference.gperf" + {"plusmn;", "±"}, + {""}, +#line 1748 "HTMLCharacterReference.gperf" + {"precnsim;", "⋨"}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 766 "HTMLCharacterReference.gperf" + {"blacktriangleleft;", "◂"}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 1754 "HTMLCharacterReference.gperf" + {"prnsim;", "⋨"}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 1815 "HTMLCharacterReference.gperf" + {"rcedil;", "ŗ"}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 1938 "HTMLCharacterReference.gperf" + {"smid;", "∣"}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 483 "HTMLCharacterReference.gperf" + {"RightDownTeeVector;", "⥝"}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 1727 "HTMLCharacterReference.gperf" + {"pluse;", "⩲"}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 1731 "HTMLCharacterReference.gperf" + {"plustwo;", "⨧"}, + {""}, + {""}, + {""}, + {""}, +#line 1846 "HTMLCharacterReference.gperf" + {"rightsquigarrow;", "↝"}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 437 "HTMLCharacterReference.gperf" + {"PartialD;", "∂"}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 84 "HTMLCharacterReference.gperf" + {"CloseCurlyQuote;", "’"}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 989 "HTMLCharacterReference.gperf" + {"downharpoonleft;", "⇃"}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 2057 "HTMLCharacterReference.gperf" + {"thksim;", "∼"}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 1034 "HTMLCharacterReference.gperf" + {"emptyv;", "∅"}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 2033 "HTMLCharacterReference.gperf" + {"swarhk;", "⤦"}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 350 "HTMLCharacterReference.gperf" + {"NonBreakingSpace;", " "}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, #line 83 "HTMLCharacterReference.gperf" - {"CloseCurlyQuote;", "’"}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 988 "HTMLCharacterReference.gperf" - {"downharpoonleft;", "⇃"}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 2056 "HTMLCharacterReference.gperf" - {"thksim;", "∼"}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 1033 "HTMLCharacterReference.gperf" - {"emptyv;", "∅"}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 2032 "HTMLCharacterReference.gperf" - {"swarhk;", "⤦"}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 349 "HTMLCharacterReference.gperf" - {"NonBreakingSpace;", " "}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 82 "HTMLCharacterReference.gperf" - {"CloseCurlyDoubleQuote;", "”"}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 1722 "HTMLCharacterReference.gperf" - {"plusb;", "⊞"}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 1220 "HTMLCharacterReference.gperf" - {"imagline;", "ℐ"}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 383 "HTMLCharacterReference.gperf" - {"NotRightTriangle;", "⋫"}, - {""}, - {""}, + {"CloseCurlyDoubleQuote;", "”"}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 1723 "HTMLCharacterReference.gperf" + {"plusb;", "⊞"}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 1221 "HTMLCharacterReference.gperf" + {"imagline;", "ℐ"}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, #line 384 "HTMLCharacterReference.gperf" - {"NotRightTriangleBar;", "⧐̸"}, - {""}, + {"NotRightTriangle;", "⋫"}, + {""}, + {""}, #line 385 "HTMLCharacterReference.gperf" - {"NotRightTriangleEqual;", "⋭"}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 2075 "HTMLCharacterReference.gperf" - {"triangle;", "▵"}, -#line 2079 "HTMLCharacterReference.gperf" - {"triangleq;", "≜"}, - {""}, - {""}, -#line 2077 "HTMLCharacterReference.gperf" - {"triangleleft;", "◃"}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, + {"NotRightTriangleBar;", "⧐̸"}, + {""}, +#line 386 "HTMLCharacterReference.gperf" + {"NotRightTriangleEqual;", "⋭"}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, #line 2076 "HTMLCharacterReference.gperf" - {"triangledown;", "▿"}, -#line 1842 "HTMLCharacterReference.gperf" - {"rightleftarrows;", "⇄"}, - {""}, - {""}, -#line 496 "HTMLCharacterReference.gperf" - {"RightVector;", "⇀"}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, + {"triangle;", "▵"}, +#line 2080 "HTMLCharacterReference.gperf" + {"triangleq;", "≜"}, + {""}, + {""}, #line 2078 "HTMLCharacterReference.gperf" - {"trianglelefteq;", "⊴"}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 483 "HTMLCharacterReference.gperf" - {"RightDownVector;", "⇂"}, - {""}, - {""}, -#line 484 "HTMLCharacterReference.gperf" - {"RightDownVectorBar;", "⥕"}, - {""}, - {""}, - {""}, -#line 1725 "HTMLCharacterReference.gperf" - {"plusdu;", "⨥"}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 393 "HTMLCharacterReference.gperf" - {"NotSucceedsEqual;", "⪰̸"}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 1724 "HTMLCharacterReference.gperf" - {"plusdo;", "∔"}, - {""}, - {""}, - {""}, - {""}, -#line 382 "HTMLCharacterReference.gperf" - {"NotReverseElement;", "∌"}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 395 "HTMLCharacterReference.gperf" - {"NotSucceedsTilde;", "≿̸"}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, + {"triangleleft;", "◃"}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 2077 "HTMLCharacterReference.gperf" + {"triangledown;", "▿"}, #line 1843 "HTMLCharacterReference.gperf" - {"rightleftharpoons;", "⇌"}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 1936 "HTMLCharacterReference.gperf" - {"smeparsl;", "⧤"}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 421 "HTMLCharacterReference.gperf" - {"OpenCurlyDoubleQuote;", "“"}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 1974 "HTMLCharacterReference.gperf" - {"straightphi;", "ϕ"}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 1900 "HTMLCharacterReference.gperf" - {"searhk;", "⤥"}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 1841 "HTMLCharacterReference.gperf" - {"rightharpoonup;", "⇀"}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 1666 "HTMLCharacterReference.gperf" - {"omid;", "⦶"}, - {""}, - {""}, - {""}, - {""}, -#line 418 "HTMLCharacterReference.gperf" - {"Omega;", "Ω"}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 1992 "HTMLCharacterReference.gperf" - {"subsub;", "⫕"}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 367 "HTMLCharacterReference.gperf" - {"NotHumpEqual;", "≏̸"}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 1853 "HTMLCharacterReference.gperf" - {"rmoustache;", "⎱"}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 1715 "HTMLCharacterReference.gperf" - {"pitchfork;", "⋔"}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 2080 "HTMLCharacterReference.gperf" - {"triangleright;", "▹"}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 1830 "HTMLCharacterReference.gperf" - {"rfisht;", "⥽"}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 1840 "HTMLCharacterReference.gperf" - {"rightharpoondown;", "⇁"}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 407 "HTMLCharacterReference.gperf" - {"OElig;", "Œ"}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 304 "HTMLCharacterReference.gperf" - {"LessFullEqual;", "≦"}, - {""}, + {"rightleftarrows;", "⇄"}, + {""}, + {""}, +#line 497 "HTMLCharacterReference.gperf" + {"RightVector;", "⇀"}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 2079 "HTMLCharacterReference.gperf" + {"trianglelefteq;", "⊴"}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 484 "HTMLCharacterReference.gperf" + {"RightDownVector;", "⇂"}, + {""}, + {""}, #line 485 "HTMLCharacterReference.gperf" - {"RightFloor;", "⌋"}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 1700 "HTMLCharacterReference.gperf" - {"parsim;", "⫳"}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 1643 "HTMLCharacterReference.gperf" - {"odash;", "⊝"}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 1717 "HTMLCharacterReference.gperf" - {"planck;", "ℏ"}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 2081 "HTMLCharacterReference.gperf" - {"trianglerighteq;", "⊵"}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 2109 "HTMLCharacterReference.gperf" - {"ufisht;", "⥾"}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 2016 "HTMLCharacterReference.gperf" - {"suphsol;", "⟉"}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 1442 "HTMLCharacterReference.gperf" - {"measuredangle;", "∡"}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 2059 "HTMLCharacterReference.gperf" - {"tilde;", "˜"}, - {""}, + {"RightDownVectorBar;", "⥕"}, + {""}, + {""}, + {""}, +#line 1726 "HTMLCharacterReference.gperf" + {"plusdu;", "⨥"}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 394 "HTMLCharacterReference.gperf" + {"NotSucceedsEqual;", "⪰̸"}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 1725 "HTMLCharacterReference.gperf" + {"plusdo;", "∔"}, + {""}, + {""}, + {""}, + {""}, +#line 383 "HTMLCharacterReference.gperf" + {"NotReverseElement;", "∌"}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 396 "HTMLCharacterReference.gperf" + {"NotSucceedsTilde;", "≿̸"}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 1844 "HTMLCharacterReference.gperf" + {"rightleftharpoons;", "⇌"}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 1937 "HTMLCharacterReference.gperf" + {"smeparsl;", "⧤"}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 422 "HTMLCharacterReference.gperf" + {"OpenCurlyDoubleQuote;", "“"}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 1975 "HTMLCharacterReference.gperf" + {"straightphi;", "ϕ"}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 1901 "HTMLCharacterReference.gperf" + {"searhk;", "⤥"}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 1842 "HTMLCharacterReference.gperf" + {"rightharpoonup;", "⇀"}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, #line 1667 "HTMLCharacterReference.gperf" - {"ominus;", "⊖"}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 2121 "HTMLCharacterReference.gperf" - {"uml", "¨"}, - {""}, - {""}, - {""}, - {""}, - {""}, + {"omid;", "⦶"}, + {""}, + {""}, + {""}, + {""}, +#line 419 "HTMLCharacterReference.gperf" + {"Omega;", "Ω"}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 1993 "HTMLCharacterReference.gperf" + {"subsub;", "⫕"}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 368 "HTMLCharacterReference.gperf" + {"NotHumpEqual;", "≏̸"}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 1854 "HTMLCharacterReference.gperf" + {"rmoustache;", "⎱"}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 1716 "HTMLCharacterReference.gperf" + {"pitchfork;", "⋔"}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 2081 "HTMLCharacterReference.gperf" + {"triangleright;", "▹"}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 1831 "HTMLCharacterReference.gperf" + {"rfisht;", "⥽"}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 1841 "HTMLCharacterReference.gperf" + {"rightharpoondown;", "⇁"}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 408 "HTMLCharacterReference.gperf" + {"OElig;", "Œ"}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 305 "HTMLCharacterReference.gperf" + {"LessFullEqual;", "≦"}, + {""}, +#line 486 "HTMLCharacterReference.gperf" + {"RightFloor;", "⌋"}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 1701 "HTMLCharacterReference.gperf" + {"parsim;", "⫳"}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 1644 "HTMLCharacterReference.gperf" + {"odash;", "⊝"}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 1718 "HTMLCharacterReference.gperf" + {"planck;", "ℏ"}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 2082 "HTMLCharacterReference.gperf" + {"trianglerighteq;", "⊵"}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 2110 "HTMLCharacterReference.gperf" + {"ufisht;", "⥾"}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 2017 "HTMLCharacterReference.gperf" + {"suphsol;", "⟉"}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 1443 "HTMLCharacterReference.gperf" + {"measuredangle;", "∡"}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 2060 "HTMLCharacterReference.gperf" + {"tilde;", "˜"}, + {""}, +#line 1668 "HTMLCharacterReference.gperf" + {"ominus;", "⊖"}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, #line 2122 "HTMLCharacterReference.gperf" - {"uml;", "¨"}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 165 "HTMLCharacterReference.gperf" - {"EmptySmallSquare;", "◻"}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 495 "HTMLCharacterReference.gperf" - {"RightUpVectorBar;", "⥔"}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 182 "HTMLCharacterReference.gperf" - {"FilledSmallSquare;", "◼"}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 1838 "HTMLCharacterReference.gperf" - {"rightarrow;", "→"}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 1664 "HTMLCharacterReference.gperf" - {"omega;", "ω"}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 2189 "HTMLCharacterReference.gperf" - {"vzigzag;", "⦚"}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 1844 "HTMLCharacterReference.gperf" - {"rightrightarrows;", "⇉"}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, + {"uml", "¨"}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 2123 "HTMLCharacterReference.gperf" + {"uml;", "¨"}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 166 "HTMLCharacterReference.gperf" + {"EmptySmallSquare;", "◻"}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 496 "HTMLCharacterReference.gperf" + {"RightUpVectorBar;", "⥔"}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 183 "HTMLCharacterReference.gperf" + {"FilledSmallSquare;", "◼"}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 1839 "HTMLCharacterReference.gperf" + {"rightarrow;", "→"}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 1665 "HTMLCharacterReference.gperf" + {"omega;", "ω"}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 2190 "HTMLCharacterReference.gperf" + {"vzigzag;", "⦚"}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 1845 "HTMLCharacterReference.gperf" + {"rightrightarrows;", "⇉"}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 243 "HTMLCharacterReference.gperf" + {"InvisibleTimes;", "⁢"}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 341 "HTMLCharacterReference.gperf" + {"NegativeMediumSpace;", "​"}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 442 "HTMLCharacterReference.gperf" + {"PlusMinus;", "±"}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 114 "HTMLCharacterReference.gperf" + {"DiacriticalTilde;", "˜"}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 110 "HTMLCharacterReference.gperf" + {"DiacriticalAcute;", "´"}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 309 "HTMLCharacterReference.gperf" + {"LessTilde;", "≲"}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 1798 "HTMLCharacterReference.gperf" + {"rarrhk;", "↪"}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 2095 "HTMLCharacterReference.gperf" + {"twoheadleftarrow;", "↞"}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 1757 "HTMLCharacterReference.gperf" + {"profline;", "⌒"}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 1722 "HTMLCharacterReference.gperf" + {"plusacir;", "⨣"}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 490 "HTMLCharacterReference.gperf" + {"RightTriangle;", "⊳"}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 1936 "HTMLCharacterReference.gperf" + {"smashp;", "⨳"}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, #line 242 "HTMLCharacterReference.gperf" - {"InvisibleTimes;", "⁢"}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 340 "HTMLCharacterReference.gperf" - {"NegativeMediumSpace;", "​"}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 441 "HTMLCharacterReference.gperf" - {"PlusMinus;", "±"}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 113 "HTMLCharacterReference.gperf" - {"DiacriticalTilde;", "˜"}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 109 "HTMLCharacterReference.gperf" - {"DiacriticalAcute;", "´"}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 308 "HTMLCharacterReference.gperf" - {"LessTilde;", "≲"}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 1797 "HTMLCharacterReference.gperf" - {"rarrhk;", "↪"}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 2094 "HTMLCharacterReference.gperf" - {"twoheadleftarrow;", "↞"}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 1756 "HTMLCharacterReference.gperf" - {"profline;", "⌒"}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 1721 "HTMLCharacterReference.gperf" - {"plusacir;", "⨣"}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 489 "HTMLCharacterReference.gperf" - {"RightTriangle;", "⊳"}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 1935 "HTMLCharacterReference.gperf" - {"smashp;", "⨳"}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 241 "HTMLCharacterReference.gperf" - {"InvisibleComma;", "⁣"}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 1973 "HTMLCharacterReference.gperf" - {"straightepsilon;", "ϵ"}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 480 "HTMLCharacterReference.gperf" - {"RightCeiling;", "⌉"}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 561 "HTMLCharacterReference.gperf" - {"TildeEqual;", "≃"}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 497 "HTMLCharacterReference.gperf" - {"RightVectorBar;", "⥓"}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 563 "HTMLCharacterReference.gperf" - {"TildeTilde;", "≈"}, - {""}, - {""}, + {"InvisibleComma;", "⁣"}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 1974 "HTMLCharacterReference.gperf" + {"straightepsilon;", "ϵ"}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, #line 481 "HTMLCharacterReference.gperf" - {"RightDoubleBracket;", "⟧"}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 112 "HTMLCharacterReference.gperf" - {"DiacriticalGrave;", "`"}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 183 "HTMLCharacterReference.gperf" - {"FilledVerySmallSquare;", "▪"}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 1718 "HTMLCharacterReference.gperf" - {"planckh;", "ℎ"}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 1729 "HTMLCharacterReference.gperf" - {"plussim;", "⨦"}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 1723 "HTMLCharacterReference.gperf" - {"pluscir;", "⨢"}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 166 "HTMLCharacterReference.gperf" - {"EmptyVerySmallSquare;", "▫"}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 1878 "HTMLCharacterReference.gperf" - {"ruluhar;", "⥨"}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 594 "HTMLCharacterReference.gperf" - {"UpEquilibrium;", "⥮"}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 504 "HTMLCharacterReference.gperf" - {"RuleDelayed;", "⧴"}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, + {"RightCeiling;", "⌉"}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, #line 562 "HTMLCharacterReference.gperf" - {"TildeFullEqual;", "≅"}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 1839 "HTMLCharacterReference.gperf" - {"rightarrowtail;", "↣"}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, + {"TildeEqual;", "≃"}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 498 "HTMLCharacterReference.gperf" + {"RightVectorBar;", "⥓"}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 564 "HTMLCharacterReference.gperf" + {"TildeTilde;", "≈"}, + {""}, + {""}, +#line 482 "HTMLCharacterReference.gperf" + {"RightDoubleBracket;", "⟧"}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 113 "HTMLCharacterReference.gperf" + {"DiacriticalGrave;", "`"}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 184 "HTMLCharacterReference.gperf" + {"FilledVerySmallSquare;", "▪"}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 1719 "HTMLCharacterReference.gperf" + {"planckh;", "ℎ"}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 1730 "HTMLCharacterReference.gperf" + {"plussim;", "⨦"}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 1724 "HTMLCharacterReference.gperf" + {"pluscir;", "⨢"}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 167 "HTMLCharacterReference.gperf" + {"EmptyVerySmallSquare;", "▫"}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 1879 "HTMLCharacterReference.gperf" + {"ruluhar;", "⥨"}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 595 "HTMLCharacterReference.gperf" + {"UpEquilibrium;", "⥮"}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 505 "HTMLCharacterReference.gperf" + {"RuleDelayed;", "⧴"}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 563 "HTMLCharacterReference.gperf" + {"TildeFullEqual;", "≅"}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 1840 "HTMLCharacterReference.gperf" + {"rightarrowtail;", "↣"}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 492 "HTMLCharacterReference.gperf" + {"RightTriangleEqual;", "⊵"}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 1847 "HTMLCharacterReference.gperf" + {"rightthreetimes;", "⋌"}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, #line 491 "HTMLCharacterReference.gperf" - {"RightTriangleEqual;", "⊵"}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 1846 "HTMLCharacterReference.gperf" - {"rightthreetimes;", "⋌"}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 490 "HTMLCharacterReference.gperf" - {"RightTriangleBar;", "⧐"}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 2088 "HTMLCharacterReference.gperf" - {"trpezium;", "⏢"}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, - {""}, -#line 1819 "HTMLCharacterReference.gperf" - {"rdldhar;", "⥩"}}; + {"RightTriangleBar;", "⧐"}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 2089 "HTMLCharacterReference.gperf" + {"trpezium;", "⏢"}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, + {""}, +#line 1820 "HTMLCharacterReference.gperf" + {"rdldhar;", "⥩"}}; +const struct NameAndGlyph * HTMLCharacterHash::Lookup(const char * str, size_t len) +{ if (len <= MAX_WORD_LENGTH && len >= MIN_WORD_LENGTH) { unsigned int key = hash(str, len); @@ -17872,6 +17861,29 @@ const struct NameAndGlyph * HTMLCharacterHash::Lookup(const char * str, size_t l } return 0; } -#line 2252 "HTMLCharacterReference.gperf" +#line 2253 "HTMLCharacterReference.gperf" + + +[[maybe_unused]] static constexpr bool html_entity_expansion_within_6_5 = []() constexpr +{ + for (const auto & entry : wordlist) + { + if (entry.glyph == nullptr) + continue; + size_t name_len = 0; + while (entry.name[name_len] != '\0') + ++name_len; + size_t glyph_len = 0; + while (entry.glyph[glyph_len] != '\0') + ++glyph_len; + if (glyph_len * 5 > (name_len + 1) * 6) + return false; + } + return true; +}(); +static_assert( + html_entity_expansion_within_6_5, + "An HTML entity decodes to more than 6/5 of its byte length; " + "update the output buffer size in decodeHTMLComponent.cpp"); // NOLINTEND(google-runtime-int,hicpp-use-nullptr,modernize-use-nullptr,modernize-macro-to-enum) diff --git a/src/Functions/HTMLCharacterReference.gperf b/src/Functions/HTMLCharacterReference.gperf index c2ff4505832c..36c3e0785c7d 100644 --- a/src/Functions/HTMLCharacterReference.gperf +++ b/src/Functions/HTMLCharacterReference.gperf @@ -4,6 +4,7 @@ %readonly-tables %includes %compare-strncmp +%global-table %{ #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wimplicit-fallthrough" @@ -2250,4 +2251,27 @@ const char *glyph; "zwj;", "‍" "zwnj;", "‌" %% + +[[maybe_unused]] static constexpr bool html_entity_expansion_within_6_5 = []() constexpr +{ + for (const auto & entry : wordlist) + { + if (entry.glyph == nullptr) + continue; + size_t name_len = 0; + while (entry.name[name_len] != '\0') + ++name_len; + size_t glyph_len = 0; + while (entry.glyph[glyph_len] != '\0') + ++glyph_len; + if (glyph_len * 5 > (name_len + 1) * 6) + return false; + } + return true; +}(); +static_assert( + html_entity_expansion_within_6_5, + "An HTML entity decodes to more than 6/5 of its byte length; " + "update the output buffer size in decodeHTMLComponent.cpp"); + // NOLINTEND(google-runtime-int,hicpp-use-nullptr,modernize-use-nullptr,modernize-macro-to-enum) diff --git a/src/Functions/HTMLCharacterReference.sh b/src/Functions/HTMLCharacterReference.sh index 3d90a90b6bcb..76259b5cbc8d 100755 --- a/src/Functions/HTMLCharacterReference.sh +++ b/src/Functions/HTMLCharacterReference.sh @@ -6,6 +6,7 @@ echo '%language=C++ %readonly-tables %includes %compare-strncmp +%global-table %{ #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wimplicit-fallthrough" @@ -24,4 +25,35 @@ const char *glyph; # character reference as available at https://html.spec.whatwg.org/multipage/named-characters.html curl -X GET https://html.spec.whatwg.org/entities.json | jq -r 'keys[] as $k | "\"\($k)\", \(.[$k] | .characters|tojson)"' | sed 's/^"&/"/' >> HTMLCharacterReference.gperf echo '%%' >> HTMLCharacterReference.gperf + +# decodeHTMLComponent sizes its output buffer assuming an entity decodes to at most 6/5 of the bytes +# it occupies in the input ('&' + name, where name includes the trailing ';'). `≫⃒`/`≪⃒` +# (5 bytes -> 6-byte UTF-8) hit that bound exactly. Verify it at compile time over the whole table so +# a newer entity list cannot silently break the buffer sizing. (`%global-table` puts `wordlist` at +# file scope so this can reference it; the CMake target makes it `constexpr`.) +cat >> HTMLCharacterReference.gperf <<'CPP' + +[[maybe_unused]] static constexpr bool html_entity_expansion_within_6_5 = []() constexpr +{ + for (const auto & entry : wordlist) + { + if (entry.glyph == nullptr) + continue; + size_t name_len = 0; + while (entry.name[name_len] != '\0') + ++name_len; + size_t glyph_len = 0; + while (entry.glyph[glyph_len] != '\0') + ++glyph_len; + if (glyph_len * 5 > (name_len + 1) * 6) + return false; + } + return true; +}(); +static_assert( + html_entity_expansion_within_6_5, + "An HTML entity decodes to more than 6/5 of its byte length; " + "update the output buffer size in decodeHTMLComponent.cpp"); +CPP + echo '// NOLINTEND(google-runtime-int,hicpp-use-nullptr,modernize-use-nullptr,modernize-macro-to-enum)' >> HTMLCharacterReference.gperf diff --git a/src/Functions/decodeHTMLComponent.cpp b/src/Functions/decodeHTMLComponent.cpp index 97012902c5c4..a1252689c8bc 100644 --- a/src/Functions/decodeHTMLComponent.cpp +++ b/src/Functions/decodeHTMLComponent.cpp @@ -31,10 +31,13 @@ namespace ColumnString::Offsets & res_offsets, size_t input_rows_count) { - /// The size of result is always not more than the size of source. - /// Because entities decodes to the shorter byte sequence. - /// Example: &#xx... &#xx... will decode to UTF-8 byte sequence not longer than 4 bytes. - res_data.resize(data.size()); + /// Most entities decode to a byte sequence not longer than the entity itself, but a few + /// expand: `≫⃒` and `≪⃒` are 5 bytes and decode to a 6-byte UTF-8 sequence + /// (U+226B/U+226A followed by the combining U+20D2). That is the largest expansion among + /// all entities, so the worst case is 6/5 of the input. Size the buffer accordingly (plus + /// a small constant for the up-to-15-byte overwrite of memcpySmallAllowReadWriteOverflow15 + /// at the end of the buffer) to avoid a heap buffer overflow. + res_data.resize(data.size() + data.size() / 5 + 64); res_offsets.resize(input_rows_count); diff --git a/tests/queries/0_stateless/04325_decode_html_component_expanding_entities.reference b/tests/queries/0_stateless/04325_decode_html_component_expanding_entities.reference new file mode 100644 index 000000000000..d3f4627737e2 --- /dev/null +++ b/tests/queries/0_stateless/04325_decode_html_component_expanding_entities.reference @@ -0,0 +1,5 @@ +1200000 +1200000 +E289ABE28392 +E289AAE28392 +
Hello & "World"
diff --git a/tests/queries/0_stateless/04325_decode_html_component_expanding_entities.sql b/tests/queries/0_stateless/04325_decode_html_component_expanding_entities.sql new file mode 100644 index 000000000000..1bdf7c9e2f6d --- /dev/null +++ b/tests/queries/0_stateless/04325_decode_html_component_expanding_entities.sql @@ -0,0 +1,14 @@ +-- Regression test for a heap buffer overflow in decodeHTMLComponent. +-- The output buffer was sized to the input length, but the entities `≫⃒` and `≪⃒` are 5 bytes +-- and decode to a 6-byte UTF-8 sequence, so a long run of them overflowed the output buffer. + +-- A large run of the expanding entity must decode without overflowing (6 bytes per 5-byte entity). +SELECT length(decodeHTMLComponent(repeat('≫⃒', 200000))); +SELECT length(decodeHTMLComponent(repeat('≪⃒', 200000))); + +-- Correctness of the expanding entities (U+226B/U+226A + combining U+20D2). +SELECT hex(decodeHTMLComponent('≫⃒')); +SELECT hex(decodeHTMLComponent('≪⃒')); + +-- Ordinary entities still decode correctly. +SELECT decodeHTMLComponent('<div>Hello & "World"</div>'); From b7834bedc1775cb224760c90c7ed7f1ada365ace Mon Sep 17 00:00:00 2001 From: robot-clickhouse Date: Tue, 9 Jun 2026 17:14:20 +0000 Subject: [PATCH 016/146] Backport #99085 to 26.3: setting to control type mismatch behavior for variant and dynamic --- docs/en/sql-reference/data-types/dynamic.md | 29 ++++++++ docs/en/sql-reference/data-types/variant.md | 29 ++++++++ .../BuzzHouse/Generator/SessionSettings.cpp | 2 + src/Core/Settings.cpp | 12 ++++ src/Core/SettingsChangesHistory.cpp | 42 +++++++++++ src/Functions/FunctionDynamicAdaptor.cpp | 70 ++++++++++++++++++- src/Functions/FunctionDynamicAdaptor.h | 10 +-- src/Functions/FunctionVariantAdaptor.cpp | 66 ++++++++++++++++- src/Functions/FunctionVariantAdaptor.h | 10 +-- ...t_dynamic_type_mismatch_settings.reference | 6 ++ ...variant_dynamic_type_mismatch_settings.sql | 37 ++++++++++ 11 files changed, 299 insertions(+), 14 deletions(-) create mode 100644 tests/queries/0_stateless/04101_variant_dynamic_type_mismatch_settings.reference create mode 100644 tests/queries/0_stateless/04101_variant_dynamic_type_mismatch_settings.sql diff --git a/docs/en/sql-reference/data-types/dynamic.md b/docs/en/sql-reference/data-types/dynamic.md index ad6d8d21c6fe..d61a4e55aa1b 100644 --- a/docs/en/sql-reference/data-types/dynamic.md +++ b/docs/en/sql-reference/data-types/dynamic.md @@ -503,6 +503,35 @@ SELECT d, d.Int64 + 1 AS res, toTypeName(res) FROM test; └───────┴──────┴─────────────────┘ ``` +### Type mismatch behavior {#dynamic-type-mismatch-behavior} + +The setting `dynamic_throw_on_type_mismatch` controls what happens when a function is applied to a `Dynamic` column and the actual stored type of a row is incompatible with the function: + +- `true` (default) — throw an exception (`ILLEGAL_TYPE_OF_ARGUMENT`) on the first incompatible row. +- `false` — return `NULL` for incompatible rows and keep the result for compatible rows. + +**Example:** + +```sql +CREATE TABLE test (d Dynamic) ENGINE = Memory; +INSERT INTO test VALUES ('world'), (123), (456); + +-- Default (throw on mismatch): length() does not accept integers, so the query throws. +SELECT length(d) FROM test; -- throws ILLEGAL_TYPE_OF_ARGUMENT + +-- With throw disabled: incompatible rows return NULL. +SET dynamic_throw_on_type_mismatch = false; +SELECT d, length(d) FROM test ORDER BY d::String NULLS LAST; +``` + +```text +┌─d─────┬─length(d)─┐ +│ world │ 5 │ +│ 123 │ ᴺᵁᴸᴸ │ +│ 456 │ ᴺᵁᴸᴸ │ +└───────┴───────────┘ +``` + ## Using Dynamic type in ORDER BY and GROUP BY {#using-dynamic-type-in-order-by-and-group-by} During `ORDER BY` and `GROUP BY` values of `Dynamic` types are compared similar to values of `Variant` type: diff --git a/docs/en/sql-reference/data-types/variant.md b/docs/en/sql-reference/data-types/variant.md index febf9bf05e35..0f97f7f4510c 100644 --- a/docs/en/sql-reference/data-types/variant.md +++ b/docs/en/sql-reference/data-types/variant.md @@ -558,3 +558,32 @@ The result type depends on what the function returns for each variant: TYPE_MISMATCH, CANNOT_CONVERT_TYPE, NO_COMMON_TYPE) are caught and result in NULL for those rows. Other errors like division by zero or out of memory are raised normally to prevent silently hiding real problems. ::: + +### Type mismatch behavior {#variant-type-mismatch-behavior} + +The setting `variant_throw_on_type_mismatch` controls what happens when a function is applied to a `Variant` column and the actual stored type of a row is incompatible with the function: + +- `true` (default) — throw an exception (`ILLEGAL_TYPE_OF_ARGUMENT`) on the first incompatible row. +- `false` — return `NULL` for incompatible rows and keep the result for compatible rows. + +**Example:** + +```sql +CREATE TABLE test (v Variant(String, UInt64)) ENGINE = Memory; +INSERT INTO test VALUES ('hello'), (42), ('foo'); + +-- Default (throw on mismatch): length() does not accept UInt64, so the query throws. +SELECT length(v) FROM test; -- throws ILLEGAL_TYPE_OF_ARGUMENT + +-- With throw disabled: incompatible rows return NULL. +SET variant_throw_on_type_mismatch = false; +SELECT v, length(v) FROM test ORDER BY v::String NULLS LAST; +``` + +```text +┌─v─────┬─length(v)─┐ +│ foo │ 3 │ +│ hello │ 5 │ +│ 42 │ ᴺᵁᴸᴸ │ +└───────┴───────────┘ +``` diff --git a/src/Client/BuzzHouse/Generator/SessionSettings.cpp b/src/Client/BuzzHouse/Generator/SessionSettings.cpp index 107566db278e..6c626db7f140 100644 --- a/src/Client/BuzzHouse/Generator/SessionSettings.cpp +++ b/src/Client/BuzzHouse/Generator/SessionSettings.cpp @@ -1306,6 +1306,8 @@ static std::unordered_map serverSettings2 = { {"use_text_index_postings_cache", trueOrFalseSetting}, {"use_variant_as_common_type", CHSetting(trueOrFalse, {"0", "1"}, true)}, {"use_variant_default_implementation_for_comparisons", trueOrFalseSettingNoOracle}, + {"variant_throw_on_type_mismatch", trueOrFalseSettingNoOracle}, + {"dynamic_throw_on_type_mismatch", trueOrFalseSettingNoOracle}, {"use_with_fill_by_sorting_prefix", trueOrFalseSetting}, {"validate_enum_literals_in_operators", trueOrFalseSettingNoOracle}, {"validate_experimental_and_suspicious_types_inside_nested_types", trueOrFalseSettingNoOracle}, diff --git a/src/Core/Settings.cpp b/src/Core/Settings.cpp index 1658193477e0..6d7ec3124d28 100644 --- a/src/Core/Settings.cpp +++ b/src/Core/Settings.cpp @@ -1042,6 +1042,18 @@ Allows or restricts using [Variant](../../sql-reference/data-types/variant.md) a )", 0) \ DECLARE(Bool, use_variant_default_implementation_for_comparisons, true, R"( Enables or disables default implementation for Variant type in comparison functions. +)", 0) \ + DECLARE(Bool, variant_throw_on_type_mismatch, true, R"( +When applying a function to a [Variant](../../sql-reference/data-types/variant.md) column using the default implementation, +controls what happens for rows whose actual type is incompatible with the function: +- `true` (default) — throw an exception. +- `false` — return `NULL` for those rows instead. +)", 0) \ + DECLARE(Bool, dynamic_throw_on_type_mismatch, true, R"( +When applying a function to a [Dynamic](../../sql-reference/data-types/dynamic.md) column using the default implementation, +controls what happens for rows whose actual type is incompatible with the function: +- `true` (default) — throw an exception. +- `false` — return `NULL` for those rows instead. )", 0) \ DECLARE(Bool, compile_expressions, true, R"( Compile some scalar functions and operators to native code. diff --git a/src/Core/SettingsChangesHistory.cpp b/src/Core/SettingsChangesHistory.cpp index 251d4e54972e..4b2a59863880 100644 --- a/src/Core/SettingsChangesHistory.cpp +++ b/src/Core/SettingsChangesHistory.cpp @@ -39,6 +39,48 @@ const VersionToSettingsChangesMap & getSettingsChangesHistory() /// controls new feature and it's 'true' by default, use 'false' as previous_value). /// It's used to implement `compatibility` setting (see https://github.com/ClickHouse/ClickHouse/issues/35972) /// Note: please check if the key already exists to prevent duplicate entries. + addSettingsChanges(settings_changes_history, "26.4", + { + {"allow_iceberg_remove_orphan_files", false, false, "New setting to gate Iceberg orphan file removal"}, + {"iceberg_orphan_files_older_than_seconds", 259200, 259200, "New setting for default orphan file age threshold"}, + {"output_format_arrow_unsupported_types_as_binary", false, true, "New setting to convert unsupported CH types to arrow binary instead of UNKNOWN_TYPE exception."}, + {"output_format_parquet_unsupported_types_as_binary", false, false, "New setting to convert unsupported CH types to parquet (arrow) binary instead of UNKNOWN_TYPE exception."}, + {"asterisk_include_virtual_columns", false, false, "New setting"}, + {"max_wkb_geometry_elements", 1'000'000, 1'000'000, "New setting to limit element counts in WKB geometry parsing, preventing excessive memory allocation on malformed data."}, + {"max_rand_distribution_trials", 1'000'000'000, 1'000'000'000, "New setting to limit trial counts in random distribution functions, preventing hangs with extreme inputs."}, + {"max_rand_distribution_parameter", 1e6, 1e6, "New setting to limit shape parameters in random distribution functions, preventing hangs with extreme inputs."}, + {"optimize_truncate_order_by_after_group_by_keys", false, true, "Remove trailing ORDER BY elements once all GROUP BY keys are covered in the ORDER BY prefix."}, + {"use_statistics_for_part_pruning", false, true, "New setting to use statistics for part pruning during query execution."}, + {"distributed_index_analysis_only_on_coordinator", false, false, "New setting."}, + {"query_plan_optimize_join_order_randomize", 0, 0, "New setting to randomize join order statistics for testing."}, + {"enable_materialized_cte", false, false, "New setting"}, + {"use_strict_insert_block_limits", false, false, "New setting to use strict min and max insert bounds on inserts. When min < max, max limits take precedence."}, + {"finalize_projection_parts_synchronously", false, false, "New setting to finalize projection parts synchronously during INSERT to reduce peak memory usage."}, + {"parallel_replicas_allow_view_over_mergetree", false, false, "New setting"}, + {"read_in_order_use_virtual_row_per_block", false, false, "Emit virtual row after each block during read-in-order to allow more frequent source reprioritization in MergingSortedTransform."}, + {"distributed_plan_prefer_replicas_over_workers", false, false, "New setting to serialize distributed plan for replicas"}, + {"use_text_index_like_evaluation_by_dictionary_scan", true, true, "New setting"}, + {"text_index_like_min_pattern_length", 4, 4, "New setting"}, + {"text_index_like_max_postings_to_read", 50, 50, "New setting"}, + {"analyzer_inline_views", false, false, "New setting"}, + {"highlight_max_matches_per_row", 10000, 10000, "New setting to limit the number of highlight matches per row to protect against excessive memory usage."}, + {"materialize_statistics_on_insert", true, false, "Disable building statistics on INSERT by default, rely on merges instead"}, + {"enable_join_transitive_predicates", false, false, "New setting to infer transitive equi-join predicates for join order optimization."}, + {"enable_join_fixed_hash_table_conversion", false, true, "New setting to enable converting the hash table to a flat array for joins when the key is a single integer with a small value range."}, + {"allow_experimental_ai_functions", false, false, "New setting"}, + {"ai_function_request_timeout_sec", 60, 60, "New setting"}, + {"ai_function_max_retries", 0, 0, "New setting"}, + {"ai_function_retry_initial_delay_ms", 1000, 1000, "New setting"}, + {"ai_function_throw_on_error", true, true, "New setting"}, + {"ai_function_max_input_tokens_per_query", 1000000, 1000000, "New setting"}, + {"ai_function_max_output_tokens_per_query", 500000, 500000, "New setting"}, + {"ai_function_max_api_calls_per_query", 0, 0, "New setting"}, + {"ai_function_throw_on_quota_exceeded", true, true, "New setting"}, + {"materialize_statistics_on_insert", true, false, "Disable building statistics on INSERT by default, rely on merges instead"}, + {"enable_join_transitive_predicates", false, false, "New setting to infer transitive equi-join predicates for join order optimization."}, + {"variant_throw_on_type_mismatch", true, true, "New setting to control type mismatch behavior in default Variant implementation"}, + {"dynamic_throw_on_type_mismatch", true, true, "New setting to control type mismatch behavior in default Dynamic implementation"}, + }); addSettingsChanges(settings_changes_history, "26.3", { {"defer_partition_pruning_after_final", false, true, "Gates the FINAL planner's unconditional skipping of partition pruning when the partition-key column is not in the sorting key. The behavior change itself shipped silently in 26.3 via https://github.com/ClickHouse/ClickHouse/pull/98242; this entry retroactively documents it so `compatibility = '26.2'` restores the pre-regression behavior (0 = prune before FINAL, fast; 1 = defer pruning, correctness-safe)."}, diff --git a/src/Functions/FunctionDynamicAdaptor.cpp b/src/Functions/FunctionDynamicAdaptor.cpp index acd910b6c3dd..db5fde2b498a 100644 --- a/src/Functions/FunctionDynamicAdaptor.cpp +++ b/src/Functions/FunctionDynamicAdaptor.cpp @@ -1,4 +1,6 @@ #include +#include +#include #include #include #include @@ -7,15 +9,37 @@ #include #include #include +#include #include namespace DB { +namespace Setting +{ +extern const SettingsBool dynamic_throw_on_type_mismatch; +} namespace ErrorCodes { extern const int LOGICAL_ERROR; + extern const int ILLEGAL_TYPE_OF_ARGUMENT; + extern const int TYPE_MISMATCH; + extern const int CANNOT_CONVERT_TYPE; + extern const int NO_COMMON_TYPE; +} + +ExecutableFunctionDynamicAdaptor::ExecutableFunctionDynamicAdaptor( + std::shared_ptr function_overload_resolver_, + size_t dynamic_argument_index_) + : function_overload_resolver(std::move(function_overload_resolver_)) + , dynamic_argument_index(dynamic_argument_index_) +{ + if (CurrentThread::isInitialized()) + { + if (auto query_context = CurrentThread::tryGetQueryContext()) + throw_on_type_mismatch = query_context->getSettingsRef()[Setting::dynamic_throw_on_type_mismatch]; + } } ColumnPtr ExecutableFunctionDynamicAdaptor::executeImpl(const ColumnsWithTypeAndName & arguments, const DataTypePtr & result_type, size_t, bool dry_run) const @@ -39,6 +63,26 @@ ColumnPtr ExecutableFunctionDynamicAdaptor::executeImpl(const ColumnsWithTypeAnd return result; } + /// Helper: build function base for the given arguments, respecting throw_on_type_mismatch. + /// Returns nullptr if the type is incompatible and throwing is disabled; otherwise throws. + auto try_build = [&](const ColumnsWithTypeAndName & args) -> FunctionBasePtr + { + if (throw_on_type_mismatch) + return function_overload_resolver->build(args); + + try + { + return function_overload_resolver->build(args); + } + catch (const Exception & e) + { + if (e.code() != ErrorCodes::ILLEGAL_TYPE_OF_ARGUMENT && e.code() != ErrorCodes::TYPE_MISMATCH + && e.code() != ErrorCodes::CANNOT_CONVERT_TYPE && e.code() != ErrorCodes::NO_COMMON_TYPE) + throw; + return nullptr; + } + }; + /// Check if this Dynamic column contains only values of one type and no NULLs. /// In this case we can replace argument with this variant and execute the function without changing all other arguments. auto non_empty_variant_discr_no_nulls = variant_column.getGlobalDiscriminatorOfOneNoneEmptyVariantNoNulls(); @@ -68,7 +112,14 @@ ColumnPtr ExecutableFunctionDynamicAdaptor::executeImpl(const ColumnsWithTypeAnd } /// Execute function on new arguments. - auto func_base = function_overload_resolver->build(new_arguments); + auto func_base = try_build(new_arguments); + if (!func_base) + { + /// Type is incompatible and throw_on_type_mismatch is false — return NULLs for all rows. + auto res = result_type->createColumn(); + res->insertManyDefaults(dynamic_column.size()); + return res; + } auto nested_result_type = func_base->getResultType(); auto nested_result = func_base->execute(new_arguments, nested_result_type, dynamic_column.size(), dry_run); @@ -152,7 +203,14 @@ ColumnPtr ExecutableFunctionDynamicAdaptor::executeImpl(const ColumnsWithTypeAnd } /// Execute function on new arguments. - auto func_base = function_overload_resolver->build(new_arguments); + auto func_base = try_build(new_arguments); + if (!func_base) + { + /// Type is incompatible and throw_on_type_mismatch is false — return NULLs for all rows. + auto res = result_type->createColumn(); + res->insertManyDefaults(dynamic_column.size()); + return res; + } auto nested_result_type = func_base->getResultType(); auto nested_result = func_base->execute(new_arguments, nested_result_type, new_arguments[0].column->size(), dry_run)->convertToFullColumnIfConst(); @@ -383,7 +441,13 @@ ColumnPtr ExecutableFunctionDynamicAdaptor::executeImpl(const ColumnsWithTypeAnd variants_results.emplace_back(); for (size_t i = 1; i != variants_arguments.size(); ++i) { - auto func_base = function_overload_resolver->build(variants_arguments[i]); + auto func_base = try_build(variants_arguments[i]); + if (!func_base) + { + /// Type is incompatible and throw_on_type_mismatch is false — treat as NULL result. + variants_results.emplace_back(); + continue; + } auto nested_result_type = func_base->getResultType(); auto nested_result = func_base->execute(variants_arguments[i], nested_result_type, variants_arguments[i][0].column->size(), dry_run)->convertToFullColumnIfConst(); diff --git a/src/Functions/FunctionDynamicAdaptor.h b/src/Functions/FunctionDynamicAdaptor.h index 85ae83d746ab..8cbe35a367da 100644 --- a/src/Functions/FunctionDynamicAdaptor.h +++ b/src/Functions/FunctionDynamicAdaptor.h @@ -11,10 +11,8 @@ class ExecutableFunctionDynamicAdaptor final : public IExecutableFunction { public: explicit ExecutableFunctionDynamicAdaptor( - std::shared_ptr function_overload_resolver_, size_t dynamic_argument_index_) - : function_overload_resolver(std::move(function_overload_resolver_)), dynamic_argument_index(dynamic_argument_index_) - { - } + std::shared_ptr function_overload_resolver_, + size_t dynamic_argument_index_); String getName() const override { return function_overload_resolver->getName(); } @@ -36,6 +34,10 @@ class ExecutableFunctionDynamicAdaptor final : public IExecutableFunction /// We remember the original IFunctionOverloadResolver to be able to build function for types inside Dynamic column. std::shared_ptr function_overload_resolver; size_t dynamic_argument_index; + /// When true, throw an exception if a dynamic variant type is incompatible with the function. + /// When false (default), return NULL for incompatible rows instead. + /// Read from `dynamic_throw_on_type_mismatch` setting via CurrentThread at construction time. + bool throw_on_type_mismatch = true; }; class FunctionBaseDynamicAdaptor final : public IFunctionBase diff --git a/src/Functions/FunctionVariantAdaptor.cpp b/src/Functions/FunctionVariantAdaptor.cpp index 88476476e169..40d8ae85733d 100644 --- a/src/Functions/FunctionVariantAdaptor.cpp +++ b/src/Functions/FunctionVariantAdaptor.cpp @@ -1,4 +1,6 @@ +#include #include +#include #include #include #include @@ -8,10 +10,15 @@ #include #include #include +#include namespace DB { +namespace Setting +{ +extern const SettingsBool variant_throw_on_type_mismatch; +} namespace ErrorCodes { @@ -22,6 +29,19 @@ extern const int CANNOT_CONVERT_TYPE; extern const int NO_COMMON_TYPE; } +ExecutableFunctionVariantAdaptor::ExecutableFunctionVariantAdaptor( + std::shared_ptr function_overload_resolver_, + size_t variant_argument_index_) + : function_overload_resolver(std::move(function_overload_resolver_)) + , variant_argument_index(variant_argument_index_) +{ + if (CurrentThread::isInitialized()) + { + if (auto query_context = CurrentThread::tryGetQueryContext()) + throw_on_type_mismatch = query_context->getSettingsRef()[Setting::variant_throw_on_type_mismatch]; + } +} + /// Strip LowCardinality wrapper from nested function result if present. /// This is needed because the FunctionBaseVariantAdaptor constructor computes result types /// using nullptr columns (treated as non-const by getReturnType), while executeImpl uses @@ -48,6 +68,26 @@ ColumnPtr ExecutableFunctionVariantAdaptor::executeImpl( const auto & variant_type = assert_cast(*arguments[variant_argument_index].type); const auto & variant_types = variant_type.getVariants(); + /// Helper: build function base for the given arguments, respecting throw_on_type_mismatch. + /// Returns nullptr if the type is incompatible and throwing is disabled; otherwise throws. + auto try_build = [&](const ColumnsWithTypeAndName & args) -> FunctionBasePtr + { + if (throw_on_type_mismatch) + return function_overload_resolver->build(args); + + try + { + return function_overload_resolver->build(args); + } + catch (const Exception & e) + { + if (e.code() != ErrorCodes::ILLEGAL_TYPE_OF_ARGUMENT && e.code() != ErrorCodes::TYPE_MISMATCH + && e.code() != ErrorCodes::CANNOT_CONVERT_TYPE && e.code() != ErrorCodes::NO_COMMON_TYPE) + throw; + return nullptr; + } + }; + /// We use default implementation for Variant type only when default implementation for NULLs is used. /// If current column contains only NULLs, result column will also contain only NULLs. if (variant_column.hasOnlyNulls()) @@ -84,7 +124,14 @@ ColumnPtr ExecutableFunctionVariantAdaptor::executeImpl( } /// Execute function on new arguments. - auto func_base = function_overload_resolver->build(new_arguments); + auto func_base = try_build(new_arguments); + if (!func_base) + { + /// Type is incompatible and throw_on_type_mismatch is false — return NULLs for all rows. + auto res = result_type->createColumn(); + res->insertManyDefaults(variant_column.size()); + return res; + } DataTypePtr nested_result_type = func_base->getResultType(); ColumnPtr nested_result = func_base->execute(new_arguments, nested_result_type, variant_column.size(), dry_run); removeLowCardinalityFromResult(nested_result_type, nested_result); @@ -185,7 +232,14 @@ ColumnPtr ExecutableFunctionVariantAdaptor::executeImpl( } /// Execute function on new arguments. - auto func_base = function_overload_resolver->build(new_arguments); + auto func_base = try_build(new_arguments); + if (!func_base) + { + /// Type is incompatible and throw_on_type_mismatch is false — return NULLs for all rows. + auto res = result_type->createColumn(); + res->insertManyDefaults(variant_column.size()); + return res; + } DataTypePtr nested_result_type = func_base->getResultType(); ColumnPtr nested_result = func_base->execute(new_arguments, nested_result_type, new_arguments[0].column->size(), dry_run) ->convertToFullColumnIfConst(); @@ -360,7 +414,13 @@ ColumnPtr ExecutableFunctionVariantAdaptor::executeImpl( if (!variants[i].column) continue; - auto func_base = function_overload_resolver->build(variants_arguments[i]); + auto func_base = try_build(variants_arguments[i]); + if (!func_base) + { + /// Type is incompatible and throw_on_type_mismatch is false — treat as NULL result. + variants_results[i] = nullptr; + continue; + } auto nested_result_type = func_base->getResultType(); auto nested_result = func_base->execute(variants_arguments[i], nested_result_type, variants_arguments[i][0].column->size(), dry_run) diff --git a/src/Functions/FunctionVariantAdaptor.h b/src/Functions/FunctionVariantAdaptor.h index 1aa83c4a6bb1..47c6c5783e84 100644 --- a/src/Functions/FunctionVariantAdaptor.h +++ b/src/Functions/FunctionVariantAdaptor.h @@ -11,10 +11,8 @@ class ExecutableFunctionVariantAdaptor final : public IExecutableFunction { public: explicit ExecutableFunctionVariantAdaptor( - std::shared_ptr function_overload_resolver_, size_t variant_argument_index_) - : function_overload_resolver(std::move(function_overload_resolver_)), variant_argument_index(variant_argument_index_) - { - } + std::shared_ptr function_overload_resolver_, + size_t variant_argument_index_); String getName() const override { return function_overload_resolver->getName(); } @@ -36,6 +34,10 @@ class ExecutableFunctionVariantAdaptor final : public IExecutableFunction /// We remember the original IFunctionOverloadResolver to be able to build function for types inside Variant column. std::shared_ptr function_overload_resolver; size_t variant_argument_index; + /// When true (default), throw an exception if a variant type is incompatible with the function. + /// When false, return NULL for incompatible rows instead. + /// Read from `variant_throw_on_type_mismatch` setting via CurrentThread at construction time. + bool throw_on_type_mismatch = true; }; class FunctionBaseVariantAdaptor final : public IFunctionBase diff --git a/tests/queries/0_stateless/04101_variant_dynamic_type_mismatch_settings.reference b/tests/queries/0_stateless/04101_variant_dynamic_type_mismatch_settings.reference new file mode 100644 index 000000000000..83cd5f185e80 --- /dev/null +++ b/tests/queries/0_stateless/04101_variant_dynamic_type_mismatch_settings.reference @@ -0,0 +1,6 @@ +1 5 +2 \N +3 3 +1 5 +2 \N +3 \N diff --git a/tests/queries/0_stateless/04101_variant_dynamic_type_mismatch_settings.sql b/tests/queries/0_stateless/04101_variant_dynamic_type_mismatch_settings.sql new file mode 100644 index 000000000000..b3d09fcd0a49 --- /dev/null +++ b/tests/queries/0_stateless/04101_variant_dynamic_type_mismatch_settings.sql @@ -0,0 +1,37 @@ +-- Tests for variant_throw_on_type_mismatch and dynamic_throw_on_type_mismatch settings. +-- These settings control what happens when a function is applied to a Variant/Dynamic column +-- and the actual type of a row is incompatible with the function. +-- Default (true): throw an exception on mismatch. +-- When set to false: return NULL for incompatible rows instead. + +SET allow_experimental_variant_type = 1; +SET allow_experimental_dynamic_type = 1; +SET use_variant_as_common_type = 1; +SET allow_suspicious_variant_types = 1; + +DROP TABLE IF EXISTS t_type_mismatch; +CREATE TABLE t_type_mismatch +( + id UInt32, + v Variant(String, UInt64), + d Dynamic +) ENGINE = Memory; + +INSERT INTO t_type_mismatch VALUES (1, 'hello', 'world'), (2, 42, 123), (3, 'foo', 456); + +-- Default behavior (variant_throw_on_type_mismatch = true): throw on incompatible type. +-- length() works on String but not on UInt64. +SELECT length(v) FROM t_type_mismatch ORDER BY id; -- { serverError ILLEGAL_TYPE_OF_ARGUMENT } + +-- Default behavior (dynamic_throw_on_type_mismatch = true): same for Dynamic. +SELECT length(d) FROM t_type_mismatch ORDER BY id; -- { serverError ILLEGAL_TYPE_OF_ARGUMENT } + +-- With variant_throw_on_type_mismatch = false: return NULL for incompatible rows, keep compatible rows intact. +SET variant_throw_on_type_mismatch = false; +SELECT id, length(v) FROM t_type_mismatch ORDER BY id; + +-- With dynamic_throw_on_type_mismatch = false: same for Dynamic. +SET dynamic_throw_on_type_mismatch = false; +SELECT id, length(d) FROM t_type_mismatch ORDER BY id; + +DROP TABLE t_type_mismatch; From 3a94371fb08ac2217b1ba25bbf8096d798e19715 Mon Sep 17 00:00:00 2001 From: robot-clickhouse Date: Tue, 9 Jun 2026 19:02:28 +0000 Subject: [PATCH 017/146] Backport #106802 to 26.3: Fix inconsistent columns on exception (i.e. MEMORY_LIMIT_EXCEEDED) during parsing --- src/Columns/ColumnNullable.h | 12 +++++- src/Columns/ColumnObject.cpp | 55 +++++++++++++++++++----- src/Columns/ColumnTuple.cpp | 19 +++++++- src/Columns/ColumnVariant.cpp | 26 +++++++++-- src/Columns/tests/gtest_column_tuple.cpp | 40 +++++++++++++++++ 5 files changed, 135 insertions(+), 17 deletions(-) diff --git a/src/Columns/ColumnNullable.h b/src/Columns/ColumnNullable.h index ae5aedf65f3b..8570257d268c 100644 --- a/src/Columns/ColumnNullable.h +++ b/src/Columns/ColumnNullable.h @@ -95,7 +95,17 @@ class ColumnNullable final : public COWHelper, Col void insertDefault() override { getNestedColumn().insertDefault(); - getNullMapData().push_back(true); + /// Keep the nested column and the null map in sync even if appending to the + /// null map throws (e.g. on a memory limit). + try + { + getNullMapData().push_back(true); + } + catch (...) + { + getNestedColumn().popBack(1); + throw; + } } void popBack(size_t n) override; diff --git a/src/Columns/ColumnObject.cpp b/src/Columns/ColumnObject.cpp index df8aea6251e8..f8f2e5884b46 100644 --- a/src/Columns/ColumnObject.cpp +++ b/src/Columns/ColumnObject.cpp @@ -832,20 +832,55 @@ void ColumnObject::deserializeValueFromSharedData(const ColumnString * shared_da void ColumnObject::insertDefault() { - for (auto & [_, column] : typed_paths) - column->insertDefault(); - for (auto & [_, column] : dynamic_paths_ptrs) - column->insertDefault(); - shared_data->insertDefault(); + /// Exception-safe: if some sub-column's insertDefault throws (e.g. on a memory limit), + /// roll back the sub-columns that were already modified, otherwise the object is left + /// with sub-columns of different sizes and popBack would over-pop the shorter ones. + size_t prev_size = size(); + try + { + for (auto & [_, column] : typed_paths) + column->insertDefault(); + for (auto & [_, column] : dynamic_paths_ptrs) + column->insertDefault(); + shared_data->insertDefault(); + } + catch (...) + { + for (auto & [_, column] : typed_paths) + if (column->size() > prev_size) + column->popBack(column->size() - prev_size); + for (auto & [_, column] : dynamic_paths_ptrs) + if (column->size() > prev_size) + column->popBack(column->size() - prev_size); + if (shared_data->size() > prev_size) + shared_data->popBack(shared_data->size() - prev_size); + throw; + } } void ColumnObject::insertManyDefaults(size_t length) { - for (auto & [_, column] : typed_paths) - column->insertManyDefaults(length); - for (auto & [_, column] : dynamic_paths_ptrs) - column->insertManyDefaults(length); - shared_data->insertManyDefaults(length); + size_t prev_size = size(); + try + { + for (auto & [_, column] : typed_paths) + column->insertManyDefaults(length); + for (auto & [_, column] : dynamic_paths_ptrs) + column->insertManyDefaults(length); + shared_data->insertManyDefaults(length); + } + catch (...) + { + for (auto & [_, column] : typed_paths) + if (column->size() > prev_size) + column->popBack(column->size() - prev_size); + for (auto & [_, column] : dynamic_paths_ptrs) + if (column->size() > prev_size) + column->popBack(column->size() - prev_size); + if (shared_data->size() > prev_size) + shared_data->popBack(shared_data->size() - prev_size); + throw; + } } void ColumnObject::popBack(size_t n) diff --git a/src/Columns/ColumnTuple.cpp b/src/Columns/ColumnTuple.cpp index c7833b36ec4a..6286b45d848e 100644 --- a/src/Columns/ColumnTuple.cpp +++ b/src/Columns/ColumnTuple.cpp @@ -279,9 +279,24 @@ void ColumnTuple::doInsertManyFrom(const IColumn & src, size_t position, size_t void ColumnTuple::insertDefault() { + /// Must be exception-safe: if some nested column throws (e.g. on a memory limit), + /// the already modified nested columns have to be rolled back, otherwise the tuple + /// is left with nested columns of different sizes, which later leads to over-popping + /// in popBack during rollback of a partially read row. + size_t i = 0; + try + { + for (; i < columns.size(); ++i) + columns[i]->insertDefault(); + } + catch (...) + { + for (size_t rollback = 0; rollback < i; ++rollback) + columns[rollback]->popBack(1); + throw; + } + ++column_length; - for (auto & column : columns) - column->insertDefault(); } void ColumnTuple::popBack(size_t n) diff --git a/src/Columns/ColumnVariant.cpp b/src/Columns/ColumnVariant.cpp index a41a250a810a..aaaa4265e261 100644 --- a/src/Columns/ColumnVariant.cpp +++ b/src/Columns/ColumnVariant.cpp @@ -718,14 +718,32 @@ void ColumnVariant::deserializeBinaryIntoVariant(ColumnVariant::Discriminator gl void ColumnVariant::insertDefault() { getLocalDiscriminators().push_back(NULL_DISCRIMINATOR); - getOffsets().emplace_back(); + /// Keep local_discriminators and offsets in sync even if appending to offsets throws + /// (e.g. on a memory limit), otherwise popBack would over-pop the shorter one. + try + { + getOffsets().emplace_back(); + } + catch (...) + { + getLocalDiscriminators().pop_back(); + throw; + } } void ColumnVariant::insertManyDefaults(size_t length) { - size_t size = local_discriminators->size(); - getLocalDiscriminators().resize_fill(size + length, NULL_DISCRIMINATOR); - getOffsets().resize_fill(size + length); + size_t prev_size = local_discriminators->size(); + getLocalDiscriminators().resize_fill(prev_size + length, NULL_DISCRIMINATOR); + try + { + getOffsets().resize_fill(prev_size + length); + } + catch (...) + { + getLocalDiscriminators().resize_assume_reserved(prev_size); + throw; + } } void ColumnVariant::popBack(size_t n) diff --git a/src/Columns/tests/gtest_column_tuple.cpp b/src/Columns/tests/gtest_column_tuple.cpp index 7f3fd657a39a..805c07dcc505 100644 --- a/src/Columns/tests/gtest_column_tuple.cpp +++ b/src/Columns/tests/gtest_column_tuple.cpp @@ -1,8 +1,15 @@ #include #include +#include #include +#include +#include #include +#include +#include +#include +#include using namespace DB; @@ -77,6 +84,39 @@ TEST(ColumnTuple, EmptyTupleExpand) ASSERT_EQ(expanded_inv->size(), mask.size()); } +TEST(ColumnTuple, InsertDefaultIsExceptionSafe) +{ + /// First element is plain: its insertDefault succeeds. + /// Second element is a wide FixedString: its insertDefault triggers a large allocation + /// that overshoots a clamped memory limit and throws MEMORY_LIMIT_EXCEEDED - the same + /// way a real query hits a memory limit in the middle of a default insert. + static constexpr size_t huge = 64 * 1024 * 1024; + + auto first = ColumnFloat64::create(); + first->reserve(1); + MutableColumns elements; + elements.push_back(std::move(first)); + elements.push_back(ColumnFixedString::create(huge)); + auto tuple = ColumnTuple::create(std::move(elements)); + + { + MainThreadStatus::getInstance(); + CurrentThread::flushUntrackedMemory(); + const Int64 prev_hard_limit = total_memory_tracker.getHardLimit(); + SCOPE_EXIT_SAFE(total_memory_tracker.setHardLimit(prev_hard_limit)); + total_memory_tracker.setHardLimit(total_memory_tracker.get() + 1024); + + ASSERT_THROW(tuple->insertDefault(), Exception); + } + + /// The first element must have been rolled back so that all nested columns stay in sync, + /// otherwise a later popBack would over-pop the shorter element. + const auto & tuple_concrete = assert_cast(*tuple); + ASSERT_EQ(tuple_concrete.getColumn(0).size(), 0); + ASSERT_EQ(tuple_concrete.getColumn(1).size(), 0); + ASSERT_EQ(tuple->size(), 0); +} + TEST(ColumnTuple, EmptyTuplePermute) { ColumnPtr tuple_col = ColumnTuple::create(5); From fd72b179970d25966c4943e07db38875e817efff Mon Sep 17 00:00:00 2001 From: robot-clickhouse Date: Tue, 9 Jun 2026 19:57:12 +0000 Subject: [PATCH 018/146] Backport #106692 to 26.3: Fix segfault due to use-after-free in NATS --- src/Storages/NATS/INATSConsumer.cpp | 65 ++++++++++++++++----- src/Storages/NATS/INATSConsumer.h | 2 + src/Storages/NATS/NATSJetStreamConsumer.cpp | 5 ++ src/Storages/NATS/NATSJetStreamConsumer.h | 2 + src/Storages/NATS/StorageNATS.cpp | 3 +- 5 files changed, 60 insertions(+), 17 deletions(-) diff --git a/src/Storages/NATS/INATSConsumer.cpp b/src/Storages/NATS/INATSConsumer.cpp index f8705d134c66..7c95e0198e60 100644 --- a/src/Storages/NATS/INATSConsumer.cpp +++ b/src/Storages/NATS/INATSConsumer.cpp @@ -5,15 +5,13 @@ #include #include #include +#include #include namespace DB { -namespace ErrorCodes -{ - extern const int INVALID_STATE; -} +static const int64_t DRAIN_TIMEOUT_MS = 5000; INATSConsumer::INATSConsumer( NATSConnectionPtr connection_, @@ -37,6 +35,27 @@ bool INATSConsumer::isSubscribed() const } void INATSConsumer::unsubscribe() { + if (stopped) + { + received.finish(); + + for (auto & subscription : subscriptions) + { + auto status = natsSubscription_DrainTimeout(subscription.get(), DRAIN_TIMEOUT_MS); + if (status != NATS_OK) + { + LOG_WARNING(log, "Failed to start draining a subscription of consumer {}: {}", + static_cast(this), natsStatus_GetText(status)); + continue; + } + + status = natsSubscription_WaitForDrainCompletion(subscription.get(), DRAIN_TIMEOUT_MS); + if (status != NATS_OK) + LOG_WARNING(log, "A subscription of consumer {} did not finish draining: {}", + static_cast(this), natsStatus_GetText(status)); + } + } + subscriptions.clear(); LOG_DEBUG(log, "Consumer {} unsubscribed", static_cast(this)); @@ -53,22 +72,38 @@ ReadBufferPtr INATSConsumer::consume() void INATSConsumer::onMsg(natsConnection *, natsSubscription *, natsMsg * msg, void * consumer) { auto * nats_consumer = static_cast(consumer); - const int msg_length = natsMsg_GetDataLength(msg); - if (msg_length) + try { - String message_received = std::string(natsMsg_GetData(msg), msg_length); - String subject = natsMsg_GetSubject(msg); - - MessageData data = { - .message = message_received, - .subject = subject, - }; - if (!nats_consumer->received.push(std::move(data))) - throw Exception(ErrorCodes::INVALID_STATE, "Could not push to received queue"); + const int msg_length = natsMsg_GetDataLength(msg); + if (msg_length) + { + String message_received = std::string(natsMsg_GetData(msg), msg_length); + String subject = natsMsg_GetSubject(msg); + + MessageData data = { + .message = message_received, + .subject = subject, + }; + if (!nats_consumer->received.push(std::move(data))) + { + LOG_DEBUG(nats_consumer->log, "Consumer {} is shutting down, dropping a message", static_cast(nats_consumer)); + nats_consumer->nackMessage(msg); + } + } + } + catch (...) + { + tryLogCurrentException(nats_consumer->log, "Could not push to received queue"); + nats_consumer->nackMessage(msg); } natsMsg_Destroy(msg); } +void INATSConsumer::nackMessage(natsMsg *) +{ + /// Core NATS has no acknowledgements. Nothing to do. +} + } diff --git a/src/Storages/NATS/INATSConsumer.h b/src/Storages/NATS/INATSConsumer.h index f498d1dc6dea..501dba9b6489 100644 --- a/src/Storages/NATS/INATSConsumer.h +++ b/src/Storages/NATS/INATSConsumer.h @@ -67,6 +67,8 @@ class INATSConsumer static void onMsg(natsConnection * nc, natsSubscription * sub, natsMsg * msg, void * consumer); + virtual void nackMessage(natsMsg * msg); + private: NATSConnectionPtr connection; std::vector subscriptions; diff --git a/src/Storages/NATS/NATSJetStreamConsumer.cpp b/src/Storages/NATS/NATSJetStreamConsumer.cpp index d5c4932c4f64..72c0de89ddc3 100644 --- a/src/Storages/NATS/NATSJetStreamConsumer.cpp +++ b/src/Storages/NATS/NATSJetStreamConsumer.cpp @@ -9,6 +9,11 @@ namespace ErrorCodes extern const int INVALID_STATE; } +void NATSJetStreamConsumer::nackMessage(natsMsg * msg) +{ + natsMsg_Nak(msg, nullptr); +} + NATSJetStreamConsumer::NATSJetStreamConsumer( NATSConnectionPtr connection, String stream_name_, diff --git a/src/Storages/NATS/NATSJetStreamConsumer.h b/src/Storages/NATS/NATSJetStreamConsumer.h index 2cbcc354bb0e..df0699b5ae8c 100644 --- a/src/Storages/NATS/NATSJetStreamConsumer.h +++ b/src/Storages/NATS/NATSJetStreamConsumer.h @@ -26,6 +26,8 @@ class NATSJetStreamConsumer : public INATSConsumer void subscribe() override; protected: + void nackMessage(natsMsg * msg) override; + NATSSubscriptionPtr subscribeToSubject(const String & subject); const String stream_name; diff --git a/src/Storages/NATS/StorageNATS.cpp b/src/Storages/NATS/StorageNATS.cpp index 30a2a536e59f..cc1777bae669 100644 --- a/src/Storages/NATS/StorageNATS.cpp +++ b/src/Storages/NATS/StorageNATS.cpp @@ -479,8 +479,7 @@ void StorageNATS::shutdown(bool /* is_drop */) /// Just a paranoid try catch, it is not actually needed. try { - if (drop_table) - unsubscribeConsumers(); + unsubscribeConsumers(); if (consumers_connection) { From 1183bf69b1f7ec59575fe5ae852490cbb427cce4 Mon Sep 17 00:00:00 2001 From: robot-clickhouse Date: Wed, 10 Jun 2026 03:26:43 +0000 Subject: [PATCH 019/146] Backport #106411 to 26.3: Fix backup FILE_DOESNT_EXIST for refreshable materialized view targets --- src/Backups/BackupEntriesCollector.cpp | 22 ++-- src/Backups/BackupEntriesCollector.h | 3 +- tests/integration/test_refreshable_mv/test.py | 115 +++++++++++++++++- 3 files changed, 125 insertions(+), 15 deletions(-) diff --git a/src/Backups/BackupEntriesCollector.cpp b/src/Backups/BackupEntriesCollector.cpp index 4f6b74022732..868ac0711ca8 100644 --- a/src/Backups/BackupEntriesCollector.cpp +++ b/src/Backups/BackupEntriesCollector.cpp @@ -15,8 +15,6 @@ #include #include #include -#include -#include #include #include #include @@ -570,10 +568,16 @@ void BackupEntriesCollector::gatherTablesMetadata() res_table_info.metadata_path_in_backup = metadata_path_in_backup; res_table_info.data_path_in_backup = data_path_in_backup; - if (const auto * mv = typeid_cast(storage.get())) + /// Record REPLACE targets of refreshable materialized views from the create query, not + /// the storage object: for Replicated/Shared databases `getTablesForBackup` resolves it + /// from a ZooKeeper snapshot and may return null if not yet created on this replica. + if (create.is_materialized_view && create.refresh_strategy && !create.refresh_strategy->append + && create.hasTargetTableID(ViewTarget::To)) { - if (mv->isRefreshable() && !mv->isAppendRefreshStrategy()) - rmv_replace_target_ids.insert(mv->getTargetTableId()); + StorageID target_id = create.getTargetTableID(ViewTarget::To); + if (!target_id.hasDatabase()) + target_id.database_name = database_name; + rmv_replace_target_ids.insert(target_id); } } } @@ -883,17 +887,15 @@ void BackupEntriesCollector::makeBackupEntriesForTableData(const QualifiedTableN bool BackupEntriesCollector::shouldBackupTableData( const QualifiedTableName & table_name, - const StoragePtr & storage, + /// Used in the Cloud build. + [[maybe_unused]] const StoragePtr & storage, const std::unordered_set & rmv_replace_target_ids) const { if (backup_settings.structure_only) return false; - if (!storage) - return true; - if (!backup_settings.backup_data_from_refreshable_materialized_view_targets - && rmv_replace_target_ids.contains(storage->getStorageID())) + && rmv_replace_target_ids.contains(StorageID{table_name.database, table_name.table})) { LOG_TRACE(log, "Skipping table data for {} (a target of a refreshable materialized view)", table_name.getFullName()); return false; diff --git a/src/Backups/BackupEntriesCollector.h b/src/Backups/BackupEntriesCollector.h index a7b54226acda..84c9819aa71e 100644 --- a/src/Backups/BackupEntriesCollector.h +++ b/src/Backups/BackupEntriesCollector.h @@ -99,7 +99,8 @@ class BackupEntriesCollector : private boost::noncopyable void makeBackupEntriesForTableData(const QualifiedTableName & table_name); bool shouldBackupTableData( const QualifiedTableName & table_name, - const StoragePtr & storage, + /// Used in the Cloud build. + [[maybe_unused]] const StoragePtr & storage, const std::unordered_set & rmv_replace_target_ids) const; void addBackupEntryUnlocked(const String & file_name, BackupEntryPtr backup_entry); diff --git a/tests/integration/test_refreshable_mv/test.py b/tests/integration/test_refreshable_mv/test.py index c241145ff3d9..c7880dd2dce8 100644 --- a/tests/integration/test_refreshable_mv/test.py +++ b/tests/integration/test_refreshable_mv/test.py @@ -108,11 +108,9 @@ def test_refreshable_mv_in_replicated_db(started_cluster, cleanup): for node in nodes: # Wait twice to make sure we wait for a refresh that started after we adjusted the clock. # Otherwise another refresh may start right after (because clock moved far forward). - node.query( - f"system wait view re.{name};\ + node.query(f"system wait view re.{name};\ system refresh view re.{name};\ - system wait view re.{name};" - ) + system wait view re.{name};") for node in nodes: node.query(f"system sync replica re.{name};") rows_before = int(nodes[randint(0, 1)].query(f"select count() from re.{name}")) @@ -550,6 +548,57 @@ def test_backup_inner_table(started_cluster, cleanup): do_test_backup(False) +def test_backup_skips_rmv_target_when_view_storage_unresolved(started_cluster, cleanup): + # A refreshable MV's REPLACE target must be skipped during backup even when the MV resolves to a + # null storage on the backup-initiating replica. node1 (the only `with_minio` instance) backs up; + # pausing its DDL queue while node2 creates the MV leaves node1 seeing the MV only in ZooKeeper. + znode = f"/test/re_repro_null_storage_{test_idx}" + for node in nodes: + node.query( + f"create database re engine = Replicated('{znode}', 'shard1', '{{replica}}');" + ) + + # Base tables on node2; let node1 catch up. + node2.query( + "create table re.src (x Int64) engine ReplicatedMergeTree order by x;" + "create table re.repro_tgt_view (x Int64) engine ReplicatedMergeTree order by x;" + "insert into re.repro_tgt_view values (1);" + ) + node1.query("system sync database replica re") + node1.query_with_retry("system sync replica re.repro_tgt_view") + + # Stall node1's DDL queue so it won't apply the CREATE below. + node1.query("system enable failpoint database_replicated_stop_entry_execution") + + try: + # Create the MV on node2 without waiting for node1. `EMPTY`+`EVERY 1 YEAR` avoid any refresh, + # so re.repro_tgt_view is never EXCHANGEd and stays resolvable on node1 while the MV does not. + node2.query( + "create materialized view re.repro_mv refresh every 1 year to re.repro_tgt_view empty as select x from re.src", + settings={"distributed_ddl_task_timeout": 0}, + ) + # The MV's metadata must be in ZooKeeper before backing up. + assert_eq_with_retry( + node2, + "select count() from system.tables where database = 're' and name = 'repro_mv'", + "1", + ) + + backup_destination = new_backup_destination() + node1.query(f"BACKUP DATABASE re TO {backup_destination}") + finally: + # Re-enable the queue before asserting, so a failure doesn't hang teardown's `drop ... sync`. + node1.query("system disable failpoint database_replicated_stop_entry_execution") + + # The target's data must be skipped even though the MV's storage was unresolved. + assert node1.contains_in_log( + "Skipping table data for re.repro_tgt_view (a target of a refreshable materialized view)" + ) + + for node in nodes: + node.query("drop database re sync") + + def test_adding_replica(started_cluster, cleanup): node1.query( "create database re engine = Replicated('/test/re', 'shard1', 'r1');" @@ -576,6 +625,64 @@ def test_adding_replica(started_cluster, cleanup): ) +def test_backup_skips_rmv_target_when_target_storage_unresolved( + started_cluster, cleanup +): + # Like the previous test, but the target itself resolves to a null storage on node1: the MV's + # initial refresh EXCHANGEs re.repro_tgt to a new UUID that paused node1 never applies. It must + # still be skipped, else a null storage delegates the data to another replica, racing the EXCHANGE. + znode = f"/test/re_repro_target_null_{test_idx}" + for node in nodes: + node.query( + f"create database re engine = Replicated('{znode}', 'shard1', '{{replica}}');" + ) + + # Base tables on node2; let node1 catch up. + node2.query( + "create table re.src (x Int64) engine ReplicatedMergeTree order by x;" + "create table re.repro_tgt_storage (x Int64) engine ReplicatedMergeTree order by x;" + "insert into re.repro_tgt_storage values (1);" + ) + node1.query("system sync database replica re") + node1.query_with_retry("system sync replica re.repro_tgt_storage") + + tgt_uuid = node2.query( + "select uuid from system.tables where database = 're' and name = 'repro_tgt_storage'" + ).strip() + + # Stall node1's DDL queue so it won't apply the CREATE/EXCHANGE below. + node1.query("system enable failpoint database_replicated_stop_entry_execution") + + try: + # Create the MV on node2 without waiting for node1; its initial refresh EXCHANGEs re.repro_tgt_storage. + node2.query( + "create materialized view re.repro_mv refresh every 1 year to re.repro_tgt_storage as select x from re.src", + settings={"distributed_ddl_task_timeout": 0}, + ) + # Wait until the EXCHANGE landed (re.repro_tgt_storage's UUID changes on node2). + assert_eq_with_retry( + node2, + f"select toString(uuid) != '{tgt_uuid}' from system.tables where database = 're' and name = 'repro_tgt_storage'", + "1", + retry_count=60, + sleep_time=1, + ) + + backup_destination = new_backup_destination() + node1.query(f"BACKUP DATABASE re TO {backup_destination}") + finally: + # Re-enable the queue before asserting, so a failure doesn't hang teardown's `drop ... sync`. + node1.query("system disable failpoint database_replicated_stop_entry_execution") + + # The target's data must be skipped even though its storage was unresolved on the backup replica. + assert node1.contains_in_log( + "Skipping table data for re.repro_tgt_storage (a target of a refreshable materialized view)" + ) + + for node in nodes: + node.query("drop database re sync") + + def test_replicated_db_startup_race(started_cluster, cleanup): for node in nodes: node.query( From 67ad718ee19688b49298cb641c1a0e6d84ac3203 Mon Sep 17 00:00:00 2001 From: robot-clickhouse Date: Wed, 10 Jun 2026 13:34:52 +0000 Subject: [PATCH 020/146] Backport #105222 to 26.3: Update submodule --- contrib/librdkafka | 2 +- tests/integration/test_kafka_bad_messages/test_1.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/contrib/librdkafka b/contrib/librdkafka index cd9a80837602..6aa06cfec6ae 160000 --- a/contrib/librdkafka +++ b/contrib/librdkafka @@ -1 +1 @@ -Subproject commit cd9a80837602fe0f97c146beff752e705f40cb0a +Subproject commit 6aa06cfec6ae35efb6e6f895326ed0ec2ecc2a27 diff --git a/tests/integration/test_kafka_bad_messages/test_1.py b/tests/integration/test_kafka_bad_messages/test_1.py index cc778944e68a..36b40427f486 100644 --- a/tests/integration/test_kafka_bad_messages/test_1.py +++ b/tests/integration/test_kafka_bad_messages/test_1.py @@ -113,7 +113,7 @@ def test_log_to_exceptions(kafka_cluster, max_retries=20): logging.debug(f"system.kafka_consumers content: {system_kafka_consumers_content}") assert system_kafka_consumers_content.startswith( - f"[thrd:localhost:{non_existent_broker_port}/bootstrap]: localhost:{non_existent_broker_port}/bootstrap: Connect to ipv4#127.0.0.1:{non_existent_broker_port} failed: Connection refused" + f"[thrd:localhost:{non_existent_broker_port}/bootstrap]: 1/1 brokers are down" ) instance.query("DROP TABLE foo_exceptions") From 7317a420985664defc45f1211722df5b4d007121 Mon Sep 17 00:00:00 2001 From: robot-clickhouse Date: Wed, 10 Jun 2026 15:24:41 +0000 Subject: [PATCH 021/146] Backport #105847 to 26.3: Fix on-fly mutation LowCardinality pass-through column conversion --- src/Storages/MergeTree/AlterConversions.cpp | 63 +++++++++++++++++++ .../MergeTree/MergeTreeBlockReadUtils.cpp | 1 + src/Storages/MergeTree/MergeTreeRangeReader.h | 5 ++ .../MergeTree/MergeTreeReadersChain.cpp | 47 +++++++++++++- .../MergeTree/MergeTreeSelectProcessor.cpp | 2 + .../MergeTreeSplitPrewhereIntoReadSteps.cpp | 1 + ...tation_lc_passthrough_conversion.reference | 1 + ...fly_mutation_lc_passthrough_conversion.sql | 43 +++++++++++++ ...fly_mutation_delete_modify_chain.reference | 1 + ...68_on_fly_mutation_delete_modify_chain.sql | 35 +++++++++++ ...y_mutation_filtered_update_chain.reference | 1 + ..._on_fly_mutation_filtered_update_chain.sql | 51 +++++++++++++++ 12 files changed, 250 insertions(+), 1 deletion(-) create mode 100644 tests/queries/0_stateless/04267_on_fly_mutation_lc_passthrough_conversion.reference create mode 100644 tests/queries/0_stateless/04267_on_fly_mutation_lc_passthrough_conversion.sql create mode 100644 tests/queries/0_stateless/04268_on_fly_mutation_delete_modify_chain.reference create mode 100644 tests/queries/0_stateless/04268_on_fly_mutation_delete_modify_chain.sql create mode 100644 tests/queries/0_stateless/04308_on_fly_mutation_filtered_update_chain.reference create mode 100644 tests/queries/0_stateless/04308_on_fly_mutation_filtered_update_chain.sql diff --git a/src/Storages/MergeTree/AlterConversions.cpp b/src/Storages/MergeTree/AlterConversions.cpp index 3950e7067fd3..748b4540ccc2 100644 --- a/src/Storages/MergeTree/AlterConversions.cpp +++ b/src/Storages/MergeTree/AlterConversions.cpp @@ -297,6 +297,68 @@ PrewhereExprSteps AlterConversions::getMutationSteps( auto actions_chain = getMutationActions(part_info, read_columns, metadata_snapshot, context); auto settings = ExpressionActionsSettings(context); + /// Columns the surviving on-fly chain will overwrite. Attached to every + /// pre-`MODIFY` step so `MergeTreeReadersChain::executeActionsBeforePrewhere` + /// can skip `performRequiredConversions` for them: their on-disk value is + /// about to be replaced and pre-casting it could fail on values the chain + /// will discard (for example, `_CAST('x', UInt64)` before `UPDATE v = '100'`). + /// + /// The set is built from the chain that `filterMutationCommands` actually + /// returns for this `read_columns`. Commands the query does not need are + /// dropped here, otherwise an earlier surviving step that reads one of + /// those columns as a source would see the on-disk type while the block + /// already advertises the post-`MODIFY` type. + /// + /// `MutationActions::dag.getOutputs()` would give a superset (it lists + /// passthrough columns too), so we read the assignment targets directly + /// from the surviving commands. + /// + /// The skip is keyed on storage column names downstream + /// (`MergeTreeReadersChain::executeActionsBeforePrewhere` calls + /// `getNameInStorage()`). Assignment targets are top-level columns today; + /// if per-subcolumn assignments to `Nested` columns ever become + /// supported, the reader-side key has to switch accordingly. + NameSet columns_overwritten_by_chain; + if (!actions_chain.empty()) + { + Names storage_read_columns; + NameSet storage_read_columns_set; + for (const auto & column : read_columns) + { + auto name_in_storage = column.getNameInStorage(); + if (storage_read_columns_set.emplace(name_in_storage).second) + { + storage_read_columns.emplace_back(name_in_storage); + } + } + addColumnsRequiredForMaterialized(storage_read_columns, storage_read_columns_set, metadata_snapshot, context); + for (const auto & command : filterMutationCommands(storage_read_columns, std::move(storage_read_columns_set))) + { + auto ast = command.ast(); + if (!ast) + { + continue; + } + if (command.type == MutationCommand::UPDATE) + { + for (const auto & [column, _] : getColumnToUpdateExpression(*ast)) + { + columns_overwritten_by_chain.insert(column); + } + } + else if (command.type == MutationCommand::DELETE) + { + /// Inserted for any chained `DELETE`. Lightweight delete + /// arrives as a `DELETE`-typed command without the original + /// `_row_exists = 0` assignment, so the explicit insert is + /// the only way to keep it skipped. Plain `ALTER DELETE` does + /// not have an on-disk `_row_exists`, so the insert is a + /// no-op for `performRequiredConversions`. + columns_overwritten_by_chain.insert(RowExistsColumn::name); + } + } + } + PrewhereExprSteps steps; for (auto & actions : actions_chain) { @@ -313,6 +375,7 @@ PrewhereExprSteps AlterConversions::getMutationSteps( .remove_filter_column = false, .need_filter = is_filter, .perform_alter_conversions = perform_alter_conversions, + .columns_overwritten_by_chain = perform_alter_conversions ? NameSet{} : columns_overwritten_by_chain, .mutation_version = actions.mutation_version, }; diff --git a/src/Storages/MergeTree/MergeTreeBlockReadUtils.cpp b/src/Storages/MergeTree/MergeTreeBlockReadUtils.cpp index edca48414b52..18360aa1877b 100644 --- a/src/Storages/MergeTree/MergeTreeBlockReadUtils.cpp +++ b/src/Storages/MergeTree/MergeTreeBlockReadUtils.cpp @@ -351,6 +351,7 @@ PrewhereExprStepPtr createLightweightDeleteStep(bool remove_filter_column) .remove_filter_column = remove_filter_column, .need_filter = true, .perform_alter_conversions = true, + .columns_overwritten_by_chain = {}, .mutation_version = std::nullopt, }; diff --git a/src/Storages/MergeTree/MergeTreeRangeReader.h b/src/Storages/MergeTree/MergeTreeRangeReader.h index cd50b9ad1eca..5f8ea9a1aa2f 100644 --- a/src/Storages/MergeTree/MergeTreeRangeReader.h +++ b/src/Storages/MergeTree/MergeTreeRangeReader.h @@ -43,6 +43,11 @@ struct PrewhereExprStep /// A step without alter conversion cannot be executed after step with alter conversions. bool perform_alter_conversions = false; + /// Columns the on-fly mutation chain will overwrite. They are exempt from + /// per-step alter conversion. See `MergeTreeReadersChain::executeActionsBeforePrewhere` + /// for usage. Empty when `perform_alter_conversions == true`. + NameSet columns_overwritten_by_chain; + /// Version of mutation if step is a part of on-fly mutation. std::optional mutation_version; }; diff --git a/src/Storages/MergeTree/MergeTreeReadersChain.cpp b/src/Storages/MergeTree/MergeTreeReadersChain.cpp index 8aa5bd450135..fae373883d18 100644 --- a/src/Storages/MergeTree/MergeTreeReadersChain.cpp +++ b/src/Storages/MergeTree/MergeTreeReadersChain.cpp @@ -228,11 +228,56 @@ void MergeTreeReadersChain::executeActionsBeforePrewhere( apply_patches(ColumnForPatch::Order::BeforeConversions); - /// If columns not empty, then apply on-fly alter conversions if any required + /// Apply alter conversions for columns read by this step. + /// + /// On-fly UPDATE/DELETE steps that precede a pending `ALTER MODIFY COLUMN` are built + /// by `AlterConversions::getMutationSteps` with `perform_alter_conversions = false`, + /// because the on-fly action's DAG embeds its own CAST for the columns the mutation + /// overwrites. Pre-casting the on-disk value would fail on values the UPDATE is + /// about to replace (e.g. `CAST('x' AS UInt64)` before `UPDATE v = '100'`). + /// + /// The same step also produces pass-through columns the mutation never touches. + /// Skipping conversion for those leaves the block advertising the post-MODIFY + /// metadata type over the on-disk column class, and downstream operators + /// (`MergingSortedTransform`'s `ColumnLowCardinality::insertFrom`, `NativeWriter`'s + /// `typeid_cast`, etc.) trip on the type vs. storage mismatch. + /// + /// `prewhere_info->columns_overwritten_by_chain` is the set of columns the chain + /// will overwrite. Null those out around `performRequiredConversions` so they are + /// skipped, then restore them so the step's action sees them in their on-disk form. + /// An empty skip set means the chain has nothing to skip, so the conversion is a + /// no-op for this step and we leave the columns untouched. if (!prewhere_info || prewhere_info->perform_alter_conversions) { merge_tree_reader->performRequiredConversions(read_columns); } + else if (!prewhere_info->columns_overwritten_by_chain.empty()) + { + const auto & reader_columns = merge_tree_reader->getColumns(); + const auto & skip = prewhere_info->columns_overwritten_by_chain; + + Columns saved(read_columns.size()); + size_t pos = 0; + for (const auto & name_and_type : reader_columns) + { + if (skip.contains(name_and_type.getNameInStorage())) + { + saved[pos] = read_columns[pos]; + read_columns[pos] = nullptr; + } + ++pos; + } + + merge_tree_reader->performRequiredConversions(read_columns); + + pos = 0; + for (const auto & name_and_type : reader_columns) + { + if (skip.contains(name_and_type.getNameInStorage())) + read_columns[pos] = std::move(saved[pos]); + ++pos; + } + } apply_patches(ColumnForPatch::Order::AfterConversions); diff --git a/src/Storages/MergeTree/MergeTreeSelectProcessor.cpp b/src/Storages/MergeTree/MergeTreeSelectProcessor.cpp index 52e719b9d461..b2de962354af 100644 --- a/src/Storages/MergeTree/MergeTreeSelectProcessor.cpp +++ b/src/Storages/MergeTree/MergeTreeSelectProcessor.cpp @@ -186,6 +186,7 @@ PrewhereExprInfo MergeTreeSelectProcessor::getPrewhereActions( .remove_filter_column = row_level_filter->do_remove_column, .need_filter = true, .perform_alter_conversions = true, + .columns_overwritten_by_chain = {}, .mutation_version = std::nullopt, }; @@ -214,6 +215,7 @@ PrewhereExprInfo MergeTreeSelectProcessor::getPrewhereActions( .remove_filter_column = prewhere_info->remove_prewhere_column, .need_filter = prewhere_info->need_filter, .perform_alter_conversions = true, + .columns_overwritten_by_chain = {}, .mutation_version = std::nullopt, }; diff --git a/src/Storages/MergeTree/MergeTreeSplitPrewhereIntoReadSteps.cpp b/src/Storages/MergeTree/MergeTreeSplitPrewhereIntoReadSteps.cpp index 70aadb6c74ff..abf6b4c90f7e 100644 --- a/src/Storages/MergeTree/MergeTreeSplitPrewhereIntoReadSteps.cpp +++ b/src/Storages/MergeTree/MergeTreeSplitPrewhereIntoReadSteps.cpp @@ -338,6 +338,7 @@ bool tryBuildPrewhereSteps( step.original_node && !all_outputs.contains(step.original_node) && node_to_step[step.original_node] <= step_index, .need_filter = force_short_circuit_execution, .perform_alter_conversions = true, + .columns_overwritten_by_chain = {}, .mutation_version = std::nullopt, }; diff --git a/tests/queries/0_stateless/04267_on_fly_mutation_lc_passthrough_conversion.reference b/tests/queries/0_stateless/04267_on_fly_mutation_lc_passthrough_conversion.reference new file mode 100644 index 000000000000..49df484ef6d8 --- /dev/null +++ b/tests/queries/0_stateless/04267_on_fly_mutation_lc_passthrough_conversion.reference @@ -0,0 +1 @@ +200 ['LowCardinality(String)'] diff --git a/tests/queries/0_stateless/04267_on_fly_mutation_lc_passthrough_conversion.sql b/tests/queries/0_stateless/04267_on_fly_mutation_lc_passthrough_conversion.sql new file mode 100644 index 000000000000..7707eaebbc4a --- /dev/null +++ b/tests/queries/0_stateless/04267_on_fly_mutation_lc_passthrough_conversion.sql @@ -0,0 +1,43 @@ +-- UPDATE mutations on columns other than `c` queued BEFORE a pending MODIFY COLUMN +-- that wraps `c` in LowCardinality used to leave `c` in the on-disk type while the +-- block advertised the post-MODIFY type. See `MergeTreeReadersChain::executeActionsBeforePrewhere`. +-- +-- The bug fires when: +-- - a sort over the to-be-LC column forces it through `MergingSortedTransform` instead +-- of lazy materialization (`query_plan_max_limit_for_lazy_materialization = 10` keeps +-- lazy materialization off for the `LIMIT 5000` here, matching the production setting); +-- - `apply_mutations_on_fly = 1` engages the on-fly mutation step that previously +-- skipped `performRequiredConversions` for the column. + +DROP TABLE IF EXISTS t_on_fly_mut_lc SYNC; + +CREATE TABLE t_on_fly_mut_lc (id UInt64, name String, c String) +ENGINE = MergeTree ORDER BY id; + +INSERT INTO t_on_fly_mut_lc SELECT number, 'n' || toString(number), 's' || toString(number % 5) FROM numbers(100); +INSERT INTO t_on_fly_mut_lc SELECT number + 100, 'n' || toString(number + 100), 's' || toString(number % 5) FROM numbers(100); + +SYSTEM STOP MERGES t_on_fly_mut_lc; + +-- UPDATE BEFORE the MODIFY COLUMN — the on-fly mutation step then has +-- `perform_alter_conversions = false`. +ALTER TABLE t_on_fly_mut_lc UPDATE name = concat('u_', name) WHERE 1 = 1 + SETTINGS mutations_sync = 0, alter_sync = 0; +ALTER TABLE t_on_fly_mut_lc MODIFY COLUMN c LowCardinality(String) + SETTINGS mutations_sync = 0, alter_sync = 0; + +-- Used to throw `Code 44. Expected ColumnLowCardinality, got String: +-- While executing MergingSortedTransform`. After the fix it returns 200 rows. +SELECT name, c FROM t_on_fly_mut_lc +ORDER BY c +LIMIT 5000 +SETTINGS apply_mutations_on_fly = 1, query_plan_max_limit_for_lazy_materialization = 10 +FORMAT Null; + +-- Sanity: column is correctly typed for the user. +SELECT count(), groupUniqArray(toTypeName(c)) +FROM t_on_fly_mut_lc +SETTINGS apply_mutations_on_fly = 1 +FORMAT TSV; + +DROP TABLE t_on_fly_mut_lc; diff --git a/tests/queries/0_stateless/04268_on_fly_mutation_delete_modify_chain.reference b/tests/queries/0_stateless/04268_on_fly_mutation_delete_modify_chain.reference new file mode 100644 index 000000000000..4527b988d159 --- /dev/null +++ b/tests/queries/0_stateless/04268_on_fly_mutation_delete_modify_chain.reference @@ -0,0 +1 @@ +100 ['UInt64'] 100 199 diff --git a/tests/queries/0_stateless/04268_on_fly_mutation_delete_modify_chain.sql b/tests/queries/0_stateless/04268_on_fly_mutation_delete_modify_chain.sql new file mode 100644 index 000000000000..45d4e77fb365 --- /dev/null +++ b/tests/queries/0_stateless/04268_on_fly_mutation_delete_modify_chain.sql @@ -0,0 +1,35 @@ +-- Lightweight DELETE queued before MODIFY COLUMN: the on-fly chain must apply +-- both the row filter and the type conversion of the column being MODIFY'd, even +-- though MODIFY itself is not applied on-fly (only the version fence is set). +-- +-- A previous fix narrowed `columns_overwritten_by_chain` to UPDATE/DELETE targets +-- so the MODIFY'd column is no longer skipped from `performRequiredConversions`. +-- This test guards against re-broadening the set: with the broader set, the +-- output block would carry `c` as String while the schema advertised UInt64. + +DROP TABLE IF EXISTS t_on_fly_del_then_mod SYNC; + +-- `enable_block_number_column` and `enable_block_offset_column` are randomised by +-- `clickhouse-test` (see `tests/clickhouse-test:1665`), so the test exercises both +-- branches without pinning them here. Verified empirically that the original +-- reproducer fails identically with the settings on or off. +CREATE TABLE t_on_fly_del_then_mod (id UInt64, c String) +ENGINE = MergeTree ORDER BY id; + +INSERT INTO t_on_fly_del_then_mod SELECT number, toString(number) FROM numbers(100); +INSERT INTO t_on_fly_del_then_mod SELECT number + 100, toString(number + 100) FROM numbers(100); + +SYSTEM STOP MERGES t_on_fly_del_then_mod; + +DELETE FROM t_on_fly_del_then_mod WHERE id < 100 + SETTINGS lightweight_deletes_sync = 0, mutations_sync = 0; +ALTER TABLE t_on_fly_del_then_mod MODIFY COLUMN c UInt64 + SETTINGS mutations_sync = 0, alter_sync = 0; + +-- Survivors are rows 100..199; column must be UInt64. +SELECT count(), groupUniqArray(toTypeName(c)), min(c), max(c) +FROM t_on_fly_del_then_mod +SETTINGS apply_mutations_on_fly = 1 +FORMAT TSV; + +DROP TABLE t_on_fly_del_then_mod SYNC; diff --git a/tests/queries/0_stateless/04308_on_fly_mutation_filtered_update_chain.reference b/tests/queries/0_stateless/04308_on_fly_mutation_filtered_update_chain.reference new file mode 100644 index 000000000000..e373ee695f6e --- /dev/null +++ b/tests/queries/0_stateless/04308_on_fly_mutation_filtered_update_chain.reference @@ -0,0 +1 @@ +50 diff --git a/tests/queries/0_stateless/04308_on_fly_mutation_filtered_update_chain.sql b/tests/queries/0_stateless/04308_on_fly_mutation_filtered_update_chain.sql new file mode 100644 index 000000000000..fada2c3266b4 --- /dev/null +++ b/tests/queries/0_stateless/04308_on_fly_mutation_filtered_update_chain.sql @@ -0,0 +1,51 @@ +-- Regression test from `clickhouse-gh[bot]` review on PR #105847 +-- (https://github.com/ClickHouse/ClickHouse/pull/105847#discussion_r3346245939). +-- +-- `filterMutationCommands` may drop a later `UPDATE` whose target the current +-- query does not need. If the on-fly skip set were built from the original +-- `mutation_commands` list it would still contain that dropped target, and an +-- earlier surviving step that reads it as a source would see the on-disk type +-- while the block already advertises the post-`MODIFY` type. The fix derives +-- the skip set from the chain that `filterMutationCommands` actually returns. +-- +-- Scenario: `UPDATE b = isNotNull(materialize(a))`, then `UPDATE a = 'x'`, +-- then `MODIFY COLUMN a LowCardinality(Nullable(String))`. Reading only `b` +-- drops the `UPDATE a`. Without the fix the surviving `UPDATE b` action sees +-- `a` advertised as `LowCardinality(Nullable(String))` while it is still +-- on-disk `Nullable(String)`, and `materialize` fails with +-- `LOGICAL_ERROR: Unexpected return type from materialize`. + +DROP TABLE IF EXISTS t_filtered_chain_isnotnull SYNC; + +CREATE TABLE t_filtered_chain_isnotnull +( + id UInt64, + a Nullable(String), + b UInt8 +) +ENGINE = MergeTree +ORDER BY id; + +INSERT INTO t_filtered_chain_isnotnull +SELECT number, if(number % 2 = 0, NULL, toString(number)), 0 +FROM numbers(100); + +SYSTEM STOP MERGES t_filtered_chain_isnotnull; + +ALTER TABLE t_filtered_chain_isnotnull + UPDATE b = isNotNull(materialize(a)) WHERE 1 = 1 + SETTINGS mutations_sync = 0, alter_sync = 0; + +ALTER TABLE t_filtered_chain_isnotnull + UPDATE a = 'x' WHERE 1 = 1 + SETTINGS mutations_sync = 0, alter_sync = 0; + +ALTER TABLE t_filtered_chain_isnotnull + MODIFY COLUMN a LowCardinality(Nullable(String)) + SETTINGS mutations_sync = 0, alter_sync = 0; + +SELECT sum(b) +FROM t_filtered_chain_isnotnull +SETTINGS apply_mutations_on_fly = 1, optimize_functions_to_subcolumns = 0; + +DROP TABLE t_filtered_chain_isnotnull SYNC; From d2bc9d5e454304b562d741119515ec3b76f9f616 Mon Sep 17 00:00:00 2001 From: robot-clickhouse Date: Wed, 10 Jun 2026 15:27:04 +0000 Subject: [PATCH 022/146] Backport #104738 to 26.3: Initialize data lake metadata before OPTIMIZE / ALTER on attach --- .../DataLakes/DataLakeConfiguration.h | 16 ++-- .../ObjectStorage/StorageObjectStorage.cpp | 10 +-- .../StorageObjectStorageConfiguration.h | 8 +- ..._metadata_not_initialized_104711.reference | 5 ++ ...ptimize_metadata_not_initialized_104711.sh | 73 +++++++++++++++++++ 5 files changed, 95 insertions(+), 17 deletions(-) create mode 100644 tests/queries/0_stateless/04230_iceberg_optimize_metadata_not_initialized_104711.reference create mode 100755 tests/queries/0_stateless/04230_iceberg_optimize_metadata_not_initialized_104711.sh diff --git a/src/Storages/ObjectStorage/DataLakes/DataLakeConfiguration.h b/src/Storages/ObjectStorage/DataLakes/DataLakeConfiguration.h index 5697b586a5ea..b4de9d5483c9 100644 --- a/src/Storages/ObjectStorage/DataLakes/DataLakeConfiguration.h +++ b/src/Storages/ObjectStorage/DataLakes/DataLakeConfiguration.h @@ -154,21 +154,21 @@ class DataLakeConfiguration : public BaseStorageConfiguration, public std::enabl current_metadata->mutate(commands, shared_from_this(), context, storage_id, metadata_snapshot, catalog, format_settings); } - void checkMutationIsPossible(const MutationCommands & commands) override + void checkMutationIsPossible(ObjectStoragePtr object_storage, ContextPtr context, const MutationCommands & commands) override { - assertInitialized(); + lazyInitializeIfNeeded(object_storage, context); current_metadata->checkMutationIsPossible(commands); } - void checkAlterIsPossible(const AlterCommands & commands) override + void checkAlterIsPossible(ObjectStoragePtr object_storage, ContextPtr context, const AlterCommands & commands) override { - assertInitialized(); + lazyInitializeIfNeeded(object_storage, context); current_metadata->checkAlterIsPossible(commands); } - void alter(const AlterCommands & params, ContextPtr context) override + void alter(ObjectStoragePtr object_storage, const AlterCommands & params, ContextPtr context) override { - assertInitialized(); + lazyInitializeIfNeeded(object_storage, context); current_metadata->alter(params, context); } @@ -374,9 +374,9 @@ class DataLakeConfiguration : public BaseStorageConfiguration, public std::enabl return nullptr; } - bool optimize(const StorageMetadataPtr & metadata_snapshot, ContextPtr context, const std::optional & format_settings) override + bool optimize(ObjectStoragePtr object_storage, const StorageMetadataPtr & metadata_snapshot, ContextPtr context, const std::optional & format_settings) override { - assertInitialized(); + lazyInitializeIfNeeded(object_storage, context); return current_metadata->optimize(metadata_snapshot, context, format_settings); } diff --git a/src/Storages/ObjectStorage/StorageObjectStorage.cpp b/src/Storages/ObjectStorage/StorageObjectStorage.cpp index 7a55d1ade4ad..5e334fcc1e6b 100644 --- a/src/Storages/ObjectStorage/StorageObjectStorage.cpp +++ b/src/Storages/ObjectStorage/StorageObjectStorage.cpp @@ -593,7 +593,7 @@ bool StorageObjectStorage::optimize( bool /*cleanup*/, [[maybe_unused]] ContextPtr context) { - return configuration->optimize(metadata_snapshot, context, format_settings); + return configuration->optimize(object_storage, metadata_snapshot, context, format_settings); } void StorageObjectStorage::truncate( @@ -765,7 +765,7 @@ void StorageObjectStorage::mutate([[maybe_unused]] const MutationCommands & comm void StorageObjectStorage::checkMutationIsPossible(const MutationCommands & commands, const Settings & /* settings */) const { - configuration->checkMutationIsPossible(commands); + configuration->checkMutationIsPossible(object_storage, CurrentThread::tryGetQueryContext(), commands); } Pipe StorageObjectStorage::executeCommand(const String & command_name, const ASTPtr & args, ContextPtr context) @@ -781,7 +781,7 @@ void StorageObjectStorage::alter(const AlterCommands & params, ContextPtr contex StorageInMemoryMetadata new_metadata = getInMemoryMetadata(); params.apply(new_metadata, context); - configuration->alter(params, context); + configuration->alter(object_storage, params, context); DatabaseCatalog::instance() .getDatabase(storage_id.database_name) @@ -789,9 +789,9 @@ void StorageObjectStorage::alter(const AlterCommands & params, ContextPtr contex setInMemoryMetadata(new_metadata); } -void StorageObjectStorage::checkAlterIsPossible(const AlterCommands & commands, ContextPtr /*context*/) const +void StorageObjectStorage::checkAlterIsPossible(const AlterCommands & commands, ContextPtr context) const { - configuration->checkAlterIsPossible(commands); + configuration->checkAlterIsPossible(object_storage, context, commands); } diff --git a/src/Storages/ObjectStorage/StorageObjectStorageConfiguration.h b/src/Storages/ObjectStorage/StorageObjectStorageConfiguration.h index a1b95973a5c6..d056596cbb10 100644 --- a/src/Storages/ObjectStorage/StorageObjectStorageConfiguration.h +++ b/src/Storages/ObjectStorage/StorageObjectStorageConfiguration.h @@ -240,12 +240,12 @@ class StorageObjectStorageConfiguration { throw Exception(ErrorCodes::NOT_IMPLEMENTED, "Table engine {} doesn't support mutations", getTypeName()); } - virtual void checkMutationIsPossible(const MutationCommands & /*commands*/) + virtual void checkMutationIsPossible(ObjectStoragePtr /*object_storage*/, ContextPtr /*context*/, const MutationCommands & /*commands*/) { throw Exception(ErrorCodes::NOT_IMPLEMENTED, "Table engine {} doesn't support mutations", getTypeName()); } - virtual void checkAlterIsPossible(const AlterCommands & commands) + virtual void checkAlterIsPossible(ObjectStoragePtr /*object_storage*/, ContextPtr /*context*/, const AlterCommands & commands) { for (const auto & command : commands) { @@ -255,7 +255,7 @@ class StorageObjectStorageConfiguration } } - virtual void alter(const AlterCommands & /*params*/, ContextPtr /*context*/) {} + virtual void alter(ObjectStoragePtr /*object_storage*/, const AlterCommands & /*params*/, ContextPtr /*context*/) {} virtual const DataLakeStorageSettings & getDataLakeSettings() const { @@ -269,7 +269,7 @@ class StorageObjectStorageConfiguration virtual std::shared_ptr getCatalog(ContextPtr /*context*/, bool /*is_attach*/) const { return nullptr; } - virtual bool optimize(const StorageMetadataPtr & /*metadata_snapshot*/, ContextPtr /*context*/, const std::optional & /*format_settings*/) + virtual bool optimize(ObjectStoragePtr /*object_storage*/, const StorageMetadataPtr & /*metadata_snapshot*/, ContextPtr /*context*/, const std::optional & /*format_settings*/) { return false; } diff --git a/tests/queries/0_stateless/04230_iceberg_optimize_metadata_not_initialized_104711.reference b/tests/queries/0_stateless/04230_iceberg_optimize_metadata_not_initialized_104711.reference new file mode 100644 index 000000000000..22dfe36ab851 --- /dev/null +++ b/tests/queries/0_stateless/04230_iceberg_optimize_metadata_not_initialized_104711.reference @@ -0,0 +1,5 @@ +INSERT failed as expected +OPTIMIZE did not crash with Logical error +ALTER did not crash with Logical error +ALTER ADD COLUMN did not crash with Logical error +1 diff --git a/tests/queries/0_stateless/04230_iceberg_optimize_metadata_not_initialized_104711.sh b/tests/queries/0_stateless/04230_iceberg_optimize_metadata_not_initialized_104711.sh new file mode 100755 index 000000000000..acbbca385829 --- /dev/null +++ b/tests/queries/0_stateless/04230_iceberg_optimize_metadata_not_initialized_104711.sh @@ -0,0 +1,73 @@ +#!/usr/bin/env bash +# Tags: no-fasttest, no-parallel +# - no-fasttest: requires `IcebergLocal` (USE_AVRO build option) +# - no-parallel: uses DETACH/ATTACH which serializes per database + +# Regression test for https://github.com/ClickHouse/ClickHouse/issues/104711: +# `OPTIMIZE TABLE` on a freshly attached `IcebergLocal` table whose metadata +# could not be loaded used to raise `Logical error: 'Metadata is not initialized'` +# from `DataLakeConfiguration::optimize`. After the fix, it raises a regular +# user-facing exception describing the underlying load failure. +# +# We reproduce the "could not load metadata" state by: +# 1. creating an Iceberg table with `iceberg_metadata_compression_method='deflate'`, +# 2. issuing an `INSERT` with an invalid `output_format_compression_level=11` +# so the second metadata file lands on disk corrupted, +# 3. `DETACH ... SYNC` + `ATTACH` (equivalent to a server restart from the +# perspective of `DataLakeConfiguration::current_metadata`), +# 4. then issuing `OPTIMIZE TABLE` / `ALTER TABLE` / `SELECT` which previously +# crashed. + +CUR_DIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd) +# shellcheck source=../shell_config.sh +. "$CUR_DIR"/../shell_config.sh + +TABLE="t_${CLICKHOUSE_DATABASE}_${RANDOM}" +TABLE_PATH="${USER_FILES_PATH}/${TABLE}/" + +trap 'rm -rf "${TABLE_PATH}" 2>/dev/null' EXIT + +# Step 1: create the table with the deflate metadata format. +${CLICKHOUSE_CLIENT} --query " + CREATE TABLE ${TABLE} (c0 Int32) + ENGINE = IcebergLocal('${TABLE_PATH}') + SETTINGS iceberg_metadata_compression_method = 'deflate' +" + +# Step 2: provoke the corrupt-metadata-on-disk state via an INSERT with an +# unsupported compression level. The INSERT itself must fail. +${CLICKHOUSE_CLIENT} --allow_insert_into_iceberg=1 --output_format_compression_level=11 \ + --query "INSERT INTO ${TABLE} VALUES (2)" 2>&1 \ + | grep -F 'INCORRECT_DATA' > /dev/null && echo "INSERT failed as expected" + +# Step 3: detach + attach to clear the in-memory `current_metadata` cache, +# the way a server restart would. The corrupted metadata produces a server-side +# `` log during ATTACH (swallowed by the lazy-init catch in +# `StorageObjectStorage`'s constructor), so silence the log channel that the +# client forwards to its stderr — otherwise the test fails on "having stderror". +${CLICKHOUSE_CLIENT} --query "DETACH TABLE ${TABLE} SYNC" +${CLICKHOUSE_CLIENT} --send_logs_level=fatal --query "ATTACH TABLE ${TABLE}" 2>/dev/null + +# Step 4: the previously-crashing operations must now raise a regular +# exception instead of `LOGICAL_ERROR`. We do not care which specific +# error code is reported (it depends on what `update` happens to fail +# with for the corrupted metadata) — only that it is NOT a logical error +# and that the server keeps running. +${CLICKHOUSE_CLIENT} --allow_experimental_iceberg_compaction=1 \ + --query "OPTIMIZE TABLE ${TABLE}" 2>&1 \ + | grep -F 'Logical error' > /dev/null && echo "FAIL: OPTIMIZE crashed with Logical error" \ + || echo "OPTIMIZE did not crash with Logical error" + +${CLICKHOUSE_CLIENT} --allow_insert_into_iceberg=1 \ + --query "ALTER TABLE ${TABLE} DELETE WHERE c0 = 0" 2>&1 \ + | grep -F 'Logical error' > /dev/null && echo "FAIL: ALTER crashed with Logical error" \ + || echo "ALTER did not crash with Logical error" + +${CLICKHOUSE_CLIENT} --query "ALTER TABLE ${TABLE} ADD COLUMN c1 Int32" 2>&1 \ + | grep -F 'Logical error' > /dev/null && echo "FAIL: ALTER ADD COLUMN crashed with Logical error" \ + || echo "ALTER ADD COLUMN did not crash with Logical error" + +# The server is still alive. +${CLICKHOUSE_CLIENT} --query "SELECT 1" + +${CLICKHOUSE_CLIENT} --query "DROP TABLE IF EXISTS ${TABLE} SYNC" From 3dd74f0083b27ff98a785fd2d01457a620968e3f Mon Sep 17 00:00:00 2001 From: robot-clickhouse Date: Wed, 10 Jun 2026 15:28:34 +0000 Subject: [PATCH 023/146] Backport #104250 to 26.3: Reject out-of-range integer partition ID for Date instead of silent overflow or LOGICAL_ERROR on ALTER --- src/Interpreters/convertFieldToType.cpp | 18 +++---- tests/integration/test_partition/test.py | 8 ++-- .../0_stateless/01073_bad_alter_partition.sql | 2 +- ..._partition_integer_overflow_date.reference | 3 ++ ...7_drop_partition_integer_overflow_date.sql | 48 +++++++++++++++++++ 5 files changed, 66 insertions(+), 13 deletions(-) create mode 100644 tests/queries/0_stateless/04207_drop_partition_integer_overflow_date.reference create mode 100644 tests/queries/0_stateless/04207_drop_partition_integer_overflow_date.sql diff --git a/src/Interpreters/convertFieldToType.cpp b/src/Interpreters/convertFieldToType.cpp index 825b6ec47eb1..a82a67848d0a 100644 --- a/src/Interpreters/convertFieldToType.cpp +++ b/src/Interpreters/convertFieldToType.cpp @@ -301,15 +301,16 @@ Field convertFieldToTypeImpl(const Field & src, const IDataType & type, const ID return dynamic_cast(type).castToValue(src); } - if ((which_type.isDate() || which_type.isDateTime()) && src.getType() == Field::Types::UInt64) + if (which_type.isDate() && src.getType() == Field::Types::UInt64) { - /// We don't need any conversion UInt64 is under type of Date and DateTime - return src; + /// Date is UInt16 under the hood; range-check so out-of-range integers + /// don't get silently truncated by the Date serializer downstream. + return convertNumericType(src, type); } - if ((which_type.isDate() || which_type.isTime()) && src.getType() == Field::Types::UInt64) + if ((which_type.isDateTime() || which_type.isTime()) && src.getType() == Field::Types::UInt64) { - /// We don't need any conversion UInt64 is under type of Date and Time + /// We don't need any conversion UInt64 is under type of DateTime and Time return src; } @@ -376,11 +377,12 @@ Field convertFieldToTypeImpl(const Field & src, const IDataType & type, const ID return DecimalField(DecimalUtils::decimalFromComponentsWithMultiplier(value, 0, 1), scale_to); } - /// For toDate('xxx') in 1::Int64, we CAST `src` to UInt64, which may - /// produce wrong result in some special cases. + /// For toDate('xxx') in 1::Int64. Date is UInt16 under the hood; + /// range-check so out-of-range integers don't get silently truncated + /// by the Date serializer downstream. if (which_type.isDate() && src.getType() == Field::Types::Int64) { - return convertNumericType(src, type); + return convertNumericType(src, type); } /// For toDate32('xxx') in 1, we CAST `src` to Int64. Also, it may diff --git a/tests/integration/test_partition/test.py b/tests/integration/test_partition/test.py index fdb108b7168f..56e06554a983 100644 --- a/tests/integration/test_partition/test.py +++ b/tests/integration/test_partition/test.py @@ -54,8 +54,8 @@ def partition_table_simple(started_cluster): def test_partition_simple(partition_table_simple): - q("ALTER TABLE test.partition_simple DETACH PARTITION 197001") - q("ALTER TABLE test.partition_simple ATTACH PARTITION 197001") + q("ALTER TABLE test.partition_simple DETACH PARTITION '1970-01-01'") + q("ALTER TABLE test.partition_simple ATTACH PARTITION '1970-01-01'") q("OPTIMIZE TABLE test.partition_simple") @@ -178,8 +178,8 @@ def test_partition_complex(partition_table_complex): partition_complex_assert_checksums(True) - q("ALTER TABLE test.partition_complex DETACH PARTITION 197001") - q("ALTER TABLE test.partition_complex ATTACH PARTITION 197001") + q("ALTER TABLE test.partition_complex DETACH PARTITION '1970-01-01'") + q("ALTER TABLE test.partition_complex ATTACH PARTITION '1970-01-01'") partition_complex_assert_columns_txt() diff --git a/tests/queries/0_stateless/01073_bad_alter_partition.sql b/tests/queries/0_stateless/01073_bad_alter_partition.sql index e179a64f3590..941f91dd2dd5 100644 --- a/tests/queries/0_stateless/01073_bad_alter_partition.sql +++ b/tests/queries/0_stateless/01073_bad_alter_partition.sql @@ -7,7 +7,7 @@ SELECT 1, * FROM merge_tree ORDER BY d; -- ALTER TABLE merge_tree DROP PARTITION 2020-01-02; -- This does not even parse -- SELECT 2, * FROM merge_tree; -ALTER TABLE merge_tree DROP PARTITION 20200103; -- unfortunately, this works, but not as user expected. +ALTER TABLE merge_tree DROP PARTITION 20200103; -- { serverError ARGUMENT_OUT_OF_BOUND } SELECT 3, * FROM merge_tree ORDER BY d; ALTER TABLE merge_tree DROP PARTITION '20200104'; diff --git a/tests/queries/0_stateless/04207_drop_partition_integer_overflow_date.reference b/tests/queries/0_stateless/04207_drop_partition_integer_overflow_date.reference new file mode 100644 index 000000000000..16db301bb512 --- /dev/null +++ b/tests/queries/0_stateless/04207_drop_partition_integer_overflow_date.reference @@ -0,0 +1,3 @@ +1 +0 +1 diff --git a/tests/queries/0_stateless/04207_drop_partition_integer_overflow_date.sql b/tests/queries/0_stateless/04207_drop_partition_integer_overflow_date.sql new file mode 100644 index 000000000000..445c174425ac --- /dev/null +++ b/tests/queries/0_stateless/04207_drop_partition_integer_overflow_date.sql @@ -0,0 +1,48 @@ +-- Regression test: ALTER TABLE ... DROP PARTITION with an integer literal that +-- overflows the Date underlying type (UInt16) used to silently truncate the +-- value modulo 65536, producing a partition ID that could collide with an +-- unrelated existing part and surface as LOGICAL_ERROR +-- ("Parsed partition value: ... doesn't match partition value for an existing +-- part with the same partition ID: ..."). + +DROP TABLE IF EXISTS t_drop_partition_overflow; + +CREATE TABLE t_drop_partition_overflow (d Date, x UInt32) +ENGINE = MergeTree PARTITION BY d ORDER BY x; + +-- 20200523 mod 65536 = 15435 = '2012-04-05', which used to collide with this part. +INSERT INTO t_drop_partition_overflow VALUES ('2012-04-05', 1); + +-- Must throw a clean ARGUMENT_OUT_OF_BOUND, not LOGICAL_ERROR. +-- UInt64 path (bare positive literal). +ALTER TABLE t_drop_partition_overflow DROP PARTITION 20200523; -- { serverError ARGUMENT_OUT_OF_BOUND } + +-- Int64 path: ParserPartition rejects bare non-`tuple` functions, but `tuple(...)` +-- is unwrapped by `getPartitionIDFromQuery` and the inner expression is evaluated, +-- so `toInt64(...)` reaches `convertFieldToType` as `Field::Int64`. +ALTER TABLE t_drop_partition_overflow DROP PARTITION tuple(toInt64(20200523)); -- { serverError ARGUMENT_OUT_OF_BOUND } +ALTER TABLE t_drop_partition_overflow DROP PARTITION tuple(CAST(20200523, 'Int64')); -- { serverError ARGUMENT_OUT_OF_BOUND } + +-- The original part must still be present. +SELECT count() FROM t_drop_partition_overflow; + +-- Sanity check: dropping by the correctly-typed partition value still works. +ALTER TABLE t_drop_partition_overflow DROP PARTITION '2012-04-05'; +SELECT count() FROM t_drop_partition_overflow; + +DROP TABLE t_drop_partition_overflow; + +-- Complex partition key: expression evaluation reaches `convertFieldToType` +-- directly (no tuple unwrap), so any Int64-producing expression on the Date +-- column triggers the same bug. +DROP TABLE IF EXISTS t_drop_partition_overflow_complex; + +CREATE TABLE t_drop_partition_overflow_complex (d Date, x UInt32) +ENGINE = MergeTree PARTITION BY (d, x) ORDER BY x; + +INSERT INTO t_drop_partition_overflow_complex VALUES ('2012-04-05', 1); + +ALTER TABLE t_drop_partition_overflow_complex DROP PARTITION (toInt64(20200523), 1); -- { serverError ARGUMENT_OUT_OF_BOUND } +SELECT count() FROM t_drop_partition_overflow_complex; + +DROP TABLE t_drop_partition_overflow_complex; From c1e0561ec7ca707d41565fe4f943e08c516382e2 Mon Sep 17 00:00:00 2001 From: robot-clickhouse Date: Wed, 10 Jun 2026 15:30:36 +0000 Subject: [PATCH 024/146] Backport #104101 to 26.3: Fix LOGICAL_ERROR in StreamingStorageRegistry::renameTable during batch renames --- src/Storages/StreamingStorageRegistry.cpp | 54 ++++++-- src/Storages/StreamingStorageRegistry.h | 15 ++- .../gtest_streaming_storage_registry.cpp | 122 ++++++++++++++++++ 3 files changed, 178 insertions(+), 13 deletions(-) create mode 100644 src/Storages/tests/gtest_streaming_storage_registry.cpp diff --git a/src/Storages/StreamingStorageRegistry.cpp b/src/Storages/StreamingStorageRegistry.cpp index 4ca0fb86e98c..51f21585e31f 100644 --- a/src/Storages/StreamingStorageRegistry.cpp +++ b/src/Storages/StreamingStorageRegistry.cpp @@ -3,6 +3,7 @@ #include #include #include +#include #include #include @@ -38,6 +39,42 @@ StreamingStorageRegistry & StreamingStorageRegistry::instance() return ret; } +size_t StreamingStorageRegistry::IdentityHash::operator()(const StorageID & storage_id) const +{ + SipHash hash; + if (storage_id.hasUUID()) + { + const auto & uuid = storage_id.uuid; + hash.update(reinterpret_cast(&uuid), sizeof(uuid)); + } + else + { + hash.update(storage_id.database_name.data(), storage_id.database_name.size()); + hash.update(storage_id.table_name.data(), storage_id.table_name.size()); + } + return hash.get64(); +} + +bool StreamingStorageRegistry::IdentityEqual::operator()(const StorageID & lhs, const StorageID & rhs) const +{ + const bool lhs_has = lhs.hasUUID(); + const bool rhs_has = rhs.hasUUID(); + + /// Both have UUID — compare by UUID only. + if (lhs_has && rhs_has) + return lhs.uuid == rhs.uuid; + + /// Mixed: one has UUID, the other does not — treat as non-equal. + /// This preserves the hash/equality contract: IdentityHash uses UUID for one + /// and name for the other, producing different hashes. Equal elements must + /// have identical hashes, so mixed cases must never compare equal. + if (lhs_has != rhs_has) + return false; + + /// Neither has UUID — fall back to name comparison. + return lhs.database_name == rhs.database_name && lhs.table_name == rhs.table_name; +} + void StreamingStorageRegistry::registerTable(const StorageID & storage) { std::lock_guard lock(mutex); @@ -85,23 +122,16 @@ void StreamingStorageRegistry::renameTable(const StorageID & from, const Storage if (shutdown_called) throw Exception(ErrorCodes::LOGICAL_ERROR, "Shutdown was called"); + /// With UUID-based identity tracking, `find` locates the entry by UUID (when available), + /// so batch renames (e.g., A↔B swap) correctly identify each table regardless of + /// intermediate name collisions. auto from_it = storages.find(from); if (from_it == storages.end()) throw Exception(ErrorCodes::LOGICAL_ERROR, "Table with storage id {} is not registered", from.getNameForLogs()); storages.erase(from_it); - - auto to_it = storages.find(to); - if (to_it == storages.end()) - { - storages.emplace(to); - LOG_TRACE(log, "Unregistered table: {}, registered table: {}", from.getNameForLogs(), to.getNameForLogs()); - } - else - { - /// This could happen because of exchange/replace tables. - LOG_TRACE(log, "Unregistered table: {}, table {} was already registered", from.getNameForLogs(), to.getNameForLogs()); - } + storages.emplace(to); + LOG_TRACE(log, "Renamed table: {} -> {}", from.getNameForLogs(), to.getNameForLogs()); } void StreamingStorageRegistry::shutdown() diff --git a/src/Storages/StreamingStorageRegistry.h b/src/Storages/StreamingStorageRegistry.h index ed967b5c722c..7f018f5c1743 100644 --- a/src/Storages/StreamingStorageRegistry.h +++ b/src/Storages/StreamingStorageRegistry.h @@ -30,10 +30,23 @@ class StreamingStorageRegistry final : private boost::noncopyable void shutdown(); private: + /// Identity-based hash: uses UUID when available, falls back to database+table name. + /// This ensures that during batch renames (e.g., A↔B swap), each table + /// is tracked by its stable identity (UUID) rather than its mutable name. + struct IdentityHash + { + size_t operator()(const StorageID & storage_id) const; + }; + + struct IdentityEqual + { + bool operator()(const StorageID & lhs, const StorageID & rhs) const; + }; + const LoggerPtr log = getLogger("StreamingStorageRegistry"); bool shutdown_called = false; std::mutex mutex; - std::unordered_set storages; + std::unordered_set storages; }; /* diff --git a/src/Storages/tests/gtest_streaming_storage_registry.cpp b/src/Storages/tests/gtest_streaming_storage_registry.cpp new file mode 100644 index 000000000000..16e7057103a2 --- /dev/null +++ b/src/Storages/tests/gtest_streaming_storage_registry.cpp @@ -0,0 +1,122 @@ +#include + +#include +#include +#include + +using namespace DB; + +/// Each test generates fresh UUIDs, so concurrent or sequential tests +/// do not collide in the singleton registry. +class StreamingStorageRegistryTest : public ::testing::Test +{ +protected: + StreamingStorageRegistry & registry = StreamingStorageRegistry::instance(); + + UUID uuid1 = UUIDHelpers::generateV4(); + UUID uuid2 = UUIDHelpers::generateV4(); +}; + +TEST_F(StreamingStorageRegistryTest, RegisterAndUnregister) +{ + StorageID table("test_db", "table_reg", uuid1); + + ASSERT_NO_THROW(registry.registerTable(table)); + ASSERT_NO_THROW(registry.unregisterTable(table)); +} + +TEST_F(StreamingStorageRegistryTest, DuplicateRegisterThrows) +{ + StorageID table("test_db", "table_dup", uuid1); + + registry.registerTable(table); + EXPECT_THROW(registry.registerTable(table), Exception); + + registry.unregisterTable(table); +} + +/// LOGICAL_ERROR aborts in debug/sanitizer builds, so skip this test there. +#ifndef DEBUG_OR_SANITIZER_BUILD +TEST_F(StreamingStorageRegistryTest, UnregisterNonExistentThrows) +{ + StorageID table("test_db", "table_missing", uuid1); + EXPECT_THROW(registry.unregisterTable(table), Exception); +} +#endif + +TEST_F(StreamingStorageRegistryTest, SimpleRename) +{ + StorageID from("test_db", "old_name", uuid1); + StorageID to("test_db", "new_name", uuid1); + + registry.registerTable(from); + ASSERT_NO_THROW(registry.renameTable(from, to)); + ASSERT_NO_THROW(registry.unregisterTable(to)); +} + +/// LOGICAL_ERROR aborts in debug/sanitizer builds, so skip this test there. +#ifndef DEBUG_OR_SANITIZER_BUILD +TEST_F(StreamingStorageRegistryTest, RenameNonExistentThrows) +{ + StorageID from("test_db", "no_such_table", uuid1); + StorageID to("test_db", "dest", uuid1); + + EXPECT_THROW(registry.renameTable(from, to), Exception); +} +#endif + +/// Regression test for the A↔B batch-rename scenario. +/// +/// SharedDatabaseCatalog::applyState renames streaming tables in sequence. +/// When two S3Queue tables swap names (e.g., during a dbt RENAME+EXCHANGE +/// workflow), the rename sequence is: +/// 1. rename A → B (table uuid1 gets name B) +/// 2. rename B → A (table uuid2 gets name A) +/// +/// Before the UUID-based identity fix, the registry matched entries by name +/// only, so step 1 would silently collide with the existing entry for B. +/// After step 2 completed, only one of the two tables remained registered, +/// causing LOGICAL_ERROR on subsequent applyState retries. +/// +/// With UUID-based tracking, each table is identified by its UUID, so both +/// survive the swap regardless of intermediate name collisions. +TEST_F(StreamingStorageRegistryTest, BatchRenameSwapPreservesBothTables) +{ + StorageID table_a("test_db", "queue_a", uuid1); + StorageID table_b("test_db", "queue_b", uuid2); + + registry.registerTable(table_a); + registry.registerTable(table_b); + + /// Step 1: rename A → B (table uuid1 now has name "queue_b") + StorageID a_with_new_name("test_db", "queue_b", uuid1); + ASSERT_NO_THROW(registry.renameTable(table_a, a_with_new_name)); + + /// Step 2: rename B → A (table uuid2 now has name "queue_a") + StorageID b_with_new_name("test_db", "queue_a", uuid2); + ASSERT_NO_THROW(registry.renameTable(table_b, b_with_new_name)); + + /// Both tables must still be registered — unregister must not throw. + EXPECT_NO_THROW(registry.unregisterTable(a_with_new_name)); + EXPECT_NO_THROW(registry.unregisterTable(b_with_new_name)); +} + +/// Same swap but across different databases, which is what dbt does when it +/// renames __new → production and production → __backup in different schemas. +TEST_F(StreamingStorageRegistryTest, BatchRenameCrossDatabaseSwap) +{ + StorageID table_a("db_prod", "queue", uuid1); + StorageID table_b("db_staging", "queue", uuid2); + + registry.registerTable(table_a); + registry.registerTable(table_b); + + StorageID a_moved("db_staging", "queue", uuid1); + ASSERT_NO_THROW(registry.renameTable(table_a, a_moved)); + + StorageID b_moved("db_prod", "queue", uuid2); + ASSERT_NO_THROW(registry.renameTable(table_b, b_moved)); + + EXPECT_NO_THROW(registry.unregisterTable(a_moved)); + EXPECT_NO_THROW(registry.unregisterTable(b_moved)); +} From fa5c15ee580a7bcddcf6443af05457f90806f0ba Mon Sep 17 00:00:00 2001 From: robot-clickhouse Date: Wed, 10 Jun 2026 15:32:41 +0000 Subject: [PATCH 025/146] Backport #101644 to 26.3: Do not apply lazy materialization for plans with array join --- .../optimizeLazyMaterialization.cpp | 28 +++++++++++++++++++ ..._lazy_materialization_array_join.reference | 5 ++++ .../04077_lazy_materialization_array_join.sql | 19 +++++++++++++ 3 files changed, 52 insertions(+) create mode 100644 tests/queries/0_stateless/04077_lazy_materialization_array_join.reference create mode 100644 tests/queries/0_stateless/04077_lazy_materialization_array_join.sql diff --git a/src/Processors/QueryPlan/Optimizations/optimizeLazyMaterialization.cpp b/src/Processors/QueryPlan/Optimizations/optimizeLazyMaterialization.cpp index f4428f43e99e..e6b8d3d2feb6 100644 --- a/src/Processors/QueryPlan/Optimizations/optimizeLazyMaterialization.cpp +++ b/src/Processors/QueryPlan/Optimizations/optimizeLazyMaterialization.cpp @@ -276,6 +276,31 @@ static ReadFromMergeTree * findReadingStep(QueryPlan::Node & node, StepStack & b return nullptr; } +bool allExpressionsSuitableForLazyMaterialization(const QueryPlan::Node * node) +{ + while (!node->children.empty()) + { + if (const auto * expr_step = typeid_cast(node->step.get())) + { + if (expr_step->getExpression().hasArrayJoin()) + return false; + } + else if (const auto * filter_step = typeid_cast(node->step.get())) + { + if (filter_step->getExpression().hasArrayJoin()) + return false; + } + else + { + return false; + } + + node = node->children.front(); + } + + return true; +} + bool optimizeLazyMaterialization2(QueryPlan::Node & root, QueryPlan & query_plan, QueryPlan::Nodes & nodes, const QueryPlanOptimizationSettings & settings, size_t max_limit_for_lazy_materialization) { if (root.children.size() != 1) @@ -318,6 +343,9 @@ bool optimizeLazyMaterialization2(QueryPlan::Node & root, QueryPlan & query_plan if (!canUseLazyMaterializationForReadingStep(reading_step)) return false; + if (!allExpressionsSuitableForLazyMaterialization(sorting_node->children.front())) + return false; + const auto & sorting_header = *sorting_step->getOutputHeader(); std::vector required_columns(sorting_header.columns(), false); diff --git a/tests/queries/0_stateless/04077_lazy_materialization_array_join.reference b/tests/queries/0_stateless/04077_lazy_materialization_array_join.reference new file mode 100644 index 000000000000..c5ba9f5d5aca --- /dev/null +++ b/tests/queries/0_stateless/04077_lazy_materialization_array_join.reference @@ -0,0 +1,5 @@ +[('2',2),('2',2)] +[('3',3),('3',3),('3',3)] +---------------------- +[('2',2),('2',2)] +[('3',3),('3',3),('3',3)] diff --git a/tests/queries/0_stateless/04077_lazy_materialization_array_join.sql b/tests/queries/0_stateless/04077_lazy_materialization_array_join.sql new file mode 100644 index 000000000000..99c00d9288ad --- /dev/null +++ b/tests/queries/0_stateless/04077_lazy_materialization_array_join.sql @@ -0,0 +1,19 @@ +SET query_plan_optimize_lazy_materialization = 1; +SET flatten_nested = 0; + +DROP TABLE IF EXISTS t0; +CREATE TABLE t0 +( + id UInt32, + col1 Nested(a UInt32, n Nested(s String, b UInt32)) +) +ENGINE = MergeTree +ORDER BY id; + +INSERT INTO t0 SELECT number, arrayMap(x -> (x, arrayMap(y -> (toString(number), number), range(number))), range(number)) FROM numbers(10); + +SELECT arrayJoin(col1.n) as e FROM t0 ORDER BY id LIMIT 2 OFFSET 2; + +SELECT '----------------------'; + +SELECT arrayJoin(col1.n) as e FROM t0 ORDER BY id LIMIT 2 OFFSET 2 SETTINGS optimize_read_in_order=0, query_plan_execute_functions_after_sorting=0; From 1477ce0198e0a14b906f143bcac761b061018bd6 Mon Sep 17 00:00:00 2001 From: robot-clickhouse Date: Wed, 10 Jun 2026 15:35:35 +0000 Subject: [PATCH 026/146] Backport #100899 to 26.3: Increment profile events MemoryAllocatedWithoutCheck/MemoryAllocatedWithoutCheckBytes in release build --- src/Common/MemoryTracker.cpp | 14 ++++++++------ src/Common/ProfileEvents.cpp | 6 +++--- src/Common/ProfileEvents.h | 5 +++-- 3 files changed, 14 insertions(+), 11 deletions(-) diff --git a/src/Common/MemoryTracker.cpp b/src/Common/MemoryTracker.cpp index 7d88fd7ad619..1873bbc56b11 100644 --- a/src/Common/MemoryTracker.cpp +++ b/src/Common/MemoryTracker.cpp @@ -241,18 +241,20 @@ void MemoryTracker::injectFault() const void incrementAllocationWithoutCheck(Int64 size) { - /// Note, it is always blocked for release build, so we do not write MemoryAllocatedWithoutCheck there + ProfileEvents::increment(ProfileEvents::MemoryAllocatedWithoutCheck); + if (size < 0) + return; + + ProfileEvents::increment(ProfileEvents::MemoryAllocatedWithoutCheckBytes, size); + + /// In release builds, `isBlocked` is always true, so only profile events are collected; + /// the trace sending below is debug/sanitizer-only. if (MemoryTrackerDebugBlockerInThread::isBlocked()) return; /// The choice is arbitrary (maybe we should decrease it) constexpr Int64 threshold = 16 * 1024 * 1024; - ProfileEvents::increment(ProfileEvents::MemoryAllocatedWithoutCheck); - if (size < 0) - return; - ProfileEvents::increment(ProfileEvents::MemoryAllocatedWithoutCheckBytes, size); - if (size > threshold) { auto memory_blocked_context = MemoryTrackerBlockerInThread::getLevel(); diff --git a/src/Common/ProfileEvents.cpp b/src/Common/ProfileEvents.cpp index 6acf71a5374a..d4c005d2f01b 100644 --- a/src/Common/ProfileEvents.cpp +++ b/src/Common/ProfileEvents.cpp @@ -1403,9 +1403,9 @@ namespace ProfileEvents constexpr Event END = Event(__COUNTER__); /// Global variable, initialized by zeros. -static Counter global_counters_array[END] {}; -/// Initialize global counters statically -Counters global_counters(global_counters_array); +static constinit Counter global_counters_array[END] {}; +/// Constant-initialized so it is ready before any dynamic initializer can allocate memory. +constinit Counters global_counters(global_counters_array); const Event Counters::num_counters = END; diff --git a/src/Common/ProfileEvents.h b/src/Common/ProfileEvents.h index 92380a1eba37..1ef79222d997 100644 --- a/src/Common/ProfileEvents.h +++ b/src/Common/ProfileEvents.h @@ -79,8 +79,9 @@ namespace ProfileEvents /// By default, any instance have to increment global counters explicit Counters(VariableContext level_ = VariableContext::Thread, Counters * parent_ = &global_counters); - /// Global level static initializer - explicit Counters(Counter * allocated_counters) noexcept + /// Global level static initializer (constexpr to enable constant initialization + /// before any dynamic initializer can allocate memory and call ProfileEvents::increment) + constexpr explicit Counters(Counter * allocated_counters) noexcept : counters(allocated_counters), parent(nullptr), level(VariableContext::Global) {} Counters(Counters && src) noexcept; From 22040e3ba1668c148ebc137c50b0f00142e21d5c Mon Sep 17 00:00:00 2001 From: robot-clickhouse Date: Wed, 10 Jun 2026 17:24:58 +0000 Subject: [PATCH 027/146] Backport #105246 to 26.3: Fix wrong `OSIOWaitMicroseconds` for leaking stats --- src/Common/FailPoint.cpp | 1 + src/Common/ThreadProfileEvents.cpp | 16 ++++++++ src/Common/ThreadProfileEvents.h | 2 +- src/Interpreters/ThreadStatusExt.cpp | 24 +++++++++++- ...04249_taskstats_reset_throwpoint.reference | 1 + .../04249_taskstats_reset_throwpoint.sh | 37 +++++++++++++++++++ 6 files changed, 78 insertions(+), 3 deletions(-) create mode 100644 tests/queries/0_stateless/04249_taskstats_reset_throwpoint.reference create mode 100755 tests/queries/0_stateless/04249_taskstats_reset_throwpoint.sh diff --git a/src/Common/FailPoint.cpp b/src/Common/FailPoint.cpp index fbc560eaa47e..162808af2d05 100644 --- a/src/Common/FailPoint.cpp +++ b/src/Common/FailPoint.cpp @@ -81,6 +81,7 @@ static struct InitFiu REGULAR(check_table_query_delay_for_part) \ REGULAR(dummy_failpoint) \ REGULAR(prefetched_reader_pool_failpoint) \ + REGULAR(taskstats_counters_reset_throw) \ REGULAR(shared_set_sleep_during_update) \ REGULAR(smt_outdated_parts_exception_response) \ REGULAR(object_storage_queue_fail_in_the_middle_of_file) \ diff --git a/src/Common/ThreadProfileEvents.cpp b/src/Common/ThreadProfileEvents.cpp index d778745486a9..d675ba04c95d 100644 --- a/src/Common/ThreadProfileEvents.cpp +++ b/src/Common/ThreadProfileEvents.cpp @@ -2,6 +2,8 @@ #if defined(OS_LINUX) +#include +#include #include #include @@ -63,6 +65,16 @@ namespace ProfileEvents namespace DB { +namespace FailPoints +{ +extern const char taskstats_counters_reset_throw[]; +} + +namespace ErrorCodes +{ +extern const int FAULT_INJECTED; +} + const char * TasksStatsCounters::metricsProviderString(MetricsProvider provider) { switch (provider) @@ -127,6 +139,10 @@ TasksStatsCounters::TasksStatsCounters(const UInt64 tid, const MetricsProvider p void TasksStatsCounters::reset() { + fiu_do_on(FailPoints::taskstats_counters_reset_throw, + { + throw Exception(ErrorCodes::FAULT_INJECTED, "Injected failure in TasksStatsCounters::reset"); + }); if (stats_getter) stats = stats_getter(); } diff --git a/src/Common/ThreadProfileEvents.h b/src/Common/ThreadProfileEvents.h index ddf37db8ab83..7eda09b593fd 100644 --- a/src/Common/ThreadProfileEvents.h +++ b/src/Common/ThreadProfileEvents.h @@ -206,7 +206,7 @@ class TasksStatsCounters void updateCounters(ProfileEvents::Counters & profile_events); private: - ::taskstats stats; + ::taskstats stats{}; std::function<::taskstats()> stats_getter; explicit TasksStatsCounters(UInt64 tid, MetricsProvider provider); diff --git a/src/Interpreters/ThreadStatusExt.cpp b/src/Interpreters/ThreadStatusExt.cpp index 9b567e6398c4..4bb672c040a2 100644 --- a/src/Interpreters/ThreadStatusExt.cpp +++ b/src/Interpreters/ThreadStatusExt.cpp @@ -564,7 +564,17 @@ void ThreadStatus::initPerformanceCounters() } } if (taskstats) - (*taskstats).reset(); + { + try + { + (*taskstats).reset(); + } + catch (...) + { + tryLogCurrentException(log, "Failed to reset taskstats counters, disabling for this thread", LogsLevel::warning); + taskstats = nullptr; + } + } } void ThreadStatus::finalizePerformanceCounters() @@ -623,7 +633,17 @@ void ThreadStatus::resetPerformanceCountersLastUsage() { *last_rusage = RUsageCounters::current(); if (taskstats) - (*taskstats).reset(); + { + try + { + (*taskstats).reset(); + } + catch (...) + { + tryLogCurrentException(log, "Failed to reset taskstats counters, disabling for this thread", LogsLevel::warning); + taskstats = nullptr; + } + } } void ThreadStatus::initGlobalProfiler([[maybe_unused]] UInt64 global_profiler_real_time_period, [[maybe_unused]] UInt64 global_profiler_cpu_time_period) diff --git a/tests/queries/0_stateless/04249_taskstats_reset_throwpoint.reference b/tests/queries/0_stateless/04249_taskstats_reset_throwpoint.reference new file mode 100644 index 000000000000..d00491fd7e5b --- /dev/null +++ b/tests/queries/0_stateless/04249_taskstats_reset_throwpoint.reference @@ -0,0 +1 @@ +1 diff --git a/tests/queries/0_stateless/04249_taskstats_reset_throwpoint.sh b/tests/queries/0_stateless/04249_taskstats_reset_throwpoint.sh new file mode 100755 index 000000000000..7c3cdad56b6c --- /dev/null +++ b/tests/queries/0_stateless/04249_taskstats_reset_throwpoint.sh @@ -0,0 +1,37 @@ +#!/usr/bin/env bash +# Tags: no-parallel +# Reason: enables a server-wide REGULAR failpoint that affects all threads +# Regression test for OSIOWaitMicroseconds being polluted with thread-lifetime +# blkio accumulation when TasksStatsCounters::reset throws. +# +# When reset() fails, the fix nulls out taskstats so no counter update runs, +# keeping OSIOWaitMicroseconds at 0 instead of inheriting the thread's kernel +# blkio_delay_total (which can be on the order of days for long-lived workers). + +CUR_DIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd) +# shellcheck source=../shell_config.sh +. "$CUR_DIR"/../shell_config.sh + +# Suppress Warning-level server logs from being forwarded to client stderr; +# this test deliberately triggers a Warning by injecting a reset() failure. +CLICKHOUSE_CLIENT="${CLICKHOUSE_CLIENT} --send_logs_level=fatal" + +${CLICKHOUSE_CLIENT} --query "SYSTEM ENABLE FAILPOINT taskstats_counters_reset_throw" +trap '${CLICKHOUSE_CLIENT} --query "SYSTEM DISABLE FAILPOINT taskstats_counters_reset_throw" || true' EXIT + +QUERY_ID="${CLICKHOUSE_TEST_UNIQUE_NAME}" +${CLICKHOUSE_CLIENT} --query_id="${QUERY_ID}" --query "SELECT sum(number) FROM numbers(1000) FORMAT Null" + +${CLICKHOUSE_CLIENT} --query "SYSTEM FLUSH LOGS query_log" + +# OSIOWaitMicroseconds must be 0: taskstats was nulled out on reset failure, +# so updateCounters was never called and the counter was never incremented. +${CLICKHOUSE_CLIENT} --query " + SELECT ProfileEvents['OSIOWaitMicroseconds'] = 0 + FROM system.query_log + WHERE query_id = '${QUERY_ID}' AND type = 'QueryFinish' + AND current_database = currentDatabase() + LIMIT 1 +" + +${CLICKHOUSE_CLIENT} --query "SYSTEM DISABLE FAILPOINT taskstats_counters_reset_throw" From cb6dd3ca65adbf10c24ba72376c62dca065969e8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ra=C3=BAl=20Mar=C3=ADn?= Date: Thu, 11 Jun 2026 10:24:47 +0000 Subject: [PATCH 028/146] Fix build: MutationCommand::ast is a member and column_to_update_expression exists on this branch --- src/Storages/MergeTree/AlterConversions.cpp | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/Storages/MergeTree/AlterConversions.cpp b/src/Storages/MergeTree/AlterConversions.cpp index 748b4540ccc2..de57c3c21bd6 100644 --- a/src/Storages/MergeTree/AlterConversions.cpp +++ b/src/Storages/MergeTree/AlterConversions.cpp @@ -334,14 +334,13 @@ PrewhereExprSteps AlterConversions::getMutationSteps( addColumnsRequiredForMaterialized(storage_read_columns, storage_read_columns_set, metadata_snapshot, context); for (const auto & command : filterMutationCommands(storage_read_columns, std::move(storage_read_columns_set))) { - auto ast = command.ast(); - if (!ast) + if (!command.ast) { continue; } if (command.type == MutationCommand::UPDATE) { - for (const auto & [column, _] : getColumnToUpdateExpression(*ast)) + for (const auto & [column, _] : command.column_to_update_expression) { columns_overwritten_by_chain.insert(column); } From 123f0855bcae207619a09b1348d08f140d85897d Mon Sep 17 00:00:00 2001 From: robot-clickhouse Date: Thu, 11 Jun 2026 13:40:41 +0000 Subject: [PATCH 029/146] Backport #107096 to 26.3: Fix spurious ZooKeeper session recreation on config reload --- src/Common/ZooKeeper/ZooKeeper.cpp | 5 ++ src/Common/ZooKeeper/ZooKeeperArgs.h | 4 ++ .../__init__.py | 0 .../test.py | 60 +++++++++++++++++++ 4 files changed, 69 insertions(+) create mode 100644 tests/integration/test_zookeeper_session_on_config_reload/__init__.py create mode 100644 tests/integration/test_zookeeper_session_on_config_reload/test.py diff --git a/src/Common/ZooKeeper/ZooKeeper.cpp b/src/Common/ZooKeeper/ZooKeeper.cpp index 06fb01e9ede4..116ab0d36ad0 100644 --- a/src/Common/ZooKeeper/ZooKeeper.cpp +++ b/src/Common/ZooKeeper/ZooKeeper.cpp @@ -334,6 +334,11 @@ bool ZooKeeper::configChanged(const Poco::Util::AbstractConfiguration & config, new_args.enforce_component_tracking = args.enforce_component_tracking; new_args.send_receive_os_threads_nice_value = args.send_receive_os_threads_nice_value; + /// last_zxid_seen is runtime session state propagated by startNewSession, not configuration, + /// so it is always 0 in new_args. Without this, the first config reload after any + /// expiry-driven session replacement recreates the session even if the config is unchanged. + new_args.last_zxid_seen = args.last_zxid_seen; + return args != new_args; } diff --git a/src/Common/ZooKeeper/ZooKeeperArgs.h b/src/Common/ZooKeeper/ZooKeeperArgs.h index cef7dac9607c..bd7e1bd7f825 100644 --- a/src/Common/ZooKeeper/ZooKeeperArgs.h +++ b/src/Common/ZooKeeper/ZooKeeperArgs.h @@ -29,6 +29,10 @@ struct ZooKeeperArgs /// hosts_string -- comma separated [secure://]host:port list ZooKeeperArgs(const String & hosts_string); /// NOLINT(google-explicit-constructor) ZooKeeperArgs() = default; + /// Memberwise comparison. ZooKeeper::configChanged uses it to compare args parsed from + /// config; any field below that is set out-of-band (server settings, runtime session state + /// such as last_zxid_seen) must be excluded from the comparison there, otherwise every + /// config reload spuriously recreates the session. bool operator == (const ZooKeeperArgs &) const = default; String zookeeper_name = "zookeeper"; diff --git a/tests/integration/test_zookeeper_session_on_config_reload/__init__.py b/tests/integration/test_zookeeper_session_on_config_reload/__init__.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/tests/integration/test_zookeeper_session_on_config_reload/test.py b/tests/integration/test_zookeeper_session_on_config_reload/test.py new file mode 100644 index 000000000000..436621fb8459 --- /dev/null +++ b/tests/integration/test_zookeeper_session_on_config_reload/test.py @@ -0,0 +1,60 @@ +#!/usr/bin/env python3 + +import pytest + +from helpers.cluster import ClickHouseCluster + +cluster = ClickHouseCluster(__file__) +node = cluster.add_instance("node", with_zookeeper=True) + + +@pytest.fixture(scope="module") +def started_cluster(): + try: + cluster.start() + yield cluster + finally: + cluster.shutdown() + + +def get_session_id(): + return int( + node.query( + "SELECT client_id FROM system.zookeeper_connection WHERE name = 'default'" + ).strip() + ) + + +def test_zookeeper_session_on_config_reload(started_cluster): + # Make the session observe some non-zero zxid. + node.query( + "CREATE TABLE t (key UInt64) " + "ENGINE = ReplicatedMergeTree('/clickhouse/tables/t', 'r1') ORDER BY key" + ) + + # Reloading an unchanged config must keep the session. + session_id = get_session_id() + node.query("SYSTEM RELOAD CONFIG") + assert get_session_id() == session_id + + # Expire the session and touch ZooKeeper to establish a new one. The new + # session remembers the last zxid seen by the expired one (to refuse + # connecting to a Keeper node that is behind). + node.query("SYSTEM RECONNECT ZOOKEEPER") + node.query("SELECT * FROM system.zookeeper WHERE path = '/'") + + new_session_id = get_session_id() + assert new_session_id != session_id + assert ( + int( + node.query( + "SELECT last_zxid_seen FROM system.zookeeper_connection WHERE name = 'default'" + ).strip() + ) + > 0 + ) + + # Reloading an unchanged config must keep the session even after the + # replacement (the remembered zxid is session state, not configuration). + node.query("SYSTEM RELOAD CONFIG") + assert get_session_id() == new_session_id From 73abda70ea4f0e104c53f5af790ac7e477c362d9 Mon Sep 17 00:00:00 2001 From: robot-clickhouse Date: Thu, 11 Jun 2026 13:44:54 +0000 Subject: [PATCH 030/146] Backport #101206 to 26.3: Fix crash when reading Iceberg tables with ORC files and PREWHERE enabled --- .../StorageObjectStorageSource.cpp | 168 +++++++++++++++++- ...04071_iceberg_orc_prewhere_crash.reference | 2 + .../04071_iceberg_orc_prewhere_crash.sh | 77 ++++++++ ..._iceberg_orc_row_policy_prewhere.reference | 3 + .../04146_iceberg_orc_row_policy_prewhere.sh | 93 ++++++++++ ..._orc_schema_evolution_row_policy.reference | 4 + ...iceberg_orc_schema_evolution_row_policy.sh | 127 +++++++++++++ 7 files changed, 466 insertions(+), 8 deletions(-) create mode 100644 tests/queries/0_stateless/04071_iceberg_orc_prewhere_crash.reference create mode 100755 tests/queries/0_stateless/04071_iceberg_orc_prewhere_crash.sh create mode 100644 tests/queries/0_stateless/04146_iceberg_orc_row_policy_prewhere.reference create mode 100755 tests/queries/0_stateless/04146_iceberg_orc_row_policy_prewhere.sh create mode 100644 tests/queries/0_stateless/04147_iceberg_orc_schema_evolution_row_policy.reference create mode 100755 tests/queries/0_stateless/04147_iceberg_orc_schema_evolution_row_policy.sh diff --git a/src/Storages/ObjectStorage/StorageObjectStorageSource.cpp b/src/Storages/ObjectStorage/StorageObjectStorageSource.cpp index 015ddff8aefc..617ddd7b6685 100644 --- a/src/Storages/ObjectStorage/StorageObjectStorageSource.cpp +++ b/src/Storages/ObjectStorage/StorageObjectStorageSource.cpp @@ -27,9 +27,11 @@ #include #include #include +#include #include #include #include +#include #include #include #include @@ -643,16 +645,90 @@ StorageObjectStorageSource::ReaderHolder StorageObjectStorageSource::createReade initial_header = sample_header; schema_changed = true; } - auto filter_info = [&]() + /// Save stripped filters if we need to apply them as fallback FilterTransforms + /// later in the pipeline when the file format doesn't support PREWHERE. + FilterDAGInfoPtr stripped_row_level_filter; + PrewhereInfoPtr stripped_prewhere_info; + + auto filter_info = [&]() -> FormatFilterInfoPtr { - if (!schema_changed) - return format_filter_info; - auto mapper = configuration->getColumnMapperForObject(object_info); - if (!mapper) - return format_filter_info; - return std::make_shared(format_filter_info->filter_actions_dag, format_filter_info->context.lock(), mapper, format_filter_info->row_level_filter, format_filter_info->prewhere_info); + if (!format_filter_info) + return nullptr; + + /// Check if the actual file format supports PREWHERE. For mixed-format data lake + /// tables (e.g. Iceberg with Parquet + ORC files), table-level PREWHERE support + /// may not match the individual file's format capabilities. + /// See https://github.com/ClickHouse/ClickHouse/issues/96829 + const auto actual_format = object_info->getFileFormat().value_or(configuration->format); + const bool format_supports_prewhere = + FormatFactory::instance().checkIfFormatSupportsPrewhere(actual_format, context_, format_settings); + + /// Save filters for fallback FilterTransform when format doesn't support PREWHERE. + if (!format_supports_prewhere) + { + if (format_filter_info->row_level_filter) + stripped_row_level_filter = format_filter_info->row_level_filter; + if (format_filter_info->prewhere_info) + stripped_prewhere_info = format_filter_info->prewhere_info; + } + + if (schema_changed) + { + if (auto mapper = configuration->getColumnMapperForObject(object_info)) + { + if (format_supports_prewhere) + return std::make_shared( + format_filter_info->filter_actions_dag, format_filter_info->context.lock(), + mapper, format_filter_info->row_level_filter, format_filter_info->prewhere_info); + else + return std::make_shared( + format_filter_info->filter_actions_dag, format_filter_info->context.lock(), + mapper, nullptr, nullptr); + } + } + + if (!format_supports_prewhere) + return std::make_shared( + format_filter_info->filter_actions_dag, + format_filter_info->context.lock(), + format_filter_info->column_mapper, + nullptr, nullptr); + + return format_filter_info; }(); + /// When PREWHERE / row-level filter is stripped from `format_filter_info` (i.e. the + /// actual file format doesn't support PREWHERE), the format reader will not produce + /// the input columns of those filters in its output: `read_from_format_info.format_header` + /// was already adjusted by `updateFormatPrewhereInfo` to reflect the post-PREWHERE + /// schema, so the format reader treats columns referenced only by PREWHERE as + /// "consumed" and does not emit them. We need them in the block so the fallback + /// `FilterTransform`s further down can evaluate `c0 > 10` etc. Re-add any missing + /// input columns of the stripped DAGs to the reader's sample header. + /// + /// Skip this for the schema-changed path: there `initial_header` was set above to + /// the FULL underlying file schema (`sample_header` from `getInitialSchemaByPath`), + /// so all file-side columns — including the file-side counterparts of the filter + /// input columns — are already present and emitted by the reader. The schema + /// transform that runs below then renames/casts them to query-side names BEFORE + /// the fallback `FilterTransform`s run, so policies and `PREWHERE` evaluate + /// against the same names the query planner produced. + if (!schema_changed) + { + auto add_filter_inputs = [&](const ActionsDAG & dag) + { + for (const auto & required : dag.getRequiredColumns()) + { + if (!initial_header.has(required.name)) + initial_header.insert({required.type, required.name}); + } + }; + if (stripped_row_level_filter) + add_filter_inputs(stripped_row_level_filter->actions); + if (stripped_prewhere_info) + add_filter_inputs(stripped_prewhere_info->prewhere_actions); + } + chassert(object_info->getObjectMetadata().has_value()); LOG_DEBUG( @@ -731,10 +807,28 @@ StorageObjectStorageSource::ReaderHolder StorageObjectStorageSource::createReade if (object_info->data_lake_metadata && object_info->data_lake_metadata->schema_transform) { schema_transform = object_info->data_lake_metadata->schema_transform->clone(); + /// Preserve filter-input columns through the data lake schema transform. The + /// fallback `FilterTransform`s below evaluate against query-side names, so + /// the schema_transform must continue to output any column referenced by + /// `stripped_row_level_filter` / `stripped_prewhere_info`, not just those + /// in `requested_columns` (which is post-`PREWHERE` and excludes filter + /// inputs). For non-stripped (Parquet) reads the filter columns aren't + /// referenced after the format reader so this is a no-op. + /// /// FIXME: This is currently not done for the below case (configuration->getSchemaTransformer()) /// because it is an iceberg case where transformer contains columns ids (just increasing numbers) /// which do not match requested_columns (while here requested_columns were adjusted to match physical columns). - schema_transform->removeUnusedActions(read_from_format_info.requested_columns.getNames()); + Names needed_names = read_from_format_info.requested_columns.getNames(); + auto add_filter_required_names = [&needed_names](const ActionsDAG & dag) + { + for (const auto & required : dag.getRequiredColumns()) + needed_names.push_back(required.name); + }; + if (stripped_row_level_filter) + add_filter_required_names(stripped_row_level_filter->actions); + if (stripped_prewhere_info) + add_filter_required_names(stripped_prewhere_info->prewhere_actions); + schema_transform->removeUnusedActions(needed_names); } if (!schema_transform) { @@ -752,6 +846,64 @@ StorageObjectStorageSource::ReaderHolder StorageObjectStorageSource::createReade }); } + /// Apply row-level security filter and `PREWHERE` as fallback `FilterTransform`s + /// when the file format doesn't support `PREWHERE`. For mixed-format data lake + /// tables (e.g. Iceberg with Parquet + ORC files), table-level `PREWHERE` support + /// may not match the individual file's format. We strip `row_level_filter` and + /// `prewhere_info` from `FormatFilterInfo` above and apply them here as post-read + /// filters instead. + /// + /// These transforms run AFTER the schema_transform `ExpressionTransform` above so + /// that the block they see uses query-side column names. The data lake schema + /// transform handles Iceberg / Delta column renames, type evolution, and + /// constant-default columns added by schema evolution; running the filters + /// downstream of it means policy and `PREWHERE` expressions evaluate against + /// the exact names produced by the query planner. (Running them upstream would + /// fail with `NOT_FOUND_COLUMN_IN_BLOCK` in the schema-changed path because the + /// reader emits file-side names, while filter expressions reference query-side + /// names.) + /// + /// Order between the two filters matters: row-level filter first, `PREWHERE` + /// second. This mirrors the canonical filter pipeline used everywhere else in + /// the engine: + /// - `SourceStepWithFilter::applyPrewhereActions` + /// - `MergeTreeSelectProcessor::getPrewhereActions` + /// - `Parquet::Reader::initializePrewhere` + /// `PREWHERE` actions drop their input columns from the block via `updateHeader` + /// (the DAG outputs the synthetic filter column plus only what is needed + /// downstream). If `PREWHERE` ran first, a row-policy expression that references + /// the same input column (a common case: row policy on `c0`, query + /// `SELECT c1 FROM t PREWHERE c0 > N`) could not be evaluated. Applying the + /// row-level filter first preserves the input columns for the policy and then + /// lets `PREWHERE` drop them as the planner intended. + /// + /// The query planner puts row policies into `row_level_filter` when + /// `storage->supportsPrewhere()` (`PlannerJoinTree.cpp:1012`), but individual + /// files in mixed-format tables may not support it at format level. + if (stripped_row_level_filter) + { + auto row_level_actions = std::make_shared(stripped_row_level_filter->actions.clone()); + builder.addSimpleTransform([&](const SharedHeader & header) + { + return std::make_shared( + header, row_level_actions, + stripped_row_level_filter->column_name, + stripped_row_level_filter->do_remove_column); + }); + } + + if (stripped_prewhere_info) + { + auto prewhere_actions = std::make_shared(stripped_prewhere_info->prewhere_actions.clone()); + builder.addSimpleTransform([&](const SharedHeader & header) + { + return std::make_shared( + header, prewhere_actions, + stripped_prewhere_info->prewhere_column_name, + stripped_prewhere_info->remove_prewhere_column); + }); + } + if (read_from_format_info.columns_description.hasDefaults()) { builder.addSimpleTransform( diff --git a/tests/queries/0_stateless/04071_iceberg_orc_prewhere_crash.reference b/tests/queries/0_stateless/04071_iceberg_orc_prewhere_crash.reference new file mode 100644 index 000000000000..8a3ad486afaa --- /dev/null +++ b/tests/queries/0_stateless/04071_iceberg_orc_prewhere_crash.reference @@ -0,0 +1,2 @@ +99 +49 diff --git a/tests/queries/0_stateless/04071_iceberg_orc_prewhere_crash.sh b/tests/queries/0_stateless/04071_iceberg_orc_prewhere_crash.sh new file mode 100755 index 000000000000..7c1b0210a1fb --- /dev/null +++ b/tests/queries/0_stateless/04071_iceberg_orc_prewhere_crash.sh @@ -0,0 +1,77 @@ +#!/usr/bin/env bash +# Tags: no-fasttest, no-parallel-replicas +# Reproduces https://github.com/ClickHouse/ClickHouse/issues/96829 +# +# `no-parallel-replicas` is strictly necessary here: when +# `parallel_replicas_for_cluster_engines = 1` (default) and +# `cluster_for_parallel_replicas` is set, `TableFunctionObjectStorage` wraps the +# `icebergLocal` storage in `StorageObjectStorageCluster`, which currently does +# not override `supportsPrewhere` and therefore inherits the default `false` +# from `IStorage`. The analyzer then throws `ILLEGAL_PREWHERE` for any explicit +# `PREWHERE` on the resulting cluster storage. This is a separate engine bug +# (cluster wrapper missing `supportsPrewhere` delegation to the underlying +# configuration) that should be fixed in a follow-up. +# When an Iceberg table has ORC data files but is read with format='Parquet' +# (which enables PREWHERE), the server crashes with: +# Logical error: 'PREWHERE passed to format that doesn't support it' +# because PREWHERE support is determined at the table level based on configuration +# format (Parquet), but individual files may be in ORC which doesn't support PREWHERE. + +CURDIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd) +# shellcheck source=../shell_config.sh +. "$CURDIR"/../shell_config.sh + +ICEBERG_PATH="${CLICKHOUSE_USER_FILES}/lakehouses/${CLICKHOUSE_DATABASE}_orc_prewhere" + +# Cleanup +rm -rf "${ICEBERG_PATH}" + +${CLICKHOUSE_CLIENT} --query " + SET allow_experimental_insert_into_iceberg = 1; + + -- Create Iceberg table with ORC format and insert data + CREATE TABLE t_ice_orc_pw (c0 Int64, c1 String) + ENGINE = IcebergLocal('${ICEBERG_PATH}', 'ORC'); + INSERT INTO t_ice_orc_pw SELECT number, toString(number) FROM numbers(100); + + -- Also insert a Parquet data file to have mixed formats + INSERT INTO TABLE FUNCTION icebergLocal('${ICEBERG_PATH}', 'Parquet', 'c0 Int64, c1 String') + SELECT number + 100, toString(number + 100) FROM numbers(50); +" + +# Read with Parquet config (enables PREWHERE) — this used to crash on ORC files. +# Use explicit PREWHERE (not WHERE + optimizer) because the query plan optimizer +# cannot push PREWHERE for ObjectStorage tables (getColumnSizes() returns empty). +# Explicit PREWHERE on mixed-format Iceberg tables requires the new query analyzer +# because the old analyzer evaluates PREWHERE through a different code path that +# doesn't go through FormatFilterInfo (our per-file format check). Force enable_analyzer=1 +# to ensure PREWHERE is handled by the new pipeline even in old-analyzer CI configs. +${CLICKHOUSE_CLIENT} --query " + SELECT count() + FROM icebergLocal('${ICEBERG_PATH}', 'Parquet', 'c0 Int64, c1 String') + PREWHERE c0 > 50 + SETTINGS input_format_parquet_use_native_reader_v3 = 1, enable_analyzer = 1 +" + +# Also test pure ORC read through Parquet config +ICEBERG_PATH_ORC="${CLICKHOUSE_USER_FILES}/lakehouses/${CLICKHOUSE_DATABASE}_orc_only_prewhere" +rm -rf "${ICEBERG_PATH_ORC}" + +${CLICKHOUSE_CLIENT} --query " + SET allow_experimental_insert_into_iceberg = 1; + + CREATE TABLE t_ice_orc_only (c0 Int64, c1 String) + ENGINE = IcebergLocal('${ICEBERG_PATH_ORC}', 'ORC'); + INSERT INTO t_ice_orc_only SELECT number, toString(number) FROM numbers(100); +" + +${CLICKHOUSE_CLIENT} --query " + SELECT count() + FROM icebergLocal('${ICEBERG_PATH_ORC}', 'Parquet', 'c0 Int64, c1 String') + PREWHERE c0 > 50 + SETTINGS input_format_parquet_use_native_reader_v3 = 1, enable_analyzer = 1 +" + +# Cleanup +${CLICKHOUSE_CLIENT} --query "DROP TABLE IF EXISTS t_ice_orc_pw; DROP TABLE IF EXISTS t_ice_orc_only" +rm -rf "${ICEBERG_PATH}" "${ICEBERG_PATH_ORC}" diff --git a/tests/queries/0_stateless/04146_iceberg_orc_row_policy_prewhere.reference b/tests/queries/0_stateless/04146_iceberg_orc_row_policy_prewhere.reference new file mode 100644 index 000000000000..d8f04d46ab42 --- /dev/null +++ b/tests/queries/0_stateless/04146_iceberg_orc_row_policy_prewhere.reference @@ -0,0 +1,3 @@ +139 +144 +11120 diff --git a/tests/queries/0_stateless/04146_iceberg_orc_row_policy_prewhere.sh b/tests/queries/0_stateless/04146_iceberg_orc_row_policy_prewhere.sh new file mode 100755 index 000000000000..9b0fa3370a9f --- /dev/null +++ b/tests/queries/0_stateless/04146_iceberg_orc_row_policy_prewhere.sh @@ -0,0 +1,93 @@ +#!/usr/bin/env bash +# Tags: no-fasttest, no-parallel-replicas +# `no-parallel-replicas`: see comment in `04071_iceberg_orc_prewhere_crash.sh`. +# `StorageObjectStorageCluster` (used when `parallel_replicas_for_cluster_engines = 1`, +# default) does not delegate `supportsPrewhere` to its underlying configuration, +# so explicit `PREWHERE` against `icebergLocal` is rejected by the analyzer. +# +# Regression test for transform-ordering correctness in the Iceberg ORC PREWHERE +# fallback path. When an Iceberg table is configured with format `Parquet` +# (PREWHERE supported) but contains ORC data files (PREWHERE not supported), we +# strip both `prewhere_info` and `row_level_filter` from `FormatFilterInfo` and +# re-apply them as `FilterTransform`s after the format reader. +# +# The order of these two transforms must be: row-level filter first, PREWHERE +# second. This mirrors the canonical filter pipeline used everywhere else +# (`SourceStepWithFilter::applyPrewhereActions`, +# `MergeTreeSelectProcessor::getPrewhereActions`, `Parquet::Reader`). +# +# A row policy that references the same column as PREWHERE (and where that +# column is not in the SELECT list) used to throw or filter incorrectly: PREWHERE +# ran first and removed the column from the block, so the row-policy expression +# could not be evaluated. + +CURDIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd) +# shellcheck source=../shell_config.sh +. "$CURDIR"/../shell_config.sh + +ICEBERG_PATH="${CLICKHOUSE_USER_FILES}/lakehouses/${CLICKHOUSE_DATABASE}_orc_rp_pw" +TEST_USER="${CLICKHOUSE_DATABASE}_user" +TEST_POLICY="${CLICKHOUSE_DATABASE}_policy" +TEST_TABLE="t_ice_orc_rp_pw" + +rm -rf "${ICEBERG_PATH}" + +# Create an Iceberg table with format=Parquet (so the table-level PREWHERE check +# passes) but write a mix of ORC and Parquet data files into it. +${CLICKHOUSE_CLIENT} --query " + SET allow_experimental_insert_into_iceberg = 1; + + CREATE TABLE ${TEST_TABLE} (c0 Int64, c1 String) + ENGINE = IcebergLocal('${ICEBERG_PATH}', 'Parquet'); + INSERT INTO ${TEST_TABLE} SELECT number, toString(number) FROM numbers(100); + + -- Add ORC data files via table function so the table contains mixed formats. + INSERT INTO TABLE FUNCTION icebergLocal('${ICEBERG_PATH}', 'ORC', 'c0 Int64, c1 String') + SELECT number + 100, toString(number + 100) FROM numbers(50); +" + +# Set up a user and a row policy on c0. The policy keeps rows where c0 > 5. +${CLICKHOUSE_CLIENT} --query "DROP USER IF EXISTS ${TEST_USER}" +${CLICKHOUSE_CLIENT} --query "CREATE USER ${TEST_USER} IDENTIFIED WITH plaintext_password BY 'rp_pwd'" +${CLICKHOUSE_CLIENT} --query "GRANT SELECT ON *.* TO ${TEST_USER}" +${CLICKHOUSE_CLIENT} --query "DROP ROW POLICY IF EXISTS ${TEST_POLICY} ON ${TEST_TABLE}" +${CLICKHOUSE_CLIENT} --query "CREATE ROW POLICY ${TEST_POLICY} ON ${TEST_TABLE} FOR SELECT USING c0 > 5 TO ${TEST_USER}" + +# 1) Query with PREWHERE on c0, where c0 is NOT in the SELECT list. This is the +# case that triggers `remove_prewhere_column = true`. With the bug (PREWHERE +# first), the row policy could not evaluate `c0 > 5` because the column was +# already gone from the block. With the fix (row-level filter first, PREWHERE +# second), both filters apply correctly. Expected: rows where c0 in (10..149] +# where the policy `c0 > 5` is automatically satisfied; total = 139. +${CLICKHOUSE_CLIENT} --user="${TEST_USER}" --password=rp_pwd --query " + SELECT count() + FROM ${TEST_TABLE} + PREWHERE c0 > 10 + SETTINGS input_format_parquet_use_native_reader_v3 = 1, enable_analyzer = 1 +" + +# 2) Same but with PREWHERE that overlaps the policy boundary (c0 > 3 < 5). The +# policy is the tighter filter — count must reflect c0 > 5 (intersection), +# not c0 > 3. +${CLICKHOUSE_CLIENT} --user="${TEST_USER}" --password=rp_pwd --query " + SELECT count() + FROM ${TEST_TABLE} + PREWHERE c0 > 3 + SETTINGS input_format_parquet_use_native_reader_v3 = 1, enable_analyzer = 1 +" + +# 3) Sanity: read c1 with PREWHERE on c0 (c0 in PREWHERE only; the column is +# removed after PREWHERE). Row policy c0 > 5 still applies. Output is sum of +# integer values of c1 for rows where c0 in (10..149]: sum(11..149) = 11120. +${CLICKHOUSE_CLIENT} --user="${TEST_USER}" --password=rp_pwd --query " + SELECT sum(toInt64(c1)) + FROM ${TEST_TABLE} + PREWHERE c0 > 10 + SETTINGS input_format_parquet_use_native_reader_v3 = 1, enable_analyzer = 1 +" + +# Cleanup +${CLICKHOUSE_CLIENT} --query "DROP ROW POLICY IF EXISTS ${TEST_POLICY} ON ${TEST_TABLE}" +${CLICKHOUSE_CLIENT} --query "DROP USER IF EXISTS ${TEST_USER}" +${CLICKHOUSE_CLIENT} --query "DROP TABLE IF EXISTS ${TEST_TABLE}" +rm -rf "${ICEBERG_PATH}" diff --git a/tests/queries/0_stateless/04147_iceberg_orc_schema_evolution_row_policy.reference b/tests/queries/0_stateless/04147_iceberg_orc_schema_evolution_row_policy.reference new file mode 100644 index 000000000000..578357def74e --- /dev/null +++ b/tests/queries/0_stateless/04147_iceberg_orc_schema_evolution_row_policy.reference @@ -0,0 +1,4 @@ +89 +94 +4895 +94 diff --git a/tests/queries/0_stateless/04147_iceberg_orc_schema_evolution_row_policy.sh b/tests/queries/0_stateless/04147_iceberg_orc_schema_evolution_row_policy.sh new file mode 100755 index 000000000000..459be5d63496 --- /dev/null +++ b/tests/queries/0_stateless/04147_iceberg_orc_schema_evolution_row_policy.sh @@ -0,0 +1,127 @@ +#!/usr/bin/env bash +# Tags: no-fasttest, no-parallel-replicas +# `no-parallel-replicas`: see comment in `04071_iceberg_orc_prewhere_crash.sh`. +# `StorageObjectStorageCluster` (used when `parallel_replicas_for_cluster_engines = 1`, +# default) does not delegate `supportsPrewhere` to its underlying configuration, +# so explicit `PREWHERE` against `icebergLocal` is rejected by the analyzer. +# +# Regression test for the schema-changed branch of the Iceberg ORC `PREWHERE` +# fallback path. When an Iceberg table has gone through schema evolution +# (e.g. `ALTER TABLE ... RENAME COLUMN`) and then receives a query against +# ORC data files with a row policy and/or `PREWHERE`, the source pipeline: +# +# 1) reads ORC data files using their FILE-side column names (the schema id +# that was current when the file was written), +# 2) applies a `schema_transform` `ExpressionTransform` that renames / +# typecasts / fills defaults to convert FILE-side names into the +# QUERY-side names of the current schema, +# 3) applies the fallback `FilterTransform`s for `row_level_filter` and +# `PREWHERE` (the format reader couldn't apply them because ORC doesn't +# support `PREWHERE`). +# +# Filter expressions are built by the planner against QUERY-side column names. +# They must therefore run AFTER `schema_transform`, otherwise they reference +# columns that aren't yet present in the block (the block still has FILE-side +# names) and the query fails with `NOT_FOUND_COLUMN_IN_BLOCK`. +# +# Without the fix: +# `Code: 10. DB::Exception: Not found column renamed_c0: in block c0 Int64...` +# +# This test deliberately uses ORC files only (created via the `icebergLocal` +# table function) so that every read goes through the strip-and-replay +# fallback. The mixed-format Parquet+ORC variant is covered by +# `04146_iceberg_orc_row_policy_prewhere.sh` for the non-schema-changed case. + +CUR_DIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd) +# shellcheck source=../shell_config.sh +. "$CUR_DIR"/../shell_config.sh + +ICEBERG_PATH="${CLICKHOUSE_USER_FILES}/lakehouses/${CLICKHOUSE_DATABASE}_orc_evol" +TEST_USER="${CLICKHOUSE_DATABASE}_user_evol" +TEST_POLICY="${CLICKHOUSE_DATABASE}_policy_evol" +TEST_TABLE="t_ice_orc_evol" + +rm -rf "${ICEBERG_PATH}" + +# Create an Iceberg table at format=Parquet (so the table-level `PREWHERE` +# check passes and we exercise the strip/fallback path) but populate it with +# ORC data files only via the `icebergLocal` table function. After this we +# have one ORC file under the table's first schema. +${CLICKHOUSE_CLIENT} --query " + SET allow_experimental_insert_into_iceberg = 1; + + CREATE TABLE ${TEST_TABLE} (c0 Int64, c1 String) + ENGINE = IcebergLocal('${ICEBERG_PATH}', 'Parquet'); + + INSERT INTO TABLE FUNCTION icebergLocal('${ICEBERG_PATH}', 'ORC', 'c0 Int64, c1 String') + SELECT number, toString(number) FROM numbers(100); +" + +# Bump the schema by renaming `c0` to `renamed_c0`. Existing data files still +# reference the column-id under its old name `c0` on disk; the current snapshot +# has the same column-id under the new name `renamed_c0`. Reads now go through +# the schema-changed path: format reader emits `c0`, then `schema_transform` +# renames it to `renamed_c0` downstream of the source. +${CLICKHOUSE_CLIENT} --query " + SET allow_insert_into_iceberg = 1; + ALTER TABLE ${TEST_TABLE} RENAME COLUMN c0 TO renamed_c0; +" + +# Set up a user and a row policy on the renamed column. The policy keeps rows +# where `renamed_c0 > 5`. +${CLICKHOUSE_CLIENT} --query "DROP USER IF EXISTS ${TEST_USER}" +${CLICKHOUSE_CLIENT} --query "CREATE USER ${TEST_USER} IDENTIFIED WITH plaintext_password BY 'rp_pwd_evol'" +${CLICKHOUSE_CLIENT} --query "GRANT SELECT ON *.* TO ${TEST_USER}" +${CLICKHOUSE_CLIENT} --query "DROP ROW POLICY IF EXISTS ${TEST_POLICY} ON ${TEST_TABLE}" +${CLICKHOUSE_CLIENT} --query "CREATE ROW POLICY ${TEST_POLICY} ON ${TEST_TABLE} FOR SELECT USING renamed_c0 > 5 TO ${TEST_USER}" + +# 1) `PREWHERE` on the renamed column where it is NOT in the SELECT list (so +# `remove_prewhere_column = true`). Without the fix the row-policy +# expression `renamed_c0 > 5` cannot evaluate against the ORC file's block +# because the block still has the file-side name `c0`. With the fix the +# fallback filter runs after `schema_transform`, so it sees `renamed_c0`. +# Expected: rows where `renamed_c0` in (10..99]; all are >5 so the policy +# is satisfied. count = 89. +${CLICKHOUSE_CLIENT} --user="${TEST_USER}" --password=rp_pwd_evol --query " + SELECT count() + FROM ${TEST_TABLE} + PREWHERE renamed_c0 > 10 + SETTINGS input_format_parquet_use_native_reader_v3 = 1, enable_analyzer = 1 +" + +# 2) `PREWHERE` overlaps the policy boundary (`renamed_c0 > 3`, policy +# `> 5`). Policy is the tighter filter — count must reflect +# `renamed_c0 > 5` (intersection), i.e. rows in (5..99]. count = 94. +${CLICKHOUSE_CLIENT} --user="${TEST_USER}" --password=rp_pwd_evol --query " + SELECT count() + FROM ${TEST_TABLE} + PREWHERE renamed_c0 > 3 + SETTINGS input_format_parquet_use_native_reader_v3 = 1, enable_analyzer = 1 +" + +# 3) Read `c1` with `PREWHERE` on the renamed column only (column gets removed +# after `PREWHERE`). Tests that `schema_transform` preserves both the +# `PREWHERE`-input column (so the `FilterTransform` can evaluate it) and +# the SELECT-list column. Expected: sum of integer values of `c1` for rows +# where `renamed_c0` in (10..99] = sum(11..99) = 4895. +${CLICKHOUSE_CLIENT} --user="${TEST_USER}" --password=rp_pwd_evol --query " + SELECT sum(toInt64(c1)) + FROM ${TEST_TABLE} + PREWHERE renamed_c0 > 10 + SETTINGS input_format_parquet_use_native_reader_v3 = 1, enable_analyzer = 1 +" + +# 4) Plain `SELECT` without `PREWHERE` — exercises the row policy alone on +# the renamed column under the schema-changed path. Expected: rows where +# `renamed_c0 > 5`, i.e. rows in (5..99]. count = 94. +${CLICKHOUSE_CLIENT} --user="${TEST_USER}" --password=rp_pwd_evol --query " + SELECT count() + FROM ${TEST_TABLE} + SETTINGS input_format_parquet_use_native_reader_v3 = 1, enable_analyzer = 1 +" + +# Cleanup +${CLICKHOUSE_CLIENT} --query "DROP ROW POLICY IF EXISTS ${TEST_POLICY} ON ${TEST_TABLE}" +${CLICKHOUSE_CLIENT} --query "DROP USER IF EXISTS ${TEST_USER}" +${CLICKHOUSE_CLIENT} --query "DROP TABLE IF EXISTS ${TEST_TABLE}" +rm -rf "${ICEBERG_PATH}" From bea6281c931a0613a0628da443f63598b829b500 Mon Sep 17 00:00:00 2001 From: robot-clickhouse Date: Thu, 11 Jun 2026 21:06:28 +0000 Subject: [PATCH 031/146] Backport #107097 to 26.3: Do not hold mutex across ZooKeeper reads in access entities refresh --- src/Access/ZooKeeperReplicator.cpp | 43 ++++++++++++++++++------------ src/Access/ZooKeeperReplicator.h | 10 ++++++- 2 files changed, 35 insertions(+), 18 deletions(-) diff --git a/src/Access/ZooKeeperReplicator.cpp b/src/Access/ZooKeeperReplicator.cpp index 784e200ef7b5..12440c31e855 100644 --- a/src/Access/ZooKeeperReplicator.cpp +++ b/src/Access/ZooKeeperReplicator.cpp @@ -304,6 +304,7 @@ bool ZooKeeperReplicator::removeEntity(const UUID & id, bool throw_if_not_exists if (!ok) return false; + std::lock_guard refresh_lock{refresh_mutex}; std::lock_guard lock{mutex}; removeEntityNoLock(id); return true; @@ -563,6 +564,13 @@ void ZooKeeperReplicator::refreshEntities(const zkutil::ZooKeeperPtr & zookeeper { LOG_DEBUG(&Poco::Logger::get(storage_name), "Refreshing entities list"); + /// Reading from ZooKeeper is one synchronous round trip per entity and must not happen under + /// `mutex`: findEntity (authentication, Context::getAccess) blocks on it, so holding it across + /// a slow refresh stalls every query on the server. `refresh_mutex` keeps the reads and the + /// publication of their results atomic with respect to other read->publish sequences, so this + /// snapshot can never overwrite the result of a newer read. + std::lock_guard refresh_lock{refresh_mutex}; + if (all) { /// It doesn't make sense to keep the queue because we will reread everything in this function. @@ -578,8 +586,6 @@ void ZooKeeperReplicator::refreshEntities(const zkutil::ZooKeeperPtr & zookeeper for (const String & entity_uuid_str : entity_uuid_strs) entity_uuids.emplace_back(parseUUID(entity_uuid_str)); - std::lock_guard lock{mutex}; - if (all) { /// all=true means we read & parse all access entities from ZooKeeper. @@ -589,17 +595,25 @@ void ZooKeeperReplicator::refreshEntities(const zkutil::ZooKeeperPtr & zookeeper if (auto entity = tryReadEntityFromZooKeeper(zookeeper, uuid)) entities.emplace_back(uuid, entity); } + + std::lock_guard lock{mutex}; memory_storage.setAll(entities); } else { /// all=false means we read & parse only new access entities from ZooKeeper. - memory_storage.removeAllExcept(entity_uuids); - for (const auto & uuid : entity_uuids) + std::vector new_uuids; { - if (!memory_storage.exists(uuid)) - refreshEntityNoLock(zookeeper, uuid); + std::lock_guard lock{mutex}; + memory_storage.removeAllExcept(entity_uuids); + for (const auto & uuid : entity_uuids) + { + if (!memory_storage.exists(uuid)) + new_uuids.push_back(uuid); + } } + for (const auto & uuid : new_uuids) + refreshEntityImpl(zookeeper, uuid); } LOG_DEBUG(&Poco::Logger::get(storage_name), "Refreshing entities list finished"); @@ -607,23 +621,18 @@ void ZooKeeperReplicator::refreshEntities(const zkutil::ZooKeeperPtr & zookeeper void ZooKeeperReplicator::refreshEntity(const zkutil::ZooKeeperPtr & zookeeper, const UUID & id) { - LOG_DEBUG(&Poco::Logger::get(storage_name), "Refreshing entity {}", toString(id)); - - auto entity = tryReadEntityFromZooKeeper(zookeeper, id); - - std::lock_guard lock{mutex}; - - if (entity) - setEntityNoLock(id, entity); - else - removeEntityNoLock(id); + std::lock_guard refresh_lock{refresh_mutex}; + refreshEntityImpl(zookeeper, id); } -void ZooKeeperReplicator::refreshEntityNoLock(const zkutil::ZooKeeperPtr & zookeeper, const UUID & id) +void ZooKeeperReplicator::refreshEntityImpl(const zkutil::ZooKeeperPtr & zookeeper, const UUID & id) { LOG_DEBUG(&Poco::Logger::get(storage_name), "Refreshing entity {}", toString(id)); auto entity = tryReadEntityFromZooKeeper(zookeeper, id); + + std::lock_guard lock{mutex}; + if (entity) setEntityNoLock(id, entity); else diff --git a/src/Access/ZooKeeperReplicator.h b/src/Access/ZooKeeperReplicator.h index 575d9456aeea..d2b6bec27df9 100644 --- a/src/Access/ZooKeeperReplicator.h +++ b/src/Access/ZooKeeperReplicator.h @@ -91,12 +91,20 @@ class ZooKeeperReplicator bool refresh(); void refreshEntities(const zkutil::ZooKeeperPtr & zookeeper, bool all); void refreshEntity(const zkutil::ZooKeeperPtr & zookeeper, const UUID & id); - void refreshEntityNoLock(const zkutil::ZooKeeperPtr & zookeeper, const UUID & id) TSA_REQUIRES(mutex); + void refreshEntityImpl(const zkutil::ZooKeeperPtr & zookeeper, const UUID & id) TSA_REQUIRES(refresh_mutex); AccessEntityPtr tryReadEntityFromZooKeeper(const zkutil::ZooKeeperPtr & zookeeper, const UUID & id) const; void setEntityNoLock(const UUID & id, const AccessEntityPtr & entity) TSA_REQUIRES(mutex); void removeEntityNoLock(const UUID & id) TSA_REQUIRES(mutex); + /// Serializes every "read from ZooKeeper -> publish into memory_storage" sequence (entity + /// refreshes and the cache updates of local writes), so that a publish based on an older + /// ZooKeeper read can never overwrite a publish based on a newer one. Held across ZooKeeper + /// round trips, therefore never taken by the read-only methods (findEntity etc.). + /// Lock order: cached_zookeeper_mutex -> refresh_mutex -> mutex. + std::mutex refresh_mutex; + + /// Guards memory_storage; held only for in-memory operations, never across ZooKeeper requests. mutable std::mutex mutex; }; From ec84887080406a474fddec60c29db7b19734f0bc Mon Sep 17 00:00:00 2001 From: robot-clickhouse Date: Thu, 11 Jun 2026 21:08:50 +0000 Subject: [PATCH 032/146] Backport #106731 to 26.3: Reimplement `getMetadataStorage()` for DiskEncrypted --- src/Disks/DiskEncrypted.h | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/src/Disks/DiskEncrypted.h b/src/Disks/DiskEncrypted.h index 5bf1a1c01e4a..915ec8e122ce 100644 --- a/src/Disks/DiskEncrypted.h +++ b/src/Disks/DiskEncrypted.h @@ -3,6 +3,8 @@ #include "config.h" #if USE_SSL +#include + #include #include #include @@ -372,7 +374,14 @@ class DiskEncrypted : public IDisk MetadataStoragePtr getMetadataStorage() override { - return std::make_shared(delegate->getMetadataStorage(), disk_path); + /// Cache the wrapper so that the metadata storage pointer is stable across calls (like `DiskObjectStorage`), + /// which code such as `isStoredOnTheSameDisk` relies on to hardlink parts instead of copying them; lazily, + /// because a non-object-storage delegate does not implement `getMetadataStorage`. + std::call_once(metadata_storage_init_flag, [this] + { + metadata_storage = std::make_shared(delegate->getMetadataStorage(), disk_path); + }); + return metadata_storage; } std::unordered_map getSerializedMetadata(const std::vector & paths) const override; @@ -412,6 +421,10 @@ class DiskEncrypted : public IDisk const String disk_absolute_path; MultiVersion current_settings; bool use_fake_transaction; + + /// Lazily-initialized stable wrapper returned by getMetadataStorage(); see the comment there. + std::once_flag metadata_storage_init_flag; + MetadataStoragePtr metadata_storage; }; } From 483cfb65d72808f3353f3cd17d86ba3dc3e9d251 Mon Sep 17 00:00:00 2001 From: Pedro Ferreira Date: Fri, 12 Jun 2026 09:20:26 +0000 Subject: [PATCH 033/146] Fix test --- .../04147_iceberg_orc_schema_evolution_row_policy.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/queries/0_stateless/04147_iceberg_orc_schema_evolution_row_policy.sh b/tests/queries/0_stateless/04147_iceberg_orc_schema_evolution_row_policy.sh index 459be5d63496..2f900950c8e2 100755 --- a/tests/queries/0_stateless/04147_iceberg_orc_schema_evolution_row_policy.sh +++ b/tests/queries/0_stateless/04147_iceberg_orc_schema_evolution_row_policy.sh @@ -48,7 +48,7 @@ rm -rf "${ICEBERG_PATH}" # ORC data files only via the `icebergLocal` table function. After this we # have one ORC file under the table's first schema. ${CLICKHOUSE_CLIENT} --query " - SET allow_experimental_insert_into_iceberg = 1; + SET allow_experimental_insert_into_iceberg = 1,input_format_parquet_use_native_reader_v3 = 1; CREATE TABLE ${TEST_TABLE} (c0 Int64, c1 String) ENGINE = IcebergLocal('${ICEBERG_PATH}', 'Parquet'); From f66d054d8a6dc6cb2e443565d69ef1093d88865d Mon Sep 17 00:00:00 2001 From: Pedro Ferreira Date: Fri, 12 Jun 2026 10:11:36 +0000 Subject: [PATCH 034/146] Missing include --- src/Storages/ObjectStorage/StorageObjectStorage.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Storages/ObjectStorage/StorageObjectStorage.cpp b/src/Storages/ObjectStorage/StorageObjectStorage.cpp index 5e334fcc1e6b..5dca76642965 100644 --- a/src/Storages/ObjectStorage/StorageObjectStorage.cpp +++ b/src/Storages/ObjectStorage/StorageObjectStorage.cpp @@ -1,5 +1,6 @@ #include +#include #include #include #include From 3724de7df89c58cbc59ea5d82ecb56f7395403e3 Mon Sep 17 00:00:00 2001 From: Pedro Ferreira Date: Fri, 12 Jun 2026 11:16:26 +0000 Subject: [PATCH 035/146] Fix test --- src/Storages/prepareReadingFromFormat.cpp | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/Storages/prepareReadingFromFormat.cpp b/src/Storages/prepareReadingFromFormat.cpp index d0d1c4ae3eeb..54ba1fc572da 100644 --- a/src/Storages/prepareReadingFromFormat.cpp +++ b/src/Storages/prepareReadingFromFormat.cpp @@ -260,19 +260,23 @@ Names filterTupleColumnsToRead(NamesAndTypesList & requested_columns) ReadFromFormatInfo updateFormatPrewhereInfo(const ReadFromFormatInfo & info, const FilterDAGInfoPtr & row_level_filter, const PrewhereInfoPtr & prewhere_info) { - chassert(prewhere_info); + chassert(prewhere_info || row_level_filter); - if (info.prewhere_info || info.row_level_filter) + if (info.prewhere_info) throw Exception(ErrorCodes::LOGICAL_ERROR, "updateFormatPrewhereInfo called more than once"); ReadFromFormatInfo new_info; new_info.prewhere_info = prewhere_info; + new_info.row_level_filter = row_level_filter; /// Removes columns that are only used as prewhere input. /// Adds prewhere outputs (the actual prewhere filter column is only added if /// !remove_prewhere_column; but there may also be subexpressions computed by prewhere /// expression and preserved for use further down the query pipeline). - new_info.format_header = SourceStepWithFilter::applyPrewhereActions(info.format_header, row_level_filter, prewhere_info); + /// If row_level_filter was already applied in a previous call, don't re-apply it; + /// only apply the new prewhere_info on top. + new_info.format_header = SourceStepWithFilter::applyPrewhereActions( + info.format_header, info.row_level_filter ? nullptr : row_level_filter, prewhere_info); /// We assume that any format that supports prewhere also supports subset of subcolumns, so we /// don't need to replace subcolumns with their nested columns etc. From c99644187c8da344e92b63084115976b028eebc7 Mon Sep 17 00:00:00 2001 From: Pedro Ferreira Date: Fri, 12 Jun 2026 12:37:40 +0000 Subject: [PATCH 036/146] Backport missing fix --- .../AggregateFunctionTimeseriesHelpers.cpp | 34 +++++++++++++++++-- 1 file changed, 32 insertions(+), 2 deletions(-) diff --git a/src/AggregateFunctions/TimeSeries/AggregateFunctionTimeseriesHelpers.cpp b/src/AggregateFunctions/TimeSeries/AggregateFunctionTimeseriesHelpers.cpp index 74736c861170..043842e17d33 100644 --- a/src/AggregateFunctions/TimeSeries/AggregateFunctionTimeseriesHelpers.cpp +++ b/src/AggregateFunctions/TimeSeries/AggregateFunctionTimeseriesHelpers.cpp @@ -71,7 +71,27 @@ Decimal64 normalizeParameter(const std::string & function_name, const std::strin UInt64 extractIntParameter(const std::string & function_name, const std::string & parameter_name, const Field & parameter_field) { - if (UInt64 int_value = 0; parameter_field.tryGet(int_value)) + if (parameter_field.getType() == Field::Types::Decimal64) + { + auto value = parameter_field.safeGet>(); + auto scale_multiplier = value.getScaleMultiplier(); + auto raw_value = value.getValue(); + if (scale_multiplier > 1 && raw_value % scale_multiplier != 0) + throw Exception(ErrorCodes::BAD_ARGUMENTS, + "Cannot convert Decimal64 {} parameter to integer for aggregate function {}", parameter_name, function_name); + return raw_value / scale_multiplier; + } + else if (parameter_field.getType() == Field::Types::Decimal32) + { + auto value = parameter_field.safeGet>(); + auto scale_multiplier = value.getScaleMultiplier(); + auto raw_value = value.getValue(); + if (scale_multiplier > 1 && raw_value % scale_multiplier != 0) + throw Exception(ErrorCodes::BAD_ARGUMENTS, + "Cannot convert Decimal32 {} parameter to integer for aggregate function {}", parameter_name, function_name); + return raw_value / scale_multiplier; + } + else if (UInt64 int_value = 0; parameter_field.tryGet(int_value)) { return int_value; } @@ -95,7 +115,17 @@ UInt64 extractIntParameter(const std::string & function_name, const std::string Float64 extractFloatParameter(const std::string & function_name, const std::string & parameter_name, const Field & parameter_field) { - if (Float64 float_value = 0; parameter_field.tryGet(float_value)) + if (parameter_field.getType() == Field::Types::Decimal64) + { + auto value = parameter_field.safeGet>(); + return static_cast(value.getValue()) / static_cast(value.getScaleMultiplier()); + } + else if (parameter_field.getType() == Field::Types::Decimal32) + { + auto value = parameter_field.safeGet>(); + return static_cast(value.getValue()) / static_cast(value.getScaleMultiplier()); + } + else if (Float64 float_value = 0; parameter_field.tryGet(float_value)) { return float_value; } From e3d6c73dd18f1a3550452daa3d661293ff78e312 Mon Sep 17 00:00:00 2001 From: robot-clickhouse Date: Fri, 12 Jun 2026 13:30:39 +0000 Subject: [PATCH 037/146] Backport #106395 to 26.3: Arrow IPC: fix heap out-of-bounds reads and structural validation gaps in the column reader --- .../Formats/Impl/ArrowBlockInputFormat.cpp | 5 + .../Formats/Impl/ArrowColumnToCHColumn.cpp | 648 ++++++++++++++++-- .../Formats/Impl/ArrowColumnToCHColumn.h | 7 + .../Formats/Impl/ORCBlockInputFormat.cpp | 5 + src/Processors/Sources/ArrowFlightSource.cpp | 5 + .../DataLakes/DeltaLake/TableChanges.cpp | 5 + .../04307_arrow_oob_reads.reference | 7 + .../0_stateless/04307_arrow_oob_reads.sh | 113 +++ .../04308_arrow_oob_reads_value.reference | 9 + .../04308_arrow_oob_reads_value.sh | 114 +++ .../04309_arrow_oob_reads_offsets.reference | 15 + .../04309_arrow_oob_reads_offsets.sh | 229 +++++++ ...04310_arrow_oob_reads_arithmetic.reference | 2 + .../04310_arrow_oob_reads_arithmetic.sh | 89 +++ .../04326_arrow_oob_neg_length.reference | 1 + .../0_stateless/04326_arrow_oob_neg_length.sh | 69 ++ .../04327_arrow_oob_list_offsets.reference | 5 + .../04327_arrow_oob_list_offsets.sh | 114 +++ ...28_arrow_oob_nested_child_length.reference | 7 + .../04328_arrow_oob_nested_child_length.sh | 126 ++++ .../04329_arrow_oob_struct_fields.reference | 6 + .../04329_arrow_oob_struct_fields.sh | 140 ++++ .../04330_arrow_oob_declared_length.reference | 9 + .../04330_arrow_oob_declared_length.sh | 144 ++++ .../04331_arrow_oob_validity_bitmap.reference | 4 + .../04331_arrow_oob_validity_bitmap.sh | 141 ++++ 26 files changed, 1963 insertions(+), 56 deletions(-) create mode 100644 tests/queries/0_stateless/04307_arrow_oob_reads.reference create mode 100755 tests/queries/0_stateless/04307_arrow_oob_reads.sh create mode 100644 tests/queries/0_stateless/04308_arrow_oob_reads_value.reference create mode 100755 tests/queries/0_stateless/04308_arrow_oob_reads_value.sh create mode 100644 tests/queries/0_stateless/04309_arrow_oob_reads_offsets.reference create mode 100755 tests/queries/0_stateless/04309_arrow_oob_reads_offsets.sh create mode 100644 tests/queries/0_stateless/04310_arrow_oob_reads_arithmetic.reference create mode 100755 tests/queries/0_stateless/04310_arrow_oob_reads_arithmetic.sh create mode 100644 tests/queries/0_stateless/04326_arrow_oob_neg_length.reference create mode 100755 tests/queries/0_stateless/04326_arrow_oob_neg_length.sh create mode 100644 tests/queries/0_stateless/04327_arrow_oob_list_offsets.reference create mode 100755 tests/queries/0_stateless/04327_arrow_oob_list_offsets.sh create mode 100644 tests/queries/0_stateless/04328_arrow_oob_nested_child_length.reference create mode 100755 tests/queries/0_stateless/04328_arrow_oob_nested_child_length.sh create mode 100644 tests/queries/0_stateless/04329_arrow_oob_struct_fields.reference create mode 100755 tests/queries/0_stateless/04329_arrow_oob_struct_fields.sh create mode 100644 tests/queries/0_stateless/04330_arrow_oob_declared_length.reference create mode 100755 tests/queries/0_stateless/04330_arrow_oob_declared_length.sh create mode 100644 tests/queries/0_stateless/04331_arrow_oob_validity_bitmap.reference create mode 100755 tests/queries/0_stateless/04331_arrow_oob_validity_bitmap.sh diff --git a/src/Processors/Formats/Impl/ArrowBlockInputFormat.cpp b/src/Processors/Formats/Impl/ArrowBlockInputFormat.cpp index e155c74bbac0..bdce0755d54e 100644 --- a/src/Processors/Formats/Impl/ArrowBlockInputFormat.cpp +++ b/src/Processors/Formats/Impl/ArrowBlockInputFormat.cpp @@ -93,6 +93,11 @@ Chunk ArrowBlockInputFormat::read() throw Exception(ErrorCodes::CANNOT_READ_ALL_DATA, "Error while reading batch of Arrow data: {}", batch_result.status().ToString()); + /// Validate validity bitmaps before building the table: Table::FromRecordBatches computes + /// each column's null_count, and Arrow derives an unknown FieldNode null_count by scanning + /// the bitmap over the declared length, which reads out of bounds on a truncated bitmap. + ArrowColumnToCHColumn::checkRecordBatchValidityBitmaps(**batch_result); + auto table_result = arrow::Table::FromRecordBatches({*batch_result}); if (!table_result.ok()) throw Exception(ErrorCodes::CANNOT_READ_ALL_DATA, diff --git a/src/Processors/Formats/Impl/ArrowColumnToCHColumn.cpp b/src/Processors/Formats/Impl/ArrowColumnToCHColumn.cpp index 8d8e58b89a9e..83d1e7aead9d 100644 --- a/src/Processors/Formats/Impl/ArrowColumnToCHColumn.cpp +++ b/src/Processors/Formats/Impl/ArrowColumnToCHColumn.cpp @@ -86,6 +86,220 @@ static bool emptyTimezoneAsUTC(const std::string & format_name, const FormatSett return format_name == "Parquet" && format_settings.parquet.local_time_as_utc; } +namespace +{ + +/// Validate that the validity bitmap (buffers[0]) covers all declared rows. +/// We deliberately do NOT gate on null_count(): for an array produced by Arrow's +/// Slice/Flatten the null count is kUnknownNullCount, and calling null_count() +/// triggers a CountSetBits scan over buffers[0] across [offset, offset+length), +/// which reads out of bounds when the bitmap is truncated, the very thing we are +/// trying to validate. Instead we validate the buffer size whenever a bitmap is +/// present (a present-but-undersized bitmap is always malformed), and skip when it +/// is absent (Arrow reports null_count == 0 for a missing bitmap without scanning). +/// This must run before any null_count()/IsNull()/IsValid() call, including the +/// implicit null_count() in arrow::ChunkedArray's constructor. +void checkValidityBitmap(const arrow::Array & chunk, const String & column_name) +{ + if (unlikely(chunk.length() < 0 || chunk.offset() < 0)) + throw Exception( + ErrorCodes::INCORRECT_DATA, + "Arrow array has negative length or offset for column '{}': length={}, offset={}", + column_name, chunk.length(), chunk.offset()); + const auto & buffer = chunk.data()->buffers[0]; + if (!buffer) + return; + const size_t buffer_size = static_cast(buffer->size()); + const size_t count = static_cast(chunk.offset()) + static_cast(chunk.length()); + const size_t required = count / 8 + (count % 8 != 0 ? 1 : 0); + if (unlikely(buffer_size < required)) + throw Exception( + ErrorCodes::INCORRECT_DATA, + "Arrow validity bitmap too small for column '{}': {} bytes available, {} required", + column_name, buffer_size, required); +} + +/// Validate that buffers[1] of an Arrow array chunk is large enough to hold +/// elem_size * (offset + length) bytes, then throw INCORRECT_DATA if not. +/// All readers that access buffers[1] directly must call one of these helpers +/// before touching any raw pointer, so the check can never be silently omitted. +void checkArrowBuffer(const arrow::Array & chunk, size_t elem_size, const String & column_name) +{ + if (unlikely(chunk.length() < 0 || chunk.offset() < 0)) + throw Exception( + ErrorCodes::INCORRECT_DATA, + "Arrow array has negative length or offset for column '{}': length={}, offset={}", + column_name, chunk.length(), chunk.offset()); + const auto & buffer = chunk.data()->buffers[1]; + const size_t buffer_size = buffer ? static_cast(buffer->size()) : 0; + const size_t count = static_cast(chunk.offset()) + static_cast(chunk.length()); + size_t required = 0; + if (unlikely(__builtin_mul_overflow(elem_size, count, &required))) + throw Exception( + ErrorCodes::INCORRECT_DATA, + "Arrow buffer size overflow for column '{}': element size {} × {} elements", + column_name, elem_size, count); + if (unlikely(buffer_size < required)) + throw Exception( + ErrorCodes::INCORRECT_DATA, + "Arrow buffer too small for column '{}': {} bytes available, {} required", + column_name, buffer_size, required); + checkValidityBitmap(chunk, column_name); +} + +/// Typed wrapper: validates and returns a pointer to the first element at chunk.offset(). +/// Use for bulk-copy readers. Per-element Value() readers call this for the validation +/// side-effect only (discarding the returned pointer). +template +const T * getValidatedBuffer(const arrow::Array & chunk, const String & column_name) +{ + checkArrowBuffer(chunk, sizeof(T), column_name); + const auto * buf = chunk.data()->buffers[1].get(); + if (!buf) + return nullptr; /// empty chunk (offset == 0, length == 0); no elements will be accessed + return reinterpret_cast(buf->data()) + chunk.offset(); +} + +/// Boolean arrays pack 8 values per byte starting at bit chunk.offset(), so the +/// required byte count uses ceiling division rather than a simple multiply. +void checkBooleanBuffer(const arrow::BooleanArray & chunk, const String & column_name) +{ + if (unlikely(chunk.length() < 0 || chunk.offset() < 0)) + throw Exception( + ErrorCodes::INCORRECT_DATA, + "Arrow array has negative length or offset for column '{}': length={}, offset={}", + column_name, chunk.length(), chunk.offset()); + const auto & buffer = chunk.data()->buffers[1]; + const size_t buffer_size = buffer ? static_cast(buffer->size()) : 0; + const size_t count = static_cast(chunk.offset()) + static_cast(chunk.length()); + const size_t required = count / 8 + (count % 8 != 0 ? 1 : 0); /// overflow-safe ceiling division + if (unlikely(buffer_size < required)) + throw Exception( + ErrorCodes::INCORRECT_DATA, + "Arrow buffer too small for column '{}': {} bytes available, {} required", + column_name, buffer_size, required); + checkValidityBitmap(chunk, column_name); +} + +/// Cast array to ArrowNumericArray and validate the data buffer is large enough +/// for all declared rows. Element size is derived from ArrowNumericArray::value_type, +/// so this only compiles for typed numeric arrays (Date32, Date64, Timestamp, +/// Time32/64, HalfFloat, and the plain numeric types). +/// Use checkedCastFixedSizeBinary for FixedSizeBinary arrays, checkedCastBool for BooleanArray. +template +const ArrowNumericArray & checkedCast(const arrow::Array & array, const String & column_name) +{ + static_assert( + requires { typename ArrowNumericArray::value_type; }, + "use checkedCastFixedSizeBinary for FixedSizeBinary arrays or checkedCastBool for BooleanArray"); + const auto & typed = assert_cast(array); + checkArrowBuffer(typed, sizeof(typename ArrowNumericArray::value_type), column_name); + return typed; +} + +/// Cast to a view array type (StringViewArray or BinaryViewArray) and validate +/// that the view-struct buffer (buffers[1]) has enough 16-byte view structs for +/// all declared rows. The actual string bytes live in variadic buffers[2..N] +/// and are validated individually via GetView(). +template +const ArrowViewArray & checkedCastView(const arrow::Array & array, const String & column_name) +{ + const auto & typed = assert_cast(array); + checkArrowBuffer(typed, sizeof(arrow::BinaryViewType::c_type), column_name); + return typed; +} + +/// Validate that the validity bitmap (buffers[0]) covers all declared rows. +/// Validate that the offsets buffer (buffers[1]) of a BinaryArray or LargeBinaryArray +/// holds at least (offset + length + 1) entries. One more than length is required +/// because value_length(i) = offset[i+1] - offset[i], so the last element needs +/// offset[length]. This must be checked before any call to value_offset(i) or +/// GetView(i) to prevent an over-read of the offsets buffer. +template +void checkBinaryOffsetsBuffer(const ArrowBinaryArray & chunk, const String & column_name) +{ + if (unlikely(chunk.length() < 0 || chunk.offset() < 0)) + throw Exception( + ErrorCodes::INCORRECT_DATA, + "Arrow array has negative length or offset for column '{}': length={}, offset={}", + column_name, chunk.length(), chunk.offset()); + const auto & buffer = chunk.data()->buffers[1]; + const size_t buffer_size = buffer ? static_cast(buffer->size()) : 0; + const size_t count_plus_one = static_cast(chunk.offset()) + static_cast(chunk.length()) + 1; + size_t required = 0; + if (unlikely(__builtin_mul_overflow(sizeof(typename ArrowBinaryArray::offset_type), count_plus_one, &required))) + throw Exception( + ErrorCodes::INCORRECT_DATA, + "Arrow offsets buffer size overflow for column '{}': {} entries", + column_name, count_plus_one); + if (unlikely(buffer_size < required)) + throw Exception( + ErrorCodes::INCORRECT_DATA, + "Arrow buffer too small for column '{}': {} bytes available, {} required", + column_name, buffer_size, required); + checkValidityBitmap(chunk, column_name); +} + +/// Cast to a FixedSizeBinaryArray subclass (plain FixedSizeBinaryArray, Decimal128Array, +/// Decimal256Array) and validate the data buffer using the runtime byte_width(). +template +const ArrowFixedSizeBinaryArray & checkedCastFixedSizeBinary(const arrow::Array & array, const String & column_name) +{ + const auto & typed = assert_cast(array); + checkArrowBuffer(typed, static_cast(typed.byte_width()), column_name); + return typed; +} + +/// Cast to BooleanArray and validate the bit-packed data buffer. +const arrow::BooleanArray & checkedCastBool(const arrow::Array & array, const String & column_name) +{ + const auto & typed = assert_cast(array); + checkBooleanBuffer(typed, column_name); + return typed; +} + +/// Recursively validate that every validity bitmap (buffers[0]) in an array and its children / +/// dictionary covers the declared rows, WITHOUT computing null_count. Must run before any Arrow +/// operation that computes an unknown null_count (kUnknownNullCount = -1) by scanning the bitmap +/// over the declared length, which reads out of bounds when the bitmap is truncated. +void checkArrayDataValidityBitmaps(const arrow::ArrayData & data, const String & column_name) +{ + if (unlikely(data.length < 0 || data.offset < 0)) + throw Exception( + ErrorCodes::INCORRECT_DATA, + "Arrow array has negative length or offset for column '{}': length={}, offset={}", + column_name, data.length, data.offset); + if (!data.buffers.empty() && data.buffers[0]) + { + const size_t buffer_size = static_cast(data.buffers[0]->size()); + const size_t count = static_cast(data.offset) + static_cast(data.length); + const size_t required = count / 8 + (count % 8 != 0 ? 1 : 0); + if (unlikely(buffer_size < required)) + throw Exception( + ErrorCodes::INCORRECT_DATA, + "Arrow validity bitmap too small for column '{}': {} bytes available, {} required", + column_name, buffer_size, required); + } + for (const auto & child : data.child_data) + if (child) + checkArrayDataValidityBitmaps(*child, column_name); + if (data.dictionary) + checkArrayDataValidityBitmaps(*data.dictionary, column_name); +} + +/// Validate every chunk with `validate` before the caller reserves column memory based on +/// arrow_column->length(). Without this, an inflated or buffer-inconsistent declared length +/// makes reserve() throw CANNOT_ALLOCATE_MEMORY instead of a clean INCORRECT_DATA from the +/// per-chunk validator. Use the same validator the reader's main loop uses. +template +void validateChunksBeforeReserve(const arrow::ChunkedArray & arrow_column, Validator && validate) +{ + for (int chunk_i = 0, num_chunks = arrow_column.num_chunks(); chunk_i < num_chunks; ++chunk_i) + validate(*arrow_column.chunk(chunk_i)); +} + +} + /// Inserts numeric data right into internal column data to reduce an overhead template > static ColumnWithTypeAndName readColumnWithNumericData(const std::shared_ptr & arrow_column, const String & column_name) @@ -93,6 +307,11 @@ static ColumnWithTypeAndName readColumnWithNumericData(const std::shared_ptr>(); auto internal_column = internal_type->createColumn(); auto & column_data = static_cast(*internal_column).getData(); + + /// Validate every chunk's data buffer before reserving column memory: an inflated + /// (or buffer-inconsistent) declared length must surface as INCORRECT_DATA rather than + /// a huge reserve() that throws CANNOT_ALLOCATE_MEMORY. + validateChunksBeforeReserve(*arrow_column, [&](const arrow::Array & chunk) { checkArrowBuffer(chunk, sizeof(NumericType), column_name); }); column_data.reserve(arrow_column->length()); for (int chunk_i = 0, num_chunks = arrow_column->num_chunks(); chunk_i < num_chunks; ++chunk_i) @@ -102,8 +321,7 @@ static ColumnWithTypeAndName readColumnWithNumericData(const std::shared_ptr buffer = chunk->data()->buffers[1]; - const auto * raw_data = reinterpret_cast(buffer->data()) + chunk->offset(); + const auto * raw_data = getValidatedBuffer(*chunk, column_name); column_data.insert_assume_reserved(raw_data, raw_data + chunk->length()); } return {std::move(internal_column), std::move(internal_type), column_name}; @@ -135,11 +353,16 @@ static ColumnWithTypeAndName readColumnWithStringData(const std::shared_ptrnum_chunks(); chunk_i < num_chunks; ++chunk_i) { - ArrowArray & chunk = dynamic_cast(*(arrow_column->chunk(chunk_i))); + const ArrowArray & chunk = dynamic_cast(*(arrow_column->chunk(chunk_i))); std::shared_ptr buffer = chunk.value_data(); const size_t chunk_length = chunk.length(); const size_t buffer_size = buffer ? static_cast(buffer->size()) : 0; + /// Validate the offsets buffer before the first value_offset(i) call. + /// The offsets array needs (offset + length + 1) entries because + /// value_length(i) = offset[i+1] - offset[i]. + checkBinaryOffsetsBuffer(chunk, column_name); + if (chunk.null_count() == 0) { for (size_t i = 0; i < chunk_length; ++i) @@ -181,7 +404,7 @@ static ColumnWithTypeAndName readColumnWithStringData(const std::shared_ptrnum_chunks(); chunk_i < num_chunks; ++chunk_i) { - ArrowArray & chunk = dynamic_cast(*(arrow_column->chunk(chunk_i))); + const ArrowArray & chunk = dynamic_cast(*(arrow_column->chunk(chunk_i))); std::shared_ptr buffer = chunk.value_data(); const size_t chunk_length = chunk.length(); @@ -228,16 +451,57 @@ static ColumnWithTypeAndName readColumnWithJSONData( auto internal_column = internal_type->createColumn(); auto & column_object = assert_cast(*internal_column); + /// Validate every chunk's offsets buffer AND each row's data range before reserving column + /// memory, so a forged (or buffer-inconsistent) declared length, or valid offsets metadata + /// over a truncated data buffer, is rejected as INCORRECT_DATA rather than a huge reserve(). + /// The String/Binary reader validates in its first pass for the same reason. + for (int chunk_i = 0, num_chunks = arrow_column->num_chunks(); chunk_i < num_chunks; ++chunk_i) + { + const ArrowArray & chunk = dynamic_cast(*(arrow_column->chunk(chunk_i))); + checkBinaryOffsetsBuffer(chunk, column_name); + const size_t data_buf_size = chunk.value_data() ? static_cast(chunk.value_data()->size()) : 0; + const bool has_nulls = chunk.null_count() != 0; + for (size_t row_i = 0, num_rows = chunk.length(); row_i < num_rows; ++row_i) + { + if (has_nulls && chunk.IsNull(row_i)) + continue; + const size_t safe_offset = static_cast(chunk.value_offset(row_i)); + const size_t safe_length = static_cast(chunk.value_length(row_i)); + if (unlikely(safe_offset > data_buf_size || safe_length > data_buf_size - safe_offset)) + throw Exception( + ErrorCodes::INCORRECT_DATA, + "Arrow BinaryArray offsets exceed data buffer bounds for column '{}': " + "row {} has offset {} and length {} but buffer is {} bytes", + column_name, row_i, chunk.value_offset(row_i), chunk.value_length(row_i), data_buf_size); + } + } + column_object.reserve(arrow_column->length()); for (int chunk_i = 0, num_chunks = arrow_column->num_chunks(); chunk_i < num_chunks; ++chunk_i) { - const ArrowArray & chunk = dynamic_cast(*(arrow_column->chunk(chunk_i))); + const ArrowArray & chunk = dynamic_cast(*(arrow_column->chunk(chunk_i))); + checkBinaryOffsetsBuffer(chunk, column_name); + + const size_t data_buf_size = chunk.value_data() ? static_cast(chunk.value_data()->size()) : 0; + + const auto validate_row = [&](size_t row_i) + { + const size_t safe_offset = static_cast(chunk.value_offset(row_i)); + const size_t safe_length = static_cast(chunk.value_length(row_i)); + if (unlikely(safe_offset > data_buf_size || safe_length > data_buf_size - safe_offset)) + throw Exception( + ErrorCodes::INCORRECT_DATA, + "Arrow BinaryArray offsets exceed data buffer bounds for column '{}': " + "row {} has offset {} and length {} but buffer is {} bytes", + column_name, row_i, chunk.value_offset(row_i), chunk.value_length(row_i), data_buf_size); + }; if (chunk.null_count() == 0) { for (size_t row_i = 0, num_rows = chunk.length(); row_i < num_rows; ++row_i) { + validate_row(row_i); auto view = chunk.GetView(row_i); ReadBufferFromMemory rb(view); serialization->deserializeTextJSON(column_object, rb, format_settings); @@ -252,6 +516,7 @@ static ColumnWithTypeAndName readColumnWithJSONData( column_object.insertDefault(); continue; } + validate_row(row_i); auto view = chunk.GetView(row_i); ReadBufferFromMemory rb(view); serialization->deserializeTextJSON(column_object, rb, format_settings); @@ -269,11 +534,15 @@ static ColumnWithTypeAndName readColumnWithFixedStringData(const std::shared_ptr auto internal_type = std::make_shared(fixed_len); auto internal_column = internal_type->createColumn(); PaddedPODArray & column_chars = assert_cast(*internal_column).getChars(); + validateChunksBeforeReserve(*arrow_column, [&](const arrow::Array & chunk) { checkedCastFixedSizeBinary(chunk, column_name); }); column_chars.reserve(arrow_column->length() * fixed_len); for (int chunk_i = 0, num_chunks = arrow_column->num_chunks(); chunk_i < num_chunks; ++chunk_i) { - arrow::FixedSizeBinaryArray & chunk = dynamic_cast(*(arrow_column->chunk(chunk_i))); + const auto & chunk = checkedCastFixedSizeBinary(*(arrow_column->chunk(chunk_i)), column_name); + /// An empty chunk can leave the data buffer null; skip before reading raw_values(). + if (chunk.length() == 0) + continue; const uint8_t * raw_data = chunk.raw_values(); column_chars.insert_assume_reserved(raw_data, raw_data + fixed_len * chunk.length()); } @@ -295,11 +564,15 @@ static ColumnWithTypeAndName readColumnWithBigIntegerFromFixedBinaryData(const s auto internal_column = column_type->createColumn(); auto & data = assert_cast &>(*internal_column).getData(); + validateChunksBeforeReserve(*arrow_column, [&](const arrow::Array & chunk) { checkedCastFixedSizeBinary(chunk, column_name); }); data.reserve(arrow_column->length()); for (int chunk_i = 0, num_chunks = arrow_column->num_chunks(); chunk_i < num_chunks; ++chunk_i) { - arrow::FixedSizeBinaryArray & chunk = dynamic_cast(*(arrow_column->chunk(chunk_i))); + const auto & chunk = checkedCastFixedSizeBinary(*(arrow_column->chunk(chunk_i)), column_name); + /// An empty chunk can leave the data buffer null; skip before reading raw_values(). + if (chunk.length() == 0) + continue; const auto * raw_data = reinterpret_cast(chunk.raw_values()); data.insert_assume_reserved(raw_data, raw_data + chunk.length()); } @@ -313,17 +586,29 @@ static ColumnWithTypeAndName readColumnWithBigNumberFromBinaryData(const std::sh size_t total_size = 0; for (int chunk_i = 0, num_chunks = arrow_column->num_chunks(); chunk_i < num_chunks; ++chunk_i) { - auto & chunk = dynamic_cast(*(arrow_column->chunk(chunk_i))); + const auto & chunk = dynamic_cast(*(arrow_column->chunk(chunk_i))); + checkBinaryOffsetsBuffer(chunk, column_name); const size_t chunk_length = chunk.length(); + const size_t data_buf_size = chunk.value_data() ? static_cast(chunk.value_data()->size()) : 0; for (size_t i = 0; i != chunk_length; ++i) { + if (chunk.IsNull(i)) + continue; /// If at least one value size is not equal to the size if big integer, fallback to reading String column and further cast to result type. - if (!chunk.IsNull(i) && chunk.value_length(i) != sizeof(ValueType)) + if (chunk.value_length(i) != sizeof(ValueType)) return readColumnWithStringData(arrow_column, column_name); - - total_size += chunk_length; + /// Validate the data range before reserve(total_size): valid sizeof(ValueType) offsets + /// with a truncated data buffer must be rejected here, not after the allocation. + const size_t safe_offset = static_cast(chunk.value_offset(i)); + if (unlikely(safe_offset > data_buf_size || sizeof(ValueType) > data_buf_size - safe_offset)) + throw Exception( + ErrorCodes::INCORRECT_DATA, + "Arrow BinaryArray data buffer too small for column '{}': " + "row {} has offset {} but buffer is {} bytes", + column_name, i, chunk.value_offset(i), data_buf_size); } + total_size += chunk_length; } auto internal_column = column_type->createColumn(); @@ -332,13 +617,27 @@ static ColumnWithTypeAndName readColumnWithBigNumberFromBinaryData(const std::sh for (int chunk_i = 0, num_chunks = arrow_column->num_chunks(); chunk_i < num_chunks; ++chunk_i) { - auto & chunk = dynamic_cast(*(arrow_column->chunk(chunk_i))); + const auto & chunk = dynamic_cast(*(arrow_column->chunk(chunk_i))); + checkBinaryOffsetsBuffer(chunk, column_name); + const size_t data_buf_size = chunk.value_data() ? static_cast(chunk.value_data()->size()) : 0; for (size_t value_i = 0, length = static_cast(chunk.length()); value_i < length; ++value_i) { if (chunk.IsNull(value_i)) + { integer_column.insertDefault(); + } else - integer_column.insertData(chunk.Value(value_i).data(), chunk.Value(value_i).size()); + { + const size_t safe_offset = static_cast(chunk.value_offset(value_i)); + if (unlikely(safe_offset > data_buf_size || sizeof(ValueType) > data_buf_size - safe_offset)) + throw Exception( + ErrorCodes::INCORRECT_DATA, + "Arrow BinaryArray data buffer too small for column '{}': " + "row {} has offset {} but buffer is {} bytes", + column_name, value_i, chunk.value_offset(value_i), data_buf_size); + const auto * raw_data = reinterpret_cast(chunk.value_data()->data()) + safe_offset; + integer_column.insertData(raw_data, sizeof(ValueType)); + } } } return {std::move(internal_column), column_type, column_name}; @@ -349,14 +648,15 @@ static ColumnWithTypeAndName readColumnWithBooleanData(const std::shared_ptrcreateColumn(); auto & column_data = assert_cast &>(*internal_column).getData(); + validateChunksBeforeReserve(*arrow_column, [&](const arrow::Array & chunk) { checkedCastBool(chunk, column_name); }); column_data.reserve(arrow_column->length()); for (int chunk_i = 0, num_chunks = arrow_column->num_chunks(); chunk_i < num_chunks; ++chunk_i) { - arrow::BooleanArray & chunk = dynamic_cast(*(arrow_column->chunk(chunk_i))); - if (chunk.length() == 0) + if (arrow_column->chunk(chunk_i)->length() == 0) continue; + const auto & chunk = checkedCastBool(*(arrow_column->chunk(chunk_i)), column_name); for (size_t bool_i = 0; bool_i != static_cast(chunk.length()); ++bool_i) column_data.emplace_back(chunk.Value(bool_i)); } @@ -382,13 +682,13 @@ static ColumnWithTypeAndName readColumnWithDate32Data(const std::shared_ptrcreateColumn(); PaddedPODArray & column_data = assert_cast &>(*internal_column).getData(); + validateChunksBeforeReserve(*arrow_column, [&](const arrow::Array & chunk) { checkedCast(chunk, column_name); }); column_data.reserve(arrow_column->length()); for (int chunk_i = 0, num_chunks = arrow_column->num_chunks(); chunk_i < num_chunks; ++chunk_i) { - arrow::Date32Array & chunk = dynamic_cast(*(arrow_column->chunk(chunk_i))); + const auto & chunk = checkedCast(*(arrow_column->chunk(chunk_i)), column_name); - /// Check date range only when requested type is actually Date32 if (check_date_range) { for (size_t value_i = 0, length = static_cast(chunk.length()); value_i < length; ++value_i) @@ -416,8 +716,11 @@ static ColumnWithTypeAndName readColumnWithDate32Data(const std::shared_ptr buffer = chunk.data()->buffers[1]; - const auto * raw_data = reinterpret_cast(buffer->data()) + chunk.offset(); + /// getValidatedBuffer returns nullptr for an empty chunk (Arrow may leave buffers[1] + /// null when length == 0); skip it instead of dereferencing a null data buffer. + if (chunk.length() == 0) + continue; + const auto * raw_data = getValidatedBuffer(chunk, column_name); column_data.insert_assume_reserved(raw_data, raw_data + chunk.length()); } } @@ -430,11 +733,12 @@ static ColumnWithTypeAndName readColumnWithDate64Data(const std::shared_ptr(); auto internal_column = internal_type->createColumn(); auto & column_data = assert_cast &>(*internal_column).getData(); + validateChunksBeforeReserve(*arrow_column, [&](const arrow::Array & chunk) { checkedCast(chunk, column_name); }); column_data.reserve(arrow_column->length()); for (int chunk_i = 0, num_chunks = arrow_column->num_chunks(); chunk_i < num_chunks; ++chunk_i) { - auto & chunk = dynamic_cast(*(arrow_column->chunk(chunk_i))); + const auto & chunk = checkedCast(*(arrow_column->chunk(chunk_i)), column_name); for (size_t value_i = 0, length = static_cast(chunk.length()); value_i < length; ++value_i) { auto timestamp = static_cast(chunk.Value(value_i) / 1000); // Always? in ms @@ -454,11 +758,12 @@ static ColumnWithTypeAndName readColumnWithTimestampData(const std::shared_ptr(scale, timezone); auto internal_column = internal_type->createColumn(); auto & column_data = assert_cast &>(*internal_column).getData(); + validateChunksBeforeReserve(*arrow_column, [&](const arrow::Array & chunk) { checkedCast(chunk, column_name); }); column_data.reserve(arrow_column->length()); for (int chunk_i = 0, num_chunks = arrow_column->num_chunks(); chunk_i < num_chunks; ++chunk_i) { - const auto & chunk = dynamic_cast(*(arrow_column->chunk(chunk_i))); + const auto & chunk = checkedCast(*(arrow_column->chunk(chunk_i)), column_name); for (size_t value_i = 0, length = static_cast(chunk.length()); value_i < length; ++value_i) { column_data.emplace_back(chunk.Value(value_i)); @@ -474,14 +779,15 @@ static ColumnWithTypeAndName readColumnWithTimeData(const std::shared_ptr(scale); auto internal_column = internal_type->createColumn(); + validateChunksBeforeReserve(*arrow_column, [&](const arrow::Array & chunk) { checkedCast(chunk, column_name); }); internal_column->reserve(arrow_column->length()); for (int chunk_i = 0, num_chunks = arrow_column->num_chunks(); chunk_i < num_chunks; ++chunk_i) { - auto & chunk = dynamic_cast(*(arrow_column->chunk(chunk_i))); - if (chunk.length() == 0) + if (arrow_column->chunk(chunk_i)->length() == 0) continue; + const auto & chunk = checkedCast(*(arrow_column->chunk(chunk_i)), column_name); for (size_t value_i = 0, length = static_cast(chunk.length()); value_i < length; ++value_i) { assert_cast(*internal_column).insertValue(chunk.Value(value_i)); @@ -507,11 +813,12 @@ static ColumnWithTypeAndName readColumnWithDecimalDataImpl(const std::shared_ptr auto internal_column = internal_type->createColumn(); auto & column = assert_cast &>(*internal_column); auto & column_data = column.getData(); + validateChunksBeforeReserve(*arrow_column, [&](const arrow::Array & chunk) { checkedCastFixedSizeBinary(chunk, column_name); }); column_data.reserve(arrow_column->length()); for (int chunk_i = 0, num_chunks = arrow_column->num_chunks(); chunk_i < num_chunks; ++chunk_i) { - auto & chunk = dynamic_cast(*(arrow_column->chunk(chunk_i))); + const auto & chunk = checkedCastFixedSizeBinary(*(arrow_column->chunk(chunk_i)), column_name); for (size_t value_i = 0, length = static_cast(chunk.length()); value_i < length; ++value_i) { column_data.emplace_back(chunk.IsNull(value_i) ? DecimalType(0) : *reinterpret_cast(chunk.Value(value_i))); // TODO: copy column @@ -524,11 +831,12 @@ static ColumnWithTypeAndName readColumnWithFloat16Data(const std::shared_ptrgetData(); + validateChunksBeforeReserve(*arrow_column, [&](const arrow::Array & chunk) { checkedCast(chunk, column_name); }); column_data.reserve(arrow_column->length()); for (int chunk_i = 0, num_chunks = arrow_column->num_chunks(); chunk_i < num_chunks; ++chunk_i) { - auto & chunk = dynamic_cast(*(arrow_column->chunk(chunk_i))); + const auto & chunk = checkedCast(*(arrow_column->chunk(chunk_i)), column_name); for (size_t value_i = 0, length = static_cast(chunk.length()); value_i < length; ++value_i) column_data.emplace_back(chunk.IsNull(value_i) ? 0 : convertFloat16ToFloat32(chunk.Value(value_i))); } @@ -551,8 +859,14 @@ static ColumnWithTypeAndName readColumnWithDecimalData(const std::shared_ptr & arrow_column) +static ColumnPtr readByteMapFromArrowColumn(const std::shared_ptr & arrow_column, const String & column_name) { + /// Validate every chunk's validity bitmap before allocating the null map, so a forged length + /// whose validity bitmap is too small is rejected as INCORRECT_DATA instead of triggering a + /// huge null-map allocation. + for (int chunk_i = 0; chunk_i != arrow_column->num_chunks(); ++chunk_i) + checkValidityBitmap(*arrow_column->chunk(chunk_i), column_name); + if (!arrow_column->null_count()) return ColumnUInt8::create(arrow_column->length(), static_cast(0)); @@ -576,7 +890,8 @@ static ColumnWithTypeAndName readColumnWithGeoData(const std::shared_ptrcreateColumn(); for (int chunk_i = 0, num_chunks = arrow_column->num_chunks(); chunk_i < num_chunks; ++chunk_i) { - arrow::BinaryArray & chunk = dynamic_cast(*(arrow_column->chunk(chunk_i))); + const auto & chunk = dynamic_cast(*(arrow_column->chunk(chunk_i))); + checkBinaryOffsetsBuffer(chunk, column_name); std::shared_ptr buffer = chunk.value_data(); const size_t chunk_length = chunk.length(); const size_t buffer_size = buffer ? static_cast(buffer->size()) : 0; @@ -638,8 +953,36 @@ struct ArrowOffsetArray using type = arrow::Int64Array; }; +/// Validate that an Arrow list offsets array is non-negative and monotonically non-decreasing. +/// Must run before arrow's Flatten() slices the values array using offset[0]..offset[length]: +/// a decreasing pair such as [64, 0] otherwise reaches ArrayData::Slice with an out-of-range +/// length and fails deep inside Arrow instead of being rejected here as INCORRECT_DATA. +template +static void checkListOffsetsMonotonic(const OffsetArray & arrow_offsets, const String & column_name) +{ + if (arrow_offsets.length() == 0) + return; + int64_t previous_offset = arrow_offsets.Value(0); + if (unlikely(previous_offset < 0)) + throw Exception( + ErrorCodes::INCORRECT_DATA, + "Arrow List offsets contain a negative value for column '{}': offset[0]={}", + column_name, previous_offset); + for (int64_t i = 1; i < arrow_offsets.length(); ++i) + { + int64_t offset = arrow_offsets.Value(i); + if (unlikely(offset < previous_offset)) + throw Exception( + ErrorCodes::INCORRECT_DATA, + "Arrow List offsets are not monotonically non-decreasing for column '{}': " + "offset[{}]={} < offset[{}]={}", + column_name, i, offset, i - 1, previous_offset); + previous_offset = offset; + } +} + template -static ColumnPtr readOffsetsFromArrowListColumn(const std::shared_ptr & arrow_column) +static ColumnPtr readOffsetsFromArrowListColumn(const std::shared_ptr & arrow_column, const String & column_name) { auto offsets_column = ColumnUInt64::create(); ColumnArray::Offsets & offsets_data = assert_cast &>(*offsets_column).getData(); @@ -647,9 +990,11 @@ static ColumnPtr readOffsetsFromArrowListColumn(const std::shared_ptrnum_chunks(); chunk_i < num_chunks; ++chunk_i) { - ArrowListArray & list_chunk = dynamic_cast(*(arrow_column->chunk(chunk_i))); + auto & list_chunk = dynamic_cast(*(arrow_column->chunk(chunk_i))); auto arrow_offsets_array = list_chunk.offsets(); - auto & arrow_offsets = dynamic_cast::type &>(*arrow_offsets_array); + /// The offsets array is a numeric Int32/Int64 array, validate its buffer before Value() calls. + using OffsetArray = typename ArrowOffsetArray::type; + const auto & arrow_offsets = checkedCast(*arrow_offsets_array, column_name); /* * CH uses element size as "offsets", while arrow uses actual offsets as offsets. @@ -665,12 +1010,15 @@ static ColumnPtr readOffsetsFromArrowListColumn(const std::shared_ptr(offset - previous_offset); previous_offset = offset; offsets_data.emplace_back(offsets_data.back() + elements); } @@ -706,6 +1054,7 @@ static ColumnWithTypeAndName readColumnWithIndexesDataImpl(std::shared_ptr>(); auto internal_column = internal_type->createColumn(); auto & column_data = static_cast(*internal_column).getData(); + validateChunksBeforeReserve(*arrow_column, [&](const arrow::Array & chunk) { checkArrowBuffer(chunk, sizeof(NumericType), column_name); }); column_data.reserve(arrow_column->length()); NumericType shift = is_nullable ? 2 : 1; @@ -716,8 +1065,7 @@ static ColumnWithTypeAndName readColumnWithIndexesDataImpl(std::shared_ptr buffer = chunk->data()->buffers[1]; - const auto * data = reinterpret_cast(buffer->data()) + chunk->offset(); + const auto * data = getValidatedBuffer(*chunk, column_name); /// Check that indexes are correct (protection against corrupted files) /// Note that on null values index can be arbitrary value. @@ -817,13 +1165,87 @@ static ColumnPtr readColumnWithIndexesData(std::shared_ptr } template -static std::shared_ptr getNestedArrowColumn(const std::shared_ptr & arrow_column) +static std::shared_ptr getNestedArrowColumn(const std::shared_ptr & arrow_column, const String & column_name) { arrow::ArrayVector array_vector; array_vector.reserve(arrow_column->num_chunks()); for (int chunk_i = 0, num_chunks = arrow_column->num_chunks(); chunk_i < num_chunks; ++chunk_i) { - ArrowListArray & list_chunk = dynamic_cast(*(arrow_column->chunk(chunk_i))); + auto & list_chunk = dynamic_cast(*(arrow_column->chunk(chunk_i))); + + /// Validate the parent list validity bitmap before Flatten(): when null_count > 0, + /// Flatten calls IsValid on the parent list array which reads buffers[0]. + checkValidityBitmap(list_chunk, column_name); + + /// Validate the offsets buffer before Flatten() reads it: Flatten() iterates + /// over offset[0..length] to slice the values array, so it needs (length+1) entries. + /// We also validate monotonicity here, before Flatten(), because Flatten() slices the + /// values array from offset[0] to offset[length]; a decreasing pair would otherwise fail + /// deep inside Arrow's ArrayData::Slice instead of being rejected as INCORRECT_DATA. + /// FixedSizeListArray uses a fixed stride instead of a variable-length offsets array. + if constexpr (!std::is_same_v) + { + /// offsets() returns a temporary shared_ptr; keep it alive in a named local so the + /// reference returned by checkedCast does not dangle while we validate it. + auto arrow_offsets_array = list_chunk.offsets(); + using OffsetArray = typename ArrowOffsetArray::type; + const auto & arrow_offsets = checkedCast(*arrow_offsets_array, column_name); + checkListOffsetsMonotonic(arrow_offsets, column_name); + + /// Flatten() slices the values array over [offset[0], offset[length]]. Monotonic, + /// non-negative offsets can still point past the end of (or a negative-length) values + /// array, which would fail inside Arrow's ArrayData::Slice; reject it here instead. + const auto & values = list_chunk.values(); + const int64_t values_len = values ? values->length() : 0; + if (unlikely(values_len < 0)) + throw Exception( + ErrorCodes::INCORRECT_DATA, + "Arrow List values array has negative length {} for column '{}'", + values_len, column_name); + if (arrow_offsets.length() > 0) + { + const int64_t last_offset = arrow_offsets.Value(arrow_offsets.length() - 1); + if (unlikely(last_offset > values_len)) + throw Exception( + ErrorCodes::INCORRECT_DATA, + "Arrow List last offset {} exceeds values array length {} for column '{}'", + last_offset, values_len, column_name); + } + } + else + { + /// FixedSizeList has no offsets array: Flatten() slices the values array over + /// [offset*stride, (offset+length)*stride]. Validate the fixed stride is + /// non-negative and the slice stays within the values array before Flatten() + /// reaches Arrow's ArrayData::Slice (which would otherwise fail with STD_EXCEPTION). + const int64_t stride = list_chunk.value_length(); + if (unlikely(stride < 0)) + throw Exception( + ErrorCodes::INCORRECT_DATA, + "Arrow FixedSizeList has negative list size {} for column '{}'", + stride, column_name); + const size_t count = static_cast(list_chunk.offset()) + static_cast(list_chunk.length()); + size_t required = 0; + if (unlikely(__builtin_mul_overflow(count, static_cast(stride), &required))) + throw Exception( + ErrorCodes::INCORRECT_DATA, + "Arrow FixedSizeList size overflow for column '{}': {} rows × stride {}", + column_name, count, stride); + const auto & values = list_chunk.values(); + const int64_t values_len_signed = values ? values->length() : 0; + if (unlikely(values_len_signed < 0)) + throw Exception( + ErrorCodes::INCORRECT_DATA, + "Arrow FixedSizeList values array has negative length {} for column '{}'", + values_len_signed, column_name); + const size_t values_len = static_cast(values_len_signed); + if (unlikely(required > values_len)) + throw Exception( + ErrorCodes::INCORRECT_DATA, + "Arrow FixedSizeList values array too small for column '{}': " + "need {} elements but values array has {}", + column_name, required, values_len); + } /* * It seems like arrow::ListArray::values() (nested column data) might or might not be shared across chunks. @@ -835,7 +1257,12 @@ static std::shared_ptr getNestedArrowColumn(const std::shar auto flatten_result = list_chunk.Flatten(); if (flatten_result.ok()) { - array_vector.emplace_back(flatten_result.ValueOrDie()); + /// Flatten slices the values array, producing a child with kUnknownNullCount. + /// Validate its validity bitmap now, before arrow::ChunkedArray's constructor + /// (below) sums chunk->null_count() and scans a possibly-truncated bitmap. + const auto & flattened = flatten_result.ValueOrDie(); + checkValidityBitmap(*flattened, column_name); + array_vector.emplace_back(flattened); } else { @@ -850,14 +1277,27 @@ static ColumnWithTypeAndName readIPv6ColumnFromBinaryData(const std::shared_ptr< size_t total_size = 0; for (int chunk_i = 0, num_chunks = arrow_column->num_chunks(); chunk_i < num_chunks; ++chunk_i) { - auto & chunk = dynamic_cast(*(arrow_column->chunk(chunk_i))); + const auto & chunk = dynamic_cast(*(arrow_column->chunk(chunk_i))); + checkBinaryOffsetsBuffer(chunk, column_name); const size_t chunk_length = chunk.length(); + const size_t data_buf_size = chunk.value_data() ? static_cast(chunk.value_data()->size()) : 0; for (size_t i = 0; i != chunk_length; ++i) { + if (chunk.IsNull(i)) + continue; /// If at least one value size is not 16 bytes, fallback to reading String column and further cast to IPv6. - if (!chunk.IsNull(i) && chunk.value_length(i) != sizeof(IPv6)) + if (chunk.value_length(i) != sizeof(IPv6)) return readColumnWithStringData(arrow_column, column_name); + /// Validate the data range before reserve(total_size): valid 16-byte offsets with a + /// truncated data buffer must be rejected here, not after the allocation. + const size_t safe_offset = static_cast(chunk.value_offset(i)); + if (unlikely(safe_offset > data_buf_size || sizeof(IPv6) > data_buf_size - safe_offset)) + throw Exception( + ErrorCodes::INCORRECT_DATA, + "Arrow BinaryArray data buffer too small for column '{}': " + "row {} has offset {} but buffer is {} bytes", + column_name, i, chunk.value_offset(i), data_buf_size); } total_size += chunk_length; } @@ -869,13 +1309,27 @@ static ColumnWithTypeAndName readIPv6ColumnFromBinaryData(const std::shared_ptr< for (int chunk_i = 0, num_chunks = arrow_column->num_chunks(); chunk_i < num_chunks; ++chunk_i) { - auto & chunk = dynamic_cast(*(arrow_column->chunk(chunk_i))); + const auto & chunk = dynamic_cast(*(arrow_column->chunk(chunk_i))); + checkBinaryOffsetsBuffer(chunk, column_name); + const size_t data_buf_size = chunk.value_data() ? static_cast(chunk.value_data()->size()) : 0; for (size_t value_i = 0, length = static_cast(chunk.length()); value_i < length; ++value_i) { if (chunk.IsNull(value_i)) + { ipv6_column.insertDefault(); + } else - ipv6_column.insertData(chunk.Value(value_i).data(), chunk.Value(value_i).size()); + { + const size_t safe_offset = static_cast(chunk.value_offset(value_i)); + if (unlikely(safe_offset > data_buf_size || sizeof(IPv6) > data_buf_size - safe_offset)) + throw Exception( + ErrorCodes::INCORRECT_DATA, + "Arrow BinaryArray data buffer too small for column '{}': " + "row {} has offset {} but buffer is {} bytes", + column_name, value_i, chunk.value_offset(value_i), data_buf_size); + const auto * raw_data = reinterpret_cast(chunk.value_data()->data()) + safe_offset; + ipv6_column.insertData(raw_data, sizeof(IPv6)); + } } } return {std::move(internal_column), std::move(internal_type), column_name}; @@ -886,6 +1340,7 @@ static ColumnWithTypeAndName readIPv4ColumnWithInt32Data(const std::shared_ptr(); auto internal_column = internal_type->createColumn(); auto & column_data = assert_cast(*internal_column).getData(); + validateChunksBeforeReserve(*arrow_column, [&](const arrow::Array & chunk) { checkArrowBuffer(chunk, sizeof(IPv4), column_name); }); column_data.reserve(arrow_column->length()); for (int chunk_i = 0, num_chunks = arrow_column->num_chunks(); chunk_i < num_chunks; ++chunk_i) @@ -895,8 +1350,7 @@ static ColumnWithTypeAndName readIPv4ColumnWithInt32Data(const std::shared_ptr buffer = chunk->data()->buffers[1]; - const auto * raw_data = reinterpret_cast(buffer->data()) + chunk->offset(); + const auto * raw_data = getValidatedBuffer(*chunk, column_name); column_data.insert_assume_reserved(raw_data, raw_data + chunk->length()); } return {std::move(internal_column), std::move(internal_type), column_name}; @@ -1090,7 +1544,7 @@ static ColumnWithTypeAndName readNonNullableColumnFromArrowColumn( } } - auto arrow_nested_column = getNestedArrowColumn(arrow_column); + auto arrow_nested_column = getNestedArrowColumn(arrow_column, column_name); auto nested_column = readColumnFromArrowColumn(arrow_nested_column, column_name, full_column_name, @@ -1106,7 +1560,7 @@ static ColumnWithTypeAndName readNonNullableColumnFromArrowColumn( if (!nested_column.column) return {}; - auto offsets_column = readOffsetsFromArrowListColumn(arrow_column); + auto offsets_column = readOffsetsFromArrowListColumn(arrow_column, column_name); const auto * tuple_column = assert_cast(nested_column.column.get()); const auto * tuple_type = assert_cast(nested_column.type.get()); @@ -1123,6 +1577,14 @@ static ColumnWithTypeAndName readNonNullableColumnFromArrowColumn( key_type = key_type_hint; } + { + const auto & off = assert_cast &>(*offsets_column).getData(); + if (!off.empty() && off.back() != key_column->size()) + throw Exception( + ErrorCodes::INCORRECT_DATA, + "Arrow Map column '{}': last offset {} does not match nested column size {}", + column_name, off.back(), key_column->size()); + } auto map_column = ColumnMap::create(key_column, value_column, offsets_column); auto map_type = std::make_shared(key_type, value_type); return {std::move(map_column), std::move(map_type), column_name}; @@ -1187,11 +1649,11 @@ static ColumnWithTypeAndName readNonNullableColumnFromArrowColumn( switch (list_type) { case ListType::LargeList: - return getNestedArrowColumn(arrow_column); + return getNestedArrowColumn(arrow_column, column_name); case ListType::FixedSizeList: - return getNestedArrowColumn(arrow_column); + return getNestedArrowColumn(arrow_column, column_name); case ListType::List: - return getNestedArrowColumn(arrow_column); + return getNestedArrowColumn(arrow_column, column_name); } }(); @@ -1215,16 +1677,29 @@ static ColumnWithTypeAndName readNonNullableColumnFromArrowColumn( switch (list_type) { case ListType::LargeList: - return readOffsetsFromArrowListColumn(arrow_column); + return readOffsetsFromArrowListColumn(arrow_column, column_name); case ListType::FixedSizeList: { auto fixed_length = assert_cast(arrow_column->type().get())->list_size(); return readOffsetsFromFixedArrowListColumn(arrow_column, fixed_length); } case ListType::List: - return readOffsetsFromArrowListColumn(arrow_column); + return readOffsetsFromArrowListColumn(arrow_column, column_name); } }(); + /// Validate that the last offset matches the nested column size before constructing + /// ColumnArray. ColumnArray's constructor skips data->size() == last_offset when + /// data->empty() (the && !data->empty() short-circuit), so a crafted Arrow file + /// with an empty child but non-zero offsets would silently produce a column whose + /// interior offsets point past the inner allocation. + { + const auto & off = assert_cast &>(*offsets_column).getData(); + if (!off.empty() && off.back() != nested_column.column->size()) + throw Exception( + ErrorCodes::INCORRECT_DATA, + "Arrow List column '{}': last offset {} does not match nested column size {}", + column_name, off.back(), nested_column.column->size()); + } auto array_column = ColumnArray::create(nested_column.column, offsets_column); DataTypePtr array_type; @@ -1250,9 +1725,30 @@ static ColumnWithTypeAndName readNonNullableColumnFromArrowColumn( std::vector nested_arrow_columns(arrow_struct_type->num_fields()); for (int chunk_i = 0, num_chunks = arrow_column->num_chunks(); chunk_i < num_chunks; ++chunk_i) { - arrow::StructArray & struct_chunk = dynamic_cast(*(arrow_column->chunk(chunk_i))); + auto & struct_chunk = assert_cast(*(arrow_column->chunk(chunk_i))); for (int i = 0; i < arrow_struct_type->num_fields(); ++i) - nested_arrow_columns[i].emplace_back(struct_chunk.field(i)); + { + /// field() slices child[struct.offset : struct.offset + struct.length] (the + /// struct may be sliced, e.g. when nested in a list). A child shorter than that + /// range, including the negative-length case, would fail inside Arrow's + /// ArrayData::Slice, so validate the range here first. struct.offset/length are + /// already known non-negative (checked at readColumnFromArrowColumn entry). + const auto & child_data = struct_chunk.data()->child_data[i]; + if (unlikely( + child_data->length < 0 + || struct_chunk.offset() > child_data->length + || struct_chunk.length() > child_data->length - struct_chunk.offset())) + throw Exception( + ErrorCodes::INCORRECT_DATA, + "Arrow Struct field {} (length {}) is too small for the struct slice " + "[offset {}, +{}) for column '{}'", + i, child_data->length, struct_chunk.offset(), struct_chunk.length(), column_name); + auto field_chunk = struct_chunk.field(i); + /// field() Slice-clamps children, producing kUnknownNullCount; validate the + /// child bitmap before arrow::ChunkedArray's constructor (below) scans it. + checkValidityBitmap(*field_chunk, column_name); + nested_arrow_columns[i].emplace_back(std::move(field_chunk)); + } } Columns tuple_elements; @@ -1325,6 +1821,27 @@ static ColumnWithTypeAndName readNonNullableColumnFromArrowColumn( tuple_names.emplace_back(std::move(column_with_type_and_name.name)); } + /// Validate that every field has exactly as many rows as the parent struct + /// declares. arrow::StructArray::field() silently Slices a short child to + /// the parent's reported length, so a crafted Arrow file can patch individual + /// fields' FieldNode.length without triggering an Arrow-level error. Without + /// this check, ColumnTuple would hold fields of unequal size, and any reader + /// that accesses a short field past its allocation triggers an OOB heap read. + /// Note: this also covers the Map case, Map entries are read through this + /// STRUCT branch, so mismatched key vs. value lengths are caught here. + if (!tuple_elements.empty()) + { + const size_t expected_rows = static_cast(arrow_column->length()); + for (size_t i = 0; i < tuple_elements.size(); ++i) + { + if (tuple_elements[i]->size() != expected_rows) + throw Exception( + ErrorCodes::INCORRECT_DATA, + "Arrow Struct column '{}': field '{}' has {} rows but parent struct declares {}", + column_name, tuple_names[i], tuple_elements[i]->size(), expected_rows); + } + } + ColumnPtr tuple_column; if (tuple_elements.empty()) tuple_column = ColumnTuple::create(arrow_column->length()); @@ -1343,7 +1860,7 @@ static ColumnWithTypeAndName readNonNullableColumnFromArrowColumn( arrow::ArrayVector dict_array; for (int chunk_i = 0, num_chunks = arrow_column->num_chunks(); chunk_i < num_chunks; ++chunk_i) { - arrow::DictionaryArray & dict_chunk = dynamic_cast(*(arrow_column->chunk(chunk_i))); + auto & dict_chunk = assert_cast(*(arrow_column->chunk(chunk_i))); dict_array.emplace_back(dict_chunk.dictionary()); } @@ -1395,7 +1912,7 @@ static ColumnWithTypeAndName readNonNullableColumnFromArrowColumn( arrow::ArrayVector indexes_array; for (int chunk_i = 0, num_chunks = arrow_column->num_chunks(); chunk_i < num_chunks; ++chunk_i) { - arrow::DictionaryArray & dict_chunk = dynamic_cast(*(arrow_column->chunk(chunk_i))); + auto & dict_chunk = assert_cast(*(arrow_column->chunk(chunk_i))); indexes_array.emplace_back(dict_chunk.indices()); } @@ -1467,6 +1984,18 @@ static ColumnWithTypeAndName readColumnFromArrowColumn( const std::optional> & parquet_columns_to_clickhouse, const std::optional> & clickhouse_columns_to_parquet) { + /// Validate each chunk up front, before anything reads the declared length: + /// - checkValidityBitmap rejects a negative length/offset and a validity bitmap (buffers[0]) + /// too small for the declared rows. It is scan-free (never calls null_count), so it must + /// run before the arrow_column->null_count() below: Arrow computes an unknown null_count + /// (kUnknownNullCount = -1) by scanning buffers[0] over the declared length, which reads + /// out of bounds when the bitmap is truncated. + /// - rejecting a negative length here also stops a reader reserving a huge column from a + /// wrapped-to-huge size_t length. + /// This runs for every nested level too, since the function recurses for nested types. + for (int chunk_i = 0, num_chunks = arrow_column->num_chunks(); chunk_i < num_chunks; ++chunk_i) + checkValidityBitmap(*arrow_column->chunk(chunk_i), column_name); + bool read_as_nullable_column = (arrow_column->null_count() || is_nullable_column || (type_hint && (type_hint->isNullable() || type_hint->isLowCardinalityNullable()))) && !geo_metadata && settings.allow_inferring_nullable_columns; if (read_as_nullable_column && arrow_column->type()->id() != arrow::Type::LIST && @@ -1496,7 +2025,7 @@ static ColumnWithTypeAndName readColumnFromArrowColumn( if (!nested_column.column) return {}; - auto nullmap_column = readByteMapFromArrowColumn(arrow_column); + auto nullmap_column = readByteMapFromArrowColumn(arrow_column, column_name); auto nullable_type = std::make_shared(std::move(nested_column.type)); auto nullable_column = ColumnNullable::create(nested_column.column, nullmap_column); @@ -1628,6 +2157,13 @@ ArrowColumnToCHColumn::ArrowColumnToCHColumn( { } +void ArrowColumnToCHColumn::checkRecordBatchValidityBitmaps(const arrow::RecordBatch & batch) +{ + const auto & schema = *batch.schema(); + for (int i = 0, num_columns = batch.num_columns(); i < num_columns; ++i) + checkArrayDataValidityBitmaps(*batch.column(i)->data(), schema.field(i)->name()); +} + Chunk ArrowColumnToCHColumn::arrowTableToCHChunk( const std::shared_ptr & table, size_t num_rows, diff --git a/src/Processors/Formats/Impl/ArrowColumnToCHColumn.h b/src/Processors/Formats/Impl/ArrowColumnToCHColumn.h index b41446753f2f..e06cce04579d 100644 --- a/src/Processors/Formats/Impl/ArrowColumnToCHColumn.h +++ b/src/Processors/Formats/Impl/ArrowColumnToCHColumn.h @@ -42,6 +42,13 @@ class ArrowColumnToCHColumn std::shared_ptr metadata, BlockMissingValues * block_missing_values = nullptr); + /// Validate that every validity bitmap in the record batch (recursively, including nested + /// children and dictionaries) covers its declared rows. Must be called before building an + /// arrow::Table from the batch: Arrow computes an unknown FieldNode null_count by scanning + /// the bitmap over the declared length, which reads out of bounds when the bitmap is + /// truncated. Throws INCORRECT_DATA on a malformed bitmap. + static void checkRecordBatchValidityBitmaps(const arrow::RecordBatch & batch); + /// Transform arrow schema to ClickHouse header static Block arrowSchemaToCHHeader( const arrow::Schema & schema, diff --git a/src/Processors/Formats/Impl/ORCBlockInputFormat.cpp b/src/Processors/Formats/Impl/ORCBlockInputFormat.cpp index 6c08be61bc9e..95d9cd537d74 100644 --- a/src/Processors/Formats/Impl/ORCBlockInputFormat.cpp +++ b/src/Processors/Formats/Impl/ORCBlockInputFormat.cpp @@ -59,6 +59,11 @@ Chunk ORCBlockInputFormat::read() if (!batch) return {}; + /// Validate validity bitmaps before building the table: Table::FromRecordBatches computes + /// each column's null_count, and Arrow derives an unknown FieldNode null_count by scanning + /// the bitmap over the declared length, which reads out of bounds on a truncated bitmap. + ArrowColumnToCHColumn::checkRecordBatchValidityBitmaps(*batch); + auto table_result = arrow::Table::FromRecordBatches({batch}); if (!table_result.ok()) throw Exception( diff --git a/src/Processors/Sources/ArrowFlightSource.cpp b/src/Processors/Sources/ArrowFlightSource.cpp index 4c35751dc221..f15eaffe1258 100644 --- a/src/Processors/Sources/ArrowFlightSource.cpp +++ b/src/Processors/Sources/ArrowFlightSource.cpp @@ -171,6 +171,11 @@ Chunk ArrowFlightSource::generate() FormatSettings::DateTimeOverflowBehavior::Throw, /* allow_geoparquet_parser = */ false); + /// Validate validity bitmaps before building the table: Table::FromRecordBatches computes + /// each column's null_count, and Arrow derives an unknown FieldNode null_count by scanning + /// the bitmap over the declared length, which reads out of bounds on a truncated bitmap. + ArrowColumnToCHColumn::checkRecordBatchValidityBitmaps(*chunk.data); + auto table_res = arrow::Table::FromRecordBatches({chunk.data}); if (!table_res.ok()) { diff --git a/src/Storages/ObjectStorage/DataLakes/DeltaLake/TableChanges.cpp b/src/Storages/ObjectStorage/DataLakes/DeltaLake/TableChanges.cpp index 8e2cab99e6b3..385b16dfec72 100644 --- a/src/Storages/ObjectStorage/DataLakes/DeltaLake/TableChanges.cpp +++ b/src/Storages/ObjectStorage/DataLakes/DeltaLake/TableChanges.cpp @@ -169,6 +169,11 @@ DB::Chunk TableChanges::next() record_batch.status().ToString()); } + /// Validate validity bitmaps before building the table: Table::FromRecordBatches computes + /// each column's null_count, and Arrow derives an unknown FieldNode null_count by scanning + /// the bitmap over the declared length, which reads out of bounds on a truncated bitmap. + DB::ArrowColumnToCHColumn::checkRecordBatchValidityBitmaps(**record_batch); + auto table_result = arrow::Table::FromRecordBatches(std::vector>{*record_batch}); if (!table_result.ok()) throw DB::Exception(DB::ErrorCodes::UNKNOWN_EXCEPTION, diff --git a/tests/queries/0_stateless/04307_arrow_oob_reads.reference b/tests/queries/0_stateless/04307_arrow_oob_reads.reference new file mode 100644 index 000000000000..97564127add2 --- /dev/null +++ b/tests/queries/0_stateless/04307_arrow_oob_reads.reference @@ -0,0 +1,7 @@ +INCORRECT_DATA +INCORRECT_DATA +INCORRECT_DATA +INCORRECT_DATA +INCORRECT_DATA +INCORRECT_DATA +INCORRECT_DATA diff --git a/tests/queries/0_stateless/04307_arrow_oob_reads.sh b/tests/queries/0_stateless/04307_arrow_oob_reads.sh new file mode 100755 index 000000000000..5e234acd3bbe --- /dev/null +++ b/tests/queries/0_stateless/04307_arrow_oob_reads.sh @@ -0,0 +1,113 @@ +#!/usr/bin/env bash +# Tags: no-fasttest +# Regression tests for heap out-of-bounds reads in Arrow IPC format readers +# (bulk memcpy readers — fixed-size buffers[1]). +# +# Each test crafts an Arrow IPC file where the RecordBatch declares many rows +# but the data buffer in the file body holds only a single element. On an +# unpatched build this causes a heap-buffer-overflow caught by ASan. The +# fixed build must reject every such file with INCORRECT_DATA. +# +# 1. readColumnWithNumericData – plain numeric (UInt32 / Int64 / …) +# 2. readColumnWithDurationData – Arrow Duration → Interval +# 3. readColumnWithFixedStringData – FixedSizeBinary → FixedString +# 4. readColumnWithBigIntegerFromFixedBinaryData – FixedSizeBinary → Int128/Int256 +# 5. readColumnWithDate32Data (fast path) – Date32 → numeric (no range check) +# 6. readColumnWithIndexesDataImpl – Dictionary index buffer +# 7. readIPv4ColumnWithInt32Data – Int32 → IPv4 + +CUR_DIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd) +# shellcheck source=../shell_config.sh +. "$CUR_DIR"/../shell_config.sh + +TMP_DIR="${CLICKHOUSE_TMP}/${CLICKHOUSE_TEST_UNIQUE_NAME}" +mkdir -p "$TMP_DIR" +trap 'rm -rf "$TMP_DIR"' EXIT + +python3 - "$TMP_DIR" <<'PYEOF' +import struct, io, sys +import pyarrow as pa +import pyarrow.ipc as ipc + +out = sys.argv[1] +LARGE_N = 16384 + +def write_arrow(arr, name='x'): + tbl = pa.table({name: arr}) + buf = io.BytesIO() + with ipc.new_file(buf, tbl.schema) as w: + w.write_table(tbl) + return bytearray(buf.getvalue()) + +def inflate_row_count(data, orig_n, large_n=LARGE_N): + needle = struct.pack('&1) + local exit_code=$? + if echo "$actual" | grep -qF 'INCORRECT_DATA'; then + echo 'INCORRECT_DATA' + else + local first_line + first_line=$(echo "$actual" | head -1 | cut -c1-200) + echo "FAIL [$label] expected INCORRECT_DATA (exit=${exit_code}); got: ${first_line:-}" + fi +} + +check numeric $CLICKHOUSE_LOCAL --query "SELECT x FROM file('${TMP_DIR}/numeric.arrow', Arrow)" +check duration $CLICKHOUSE_LOCAL --query "SELECT x FROM file('${TMP_DIR}/duration.arrow', Arrow)" +check fixed_string $CLICKHOUSE_LOCAL --query "SELECT x FROM file('${TMP_DIR}/fixed_string.arrow', Arrow)" +check int128 $CLICKHOUSE_LOCAL --query "SELECT x FROM file('${TMP_DIR}/int128.arrow', Arrow)" +# Date32 fast path: numeric type hint skips range checking, hits the raw buffer read +check date32 $CLICKHOUSE_LOCAL --query "SELECT x FROM file('${TMP_DIR}/date32.arrow', Arrow, 'x Int32')" +# Dict indexes: integer-valued dictionary isolates the index-buffer read path +check dict_indexes $CLICKHOUSE_LOCAL --query "SELECT x FROM file('${TMP_DIR}/dict_indexes.arrow', Arrow)" +# IPv4: Arrow Int32 with CH IPv4 type hint routes through readIPv4ColumnWithInt32Data +check ipv4 $CLICKHOUSE_LOCAL --query "SELECT x FROM file('${TMP_DIR}/ipv4.arrow', Arrow, 'x IPv4')" diff --git a/tests/queries/0_stateless/04308_arrow_oob_reads_value.reference b/tests/queries/0_stateless/04308_arrow_oob_reads_value.reference new file mode 100644 index 000000000000..6e3ef492598f --- /dev/null +++ b/tests/queries/0_stateless/04308_arrow_oob_reads_value.reference @@ -0,0 +1,9 @@ +INCORRECT_DATA +INCORRECT_DATA +INCORRECT_DATA +INCORRECT_DATA +INCORRECT_DATA +INCORRECT_DATA +INCORRECT_DATA +INCORRECT_DATA +INCORRECT_DATA diff --git a/tests/queries/0_stateless/04308_arrow_oob_reads_value.sh b/tests/queries/0_stateless/04308_arrow_oob_reads_value.sh new file mode 100755 index 000000000000..a633412acff6 --- /dev/null +++ b/tests/queries/0_stateless/04308_arrow_oob_reads_value.sh @@ -0,0 +1,114 @@ +#!/usr/bin/env bash +# Tags: no-fasttest +# Regression tests for heap out-of-bounds reads in Arrow IPC format readers +# (per-element Value() readers — checkedCast validates before the loop). +# +# 8. readColumnWithBooleanData – BooleanArray::Value() / bit-packed buffer +# 9. readColumnWithDate32Data (slow)– Date32Array::Value() +# 10. readColumnWithDate64Data – Date64Array::Value() +# 11. readColumnWithTimestampData – TimestampArray::Value() +# 12. readColumnWithTimeData (Time32)– Time32Array::Value() +# 13. readColumnWithTimeData (Time64)– Time64Array::Value() +# 14. readColumnWithFloat16Data – HalfFloatArray::Value() +# 15. readColumnWithDecimalDataImpl – DecimalArray::Value() +# 16. readColumnWithUUIDFromFixedBinaryData – FixedSizeBinaryArray::GetValue() + +CUR_DIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd) +# shellcheck source=../shell_config.sh +. "$CUR_DIR"/../shell_config.sh + +TMP_DIR="${CLICKHOUSE_TMP}/${CLICKHOUSE_TEST_UNIQUE_NAME}" +mkdir -p "$TMP_DIR" +trap 'rm -rf "$TMP_DIR"' EXIT + +python3 - "$TMP_DIR" <<'PYEOF' +import struct, io, sys +import pyarrow as pa +import pyarrow.ipc as ipc + +out = sys.argv[1] +LARGE_N = 16384 + +def write_arrow(arr, name='x'): + tbl = pa.table({name: arr}) + buf = io.BytesIO() + with ipc.new_file(buf, tbl.schema) as w: + w.write_table(tbl) + return bytearray(buf.getvalue()) + +def inflate_row_count(data, orig_n, large_n=LARGE_N): + needle = struct.pack('&1) + local exit_code=$? + if echo "$actual" | grep -qF 'INCORRECT_DATA'; then + echo 'INCORRECT_DATA' + else + local first_line + first_line=$(echo "$actual" | head -1 | cut -c1-200) + echo "FAIL [$label] expected INCORRECT_DATA (exit=${exit_code}); got: ${first_line:-}" + fi +} + +check bool $CLICKHOUSE_LOCAL --query "SELECT x FROM file('${TMP_DIR}/bool.arrow', Arrow)" +# Date32 slow path: no numeric type hint → check_date_range=true → Value() loop +check date32_slow $CLICKHOUSE_LOCAL --query "SELECT x FROM file('${TMP_DIR}/date32_slow.arrow', Arrow)" +check date64 $CLICKHOUSE_LOCAL --query "SELECT x FROM file('${TMP_DIR}/date64.arrow', Arrow)" +check timestamp $CLICKHOUSE_LOCAL --query "SELECT x FROM file('${TMP_DIR}/timestamp.arrow', Arrow)" +check time32 $CLICKHOUSE_LOCAL --query "SELECT x FROM file('${TMP_DIR}/time32.arrow', Arrow)" +check time64 $CLICKHOUSE_LOCAL --query "SELECT x FROM file('${TMP_DIR}/time64.arrow', Arrow)" +check float16 $CLICKHOUSE_LOCAL --query "SELECT x FROM file('${TMP_DIR}/float16.arrow', Arrow)" +check decimal $CLICKHOUSE_LOCAL --query "SELECT x FROM file('${TMP_DIR}/decimal.arrow', Arrow)" +# UUID: explicit type hint routes through readColumnWithUUIDFromFixedBinaryData +check uuid $CLICKHOUSE_LOCAL --query "SELECT x FROM file('${TMP_DIR}/uuid.arrow', Arrow, 'x UUID')" diff --git a/tests/queries/0_stateless/04309_arrow_oob_reads_offsets.reference b/tests/queries/0_stateless/04309_arrow_oob_reads_offsets.reference new file mode 100644 index 000000000000..e99e3dad7246 --- /dev/null +++ b/tests/queries/0_stateless/04309_arrow_oob_reads_offsets.reference @@ -0,0 +1,15 @@ +INCORRECT_DATA +INCORRECT_DATA +INCORRECT_DATA +INCORRECT_DATA +INCORRECT_DATA +INCORRECT_DATA +INCORRECT_DATA +INCORRECT_DATA +INCORRECT_DATA +INCORRECT_DATA +INCORRECT_DATA +INCORRECT_DATA +INCORRECT_DATA +INCORRECT_DATA +INCORRECT_DATA diff --git a/tests/queries/0_stateless/04309_arrow_oob_reads_offsets.sh b/tests/queries/0_stateless/04309_arrow_oob_reads_offsets.sh new file mode 100755 index 000000000000..1c4a38394dd4 --- /dev/null +++ b/tests/queries/0_stateless/04309_arrow_oob_reads_offsets.sh @@ -0,0 +1,229 @@ +#!/usr/bin/env bash +# Tags: no-fasttest +# Regression tests for heap out-of-bounds reads in Arrow IPC format readers +# (view-struct buffer and offsets-buffer readers). +# +# 17. readColumnWithViewData (StringViewArray) – view-struct buffer (16 bytes/row) +# 18. readColumnWithViewData (BinaryViewArray) – view-struct buffer +# 19. readColumnWithStringData – offsets buffer over-read before per-row data check +# 20. readOffsetsFromArrowListColumn – List offsets Int32Array via Value() +# 21. getNestedArrowColumn (Map) – Map offsets array via ListArray::Flatten() +# 22. readColumnWithJSONData – BinaryArray offsets buffer (same path as 19) +# 23. readColumnWithViewData – view struct length field exceeds variadic data buffer +# 24. readColumnWithJSONData – data buffer over-read (value_offset+length > data size) +# 25. readColumnWithBigNumberFromBinaryData – offsets buffer read before size check +# 26. readColumnWithGeoData – offsets buffer read before per-row data-buffer check +# 27. readIPv6ColumnFromBinaryData – offsets buffer read before size check +# 28. checkViewStruct – negative view size passes is_inline(), wraps in accumulation +# 29. readColumnWithBigNumberFromBinaryData – data buffer over-read in copy loop +# 30. readIPv6ColumnFromBinaryData – data buffer over-read in copy loop +# 31. getNestedArrowColumn – nullable List: valid offsets/child but truncated parent bitmap + +CUR_DIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd) +# shellcheck source=../shell_config.sh +. "$CUR_DIR"/../shell_config.sh + +TMP_DIR="${CLICKHOUSE_TMP}/${CLICKHOUSE_TEST_UNIQUE_NAME}" +mkdir -p "$TMP_DIR" +trap 'rm -rf "$TMP_DIR"' EXIT + +python3 - "$TMP_DIR" <<'PYEOF' +import struct, io, sys +import pyarrow as pa +import pyarrow.ipc as ipc + +out = sys.argv[1] +LARGE_N = 16384 + +def write_arrow(arr, name='x'): + tbl = pa.table({name: arr}) + buf = io.BytesIO() + with ipc.new_file(buf, tbl.schema) as w: + w.write_table(tbl) + return bytearray(buf.getvalue()) + +def inflate_row_count(data, orig_n, large_n=LARGE_N): + needle = struct.pack('= 8 and 0 <= struct.unpack_from(' 12 bytes force non-inline view structs. +# 7 rows * 16 bytes = 112-byte buffer; shrink declared length to 16 (1 entry). +d = write_arrow(pa.array(['x'*13]*7, type=pa.string_view())) +open(f'{out}/strview.arrow', 'wb').write(shrink_first_buffer(d, 7*16, 16)) + +# 18. BinaryViewArray: same approach. +d = write_arrow(pa.array([b'x'*13]*7, type=pa.binary_view())) +open(f'{out}/binview.arrow', 'wb').write(shrink_first_buffer(d, 7*16, 16)) + +# 19. String (BinaryArray): value_offset(i) reads the offsets buffer (buffers[1]) +# before the per-row data check; checkBinaryOffsetsBuffer catches it first. +# 1 row → tiny offsets buffer, inflated to 16384 rows. +d = write_arrow(pa.array(['hi'], type=pa.string())) +open(f'{out}/string.arrow', 'wb').write(inflate_row_count(d, 1)) + +# 20. List(Int32): readOffsetsFromArrowListColumn calls Value(i) on the offsets +# Int32Array; checkedCast validates the offsets buffer. +d = write_arrow(pa.array([[42]], type=pa.list_(pa.int32()))) +open(f'{out}/list.arrow', 'wb').write(inflate_row_count(d, 1)) + +# 21. Map(Int32, Int32): MapArray subclasses ListArray; getNestedArrowColumn calls +# Flatten() which reads the offsets buffer. +d = write_arrow(pa.array([[(1, 2)]], type=pa.map_(pa.int32(), pa.int32()))) +open(f'{out}/map.arrow', 'wb').write(inflate_row_count(d, 1)) + +# 22. readColumnWithJSONData: same offsets-buffer vulnerability as case 19, but the +# reader is invoked via a JSON type hint rather than plain String. +d = write_arrow(pa.array([b'{"a":1}'], type=pa.binary())) +open(f'{out}/json.arrow', 'wb').write(inflate_row_count(d, 1)) + +# 23. View data-buffer: checkedCastView validates the view-struct buffer but the +# length field inside each view struct is attacker-controlled and used as the +# memcpy size against the variadic data buffer. +# 96-char non-inline string → patch last int32 of its view struct to ~512 MB. +d = write_arrow(pa.array(['X'*96], type=pa.string_view())) +def patch_last_i32(data, pattern, newval): + i = data.rfind(pattern) + assert i >= 0 + data[i:i+4] = struct.pack('size(). +# 1 row of 96 'A' bytes: offsets = [0, 96]. Patch the final offset (offset[1]) to +# ~512 MB so offset[0]=0 stays valid but value_length overruns the 96-byte data buffer. +# This exercises the "safe_length > data_buf_size - safe_offset" half of the per-row +# guard (patching offset[0] instead would only test the offset half). +d = write_arrow(pa.array([b'A'*96], type=pa.binary())) +i = d.rfind(struct.pack('= 0 +d[i+4:i+8] = struct.pack(' [0, ~512 MB] +open(f'{out}/json_dlen.arrow', 'wb').write(d) + +# 25. readColumnWithBigNumberFromBinaryData: value_length(i) reads the offsets buffer +# before any offsets-buffer check; checkBinaryOffsetsBuffer must fire first. +d = write_arrow(pa.array([b'\xcc'*16], type=pa.binary())) +open(f'{out}/bignum.arrow', 'wb').write(inflate_row_count(d, 1)) + +# 26. readColumnWithGeoData: value_offset(i) and value_length(i) read the offsets +# buffer before the per-row data check; checkBinaryOffsetsBuffer must fire first. +d = write_arrow(pa.array([b'\x01\x01\x00\x00\x00' + b'\x00'*16], type=pa.binary())) +open(f'{out}/geo.arrow', 'wb').write(inflate_row_count(d, 1)) + +# 27. readIPv6ColumnFromBinaryData: same offsets-buffer gap as case 25 but for IPv6. +d = write_arrow(pa.array([b'\x00'*16], type=pa.binary())) +open(f'{out}/ipv6_binary.arrow', 'wb').write(inflate_row_count(d, 1)) + +# 28. checkViewStruct: a view-struct with size = -1 satisfies is_inline() (-1 <= 12) +# and was previously returned without error; static_cast(-1) in the +# accumulation loop wraps to SIZE_MAX, corrupting total_bytes_size. +# Build a 2-row StringViewArray with valid inline rows, then patch row-0's size to -1. +d = write_arrow(pa.array(['a', 'b'], type=pa.string_view())) +needle = struct.pack('= 0 +d[idx:idx+4] = struct.pack('= 8 and struct.unpack_from('= 0: + d[idx:idx+8] = struct.pack('= 8 and struct.unpack_from('= 0: + d[idx:idx+8] = struct.pack('= 0, f"bitmap entry (size={bitmap_size}) not found" +d[idx+8:idx+16] = struct.pack('&1) + local exit_code=$? + if echo "$actual" | grep -qF 'INCORRECT_DATA'; then + echo 'INCORRECT_DATA' + else + local first_line + first_line=$(echo "$actual" | head -1 | cut -c1-200) + echo "FAIL [$label] expected INCORRECT_DATA (exit=${exit_code}); got: ${first_line:-}" + fi +} + +check strview $CLICKHOUSE_LOCAL --query "SELECT x FROM file('${TMP_DIR}/strview.arrow', Arrow)" +check binview $CLICKHOUSE_LOCAL --query "SELECT x FROM file('${TMP_DIR}/binview.arrow', Arrow)" +# String: offsets buffer over-read before per-row data check +check string $CLICKHOUSE_LOCAL --query "SELECT x FROM file('${TMP_DIR}/string.arrow', Arrow)" +# List: offsets Int32Array read via Value() in readOffsetsFromArrowListColumn +check list $CLICKHOUSE_LOCAL --query "SELECT x FROM file('${TMP_DIR}/list.arrow', Arrow)" +# Map: MapArray subclasses ListArray; offsets read via Flatten() in getNestedArrowColumn +check map $CLICKHOUSE_LOCAL --query "SELECT x FROM file('${TMP_DIR}/map.arrow', Arrow)" +# JSON: BinaryArray with JSON type hint routes through readColumnWithJSONData +check json $CLICKHOUSE_LOCAL --query "SELECT x FROM file('${TMP_DIR}/json.arrow', Arrow, 'x JSON') FORMAT Null" +# View data-buffer: view struct length field inflated beyond the variadic data buffer +check view_dlen $CLICKHOUSE_LOCAL --query "SELECT x FROM file('${TMP_DIR}/view_dlen.arrow', Arrow)" +# JSON data-buffer: data offset+length inflated beyond value_data() buffer +check json_dlen $CLICKHOUSE_LOCAL --query "SELECT x FROM file('${TMP_DIR}/json_dlen.arrow', Arrow, 'x JSON') FORMAT Null" +# BigNum: offsets buffer read via value_length() before checkBinaryOffsetsBuffer +check bignum $CLICKHOUSE_LOCAL --query "SELECT x FROM file('${TMP_DIR}/bignum.arrow', Arrow, 'x Int128')" +# Geo: offsets buffer read via value_offset()/value_length() before checkBinaryOffsetsBuffer +check geo $CLICKHOUSE_LOCAL --query "SELECT x FROM file('${TMP_DIR}/geo.arrow', Arrow, 'x Point')" \ + --input_format_parquet_allow_geoparquet_parser=1 +# IPv6: offsets buffer read via value_length() before checkBinaryOffsetsBuffer +check ipv6_binary $CLICKHOUSE_LOCAL --query "SELECT x FROM file('${TMP_DIR}/ipv6_binary.arrow', Arrow, 'x IPv6')" +# View negative size: size=-1 passes is_inline() and wraps SIZE_MAX in accumulation +check view_negsize $CLICKHOUSE_LOCAL --query "SELECT x FROM file('${TMP_DIR}/view_negsize.arrow', Arrow)" +# BigNum data-buffer: data buffer too small for value_offset + sizeof(ValueType) +check bignum_dlen $CLICKHOUSE_LOCAL --query "SELECT x FROM file('${TMP_DIR}/bignum_dlen.arrow', Arrow, 'x Int128')" +# IPv6 data-buffer: data buffer too small for value_offset + sizeof(IPv6) +check ipv6_dlen $CLICKHOUSE_LOCAL --query "SELECT x FROM file('${TMP_DIR}/ipv6_dlen.arrow', Arrow, 'x IPv6')" +# Nullable List bitmap: valid offsets + child but truncated parent validity bitmap +check list_bitmap $CLICKHOUSE_LOCAL --query "SELECT sum(length(x)) FROM file('${TMP_DIR}/list_bitmap.arrow', Arrow)" diff --git a/tests/queries/0_stateless/04310_arrow_oob_reads_arithmetic.reference b/tests/queries/0_stateless/04310_arrow_oob_reads_arithmetic.reference new file mode 100644 index 000000000000..82c9e3f12c06 --- /dev/null +++ b/tests/queries/0_stateless/04310_arrow_oob_reads_arithmetic.reference @@ -0,0 +1,2 @@ +INCORRECT_DATA +INCORRECT_DATA diff --git a/tests/queries/0_stateless/04310_arrow_oob_reads_arithmetic.sh b/tests/queries/0_stateless/04310_arrow_oob_reads_arithmetic.sh new file mode 100755 index 000000000000..6ad98b8b62b9 --- /dev/null +++ b/tests/queries/0_stateless/04310_arrow_oob_reads_arithmetic.sh @@ -0,0 +1,89 @@ +#!/usr/bin/env bash +# Tags: no-fasttest +# Regression tests for two additional Arrow IPC buffer-validation paths. +# +# Issue A — arithmetic overflow in checkArrowBuffer: a crafted row count near +# 2^62 makes elem_size * (offset + length) wrap to 0 mod 2^64, bypassing the +# buffer-size guard. The fixed build validates the data buffer (with +# __builtin_mul_overflow) before reserving the column, so it rejects the file with +# INCORRECT_DATA ("buffer size overflow") rather than reaching reserve(2^62) (which +# on a build that skipped the checked arithmetic throws CANNOT_ALLOCATE_MEMORY). +# +# Issue B — validity bitmap (buffers[0]) not validated: a file with a valid +# data buffer but a 1-byte bitmap for 64 rows lets IsNull() read past the +# bitmap for rows 8-63. The fixed build calls checkValidityBitmap before any +# IsNull / IsValid loop. +# Note: COUNT(*) bypasses column reads; use SUM to force value materialisation. + +CUR_DIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd) +# shellcheck source=../shell_config.sh +. "$CUR_DIR"/../shell_config.sh + +TMP_DIR="${CLICKHOUSE_TMP}/${CLICKHOUSE_TEST_UNIQUE_NAME}" +mkdir -p "$TMP_DIR" +trap 'rm -rf "$TMP_DIR"' EXIT + +python3 - "$TMP_DIR" <<'PYEOF' +import struct, io, sys +import pyarrow as pa +import pyarrow.ipc as ipc + +out = sys.argv[1] + +def write_arrow(arr, name='x'): + tbl = pa.table({name: arr}) + buf = io.BytesIO() + with ipc.new_file(buf, tbl.schema) as w: + w.write_table(tbl) + return bytearray(buf.getvalue()) + +# Issue A: overflow. elem_size=4 (UInt32). 4 * 2^62 ≡ 0 (mod 2^64), so the +# unguarded multiply "elem_size * count" would wrap to 0 and pass the check. +OVERFLOW_N = 1 << 62 +d = write_arrow(pa.array([42], type=pa.uint32())) +needle = struct.pack('= 8 would read past the 1-byte bitmap without the fix. +# The bitmap buffer entry is {offset=0, length=8}; search for that specific pair +# to avoid patching the FieldNode null_count field which also equals 8. +arr = pa.array([None if i % 8 == 0 else i for i in range(64)], type=pa.int32()) +d = write_arrow(arr) +bitmap_entry = struct.pack('= 0, "could not find bitmap buffer entry" +d[idx+8 : idx+16] = struct.pack('&1) + local exit_code=$? + if echo "$actual" | grep -qF 'INCORRECT_DATA'; then + echo 'INCORRECT_DATA' + else + local first_line + first_line=$(echo "$actual" | head -1 | cut -c1-200) + echo "FAIL [$label] expected INCORRECT_DATA (exit=${exit_code}); got: ${first_line:-}" + fi +} + +# Issue A: overflow — checkArrowBuffer validates the data buffer before the column is reserved, +# so __builtin_mul_overflow fires and rejects the file as INCORRECT_DATA ("buffer size overflow") +# rather than reaching reserve(2^62). Asserting INCORRECT_DATA proves the checked-arithmetic path +# (a build that skipped it would instead throw CANNOT_ALLOCATE_MEMORY and fail this assertion). +check_incorrect_data overflow $CLICKHOUSE_LOCAL --query "SELECT x FROM file('${TMP_DIR}/overflow.arrow', Arrow)" + +# Issue B: bitmap — checkValidityBitmap must produce INCORRECT_DATA before IsNull reads. +# Use sum() to force column materialisation; count(*) is optimised away and skips data reads. +check_incorrect_data bitmap $CLICKHOUSE_LOCAL --query "SELECT sum(x) FROM file('${TMP_DIR}/bitmap_shrink.arrow', Arrow)" diff --git a/tests/queries/0_stateless/04326_arrow_oob_neg_length.reference b/tests/queries/0_stateless/04326_arrow_oob_neg_length.reference new file mode 100644 index 000000000000..d08d3c723e2e --- /dev/null +++ b/tests/queries/0_stateless/04326_arrow_oob_neg_length.reference @@ -0,0 +1 @@ +INCORRECT_DATA diff --git a/tests/queries/0_stateless/04326_arrow_oob_neg_length.sh b/tests/queries/0_stateless/04326_arrow_oob_neg_length.sh new file mode 100755 index 000000000000..214c7a5c0daf --- /dev/null +++ b/tests/queries/0_stateless/04326_arrow_oob_neg_length.sh @@ -0,0 +1,69 @@ +#!/usr/bin/env bash +# Tags: no-fasttest +# Regression test: FieldNode.length = -1 bypasses checkBinaryOffsetsBuffer. +# +# When FieldNode.length is set to -1 in an Arrow IPC file, checkBinaryOffsetsBuffer +# computes count_plus_one = static_cast(-1) + 1 = 0, required = 0, and the +# buffer-size check passes trivially. The subsequent per-row validation loop iterates +# as if chunk_length = SIZE_MAX, reaching beyond the actual 5-row offsets buffer and +# reading garbage as an offset entry. The per-row bounds check must throw INCORRECT_DATA +# before that garbage value is used to derive a read pointer. + +CUR_DIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd) +# shellcheck source=../shell_config.sh +. "$CUR_DIR"/../shell_config.sh + +TMP_DIR="${CLICKHOUSE_TMP}/${CLICKHOUSE_TEST_UNIQUE_NAME}" +mkdir -p "$TMP_DIR" +trap 'rm -rf "$TMP_DIR"' EXIT + +python3 - "$TMP_DIR" <<'PYEOF' +import struct, io, sys +import pyarrow as pa +import pyarrow.ipc as ipc + +out = sys.argv[1] + +def write_arrow(arr, name='x'): + tbl = pa.table({name: arr}) + buf = io.BytesIO() + with ipc.new_file(buf, tbl.schema) as w: + w.write_table(tbl) + return bytearray(buf.getvalue()) + +# 5 rows of LargeBinaryArray with string lengths [1,2,3,4,6]. +# Cumulative offsets are [0,1,3,6,10,16] — none equals 5, so the only +# int64=5 occurrences in the file are RecordBatch.length and FieldNode.length. +# Patching both to -1 produces a consistent malformed file that Arrow's IPC +# reader accepts (it cross-checks RecordBatch.length == FieldNode.length but +# does not reject negative values) while our code must detect the anomaly. +arr = pa.array([b"a", b"bb", b"ccc", b"dddd", b"ffffff"], type=pa.large_binary()) +d = write_arrow(arr) + +needle = struct.pack('&1) + local exit_code=$? + if echo "$actual" | grep -qF 'INCORRECT_DATA'; then + echo 'INCORRECT_DATA' + else + local first_line + first_line=$(echo "$actual" | head -1 | cut -c1-200) + echo "FAIL [$label] expected INCORRECT_DATA (exit=${exit_code}); got: ${first_line:-}" + fi +} + +check_incorrect_data neg_length \ + $CLICKHOUSE_LOCAL --query "SELECT x FROM file('${TMP_DIR}/neg_length.arrow', Arrow)" diff --git a/tests/queries/0_stateless/04327_arrow_oob_list_offsets.reference b/tests/queries/0_stateless/04327_arrow_oob_list_offsets.reference new file mode 100644 index 000000000000..dbce9be2af41 --- /dev/null +++ b/tests/queries/0_stateless/04327_arrow_oob_list_offsets.reference @@ -0,0 +1,5 @@ +INCORRECT_DATA +INCORRECT_DATA +INCORRECT_DATA +INCORRECT_DATA +INCORRECT_DATA diff --git a/tests/queries/0_stateless/04327_arrow_oob_list_offsets.sh b/tests/queries/0_stateless/04327_arrow_oob_list_offsets.sh new file mode 100755 index 000000000000..2d50f2762619 --- /dev/null +++ b/tests/queries/0_stateless/04327_arrow_oob_list_offsets.sh @@ -0,0 +1,114 @@ +#!/usr/bin/env bash +# Tags: no-fasttest +# Regression tests for OOB reads from malformed Arrow List/LargeList offsets: +# - non-monotonic offsets that underflow the uint64 element count and bypass the +# ColumnArray consistency check (offsets [0,64,65,64] over a 64-byte child); +# - a decreasing offset pair that reaches arrow::ListArray::Flatten before the +# monotonicity check (offsets [64,0,6], previously STD_EXCEPTION from ArrayData::Slice); +# - monotonic, non-negative offsets that point past the values array (offsets [1,1] over a +# zero-length child). +# Each must be rejected as INCORRECT_DATA. + +CUR_DIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd) +# shellcheck source=../shell_config.sh +. "$CUR_DIR"/../shell_config.sh + +TMP_DIR="${CLICKHOUSE_TMP}/${CLICKHOUSE_TEST_UNIQUE_NAME}" +mkdir -p "$TMP_DIR" +trap 'rm -rf "$TMP_DIR"' EXIT + +python3 - "$TMP_DIR" <<'PYEOF' +import struct, io, sys +import pyarrow as pa +import pyarrow.ipc as ipc + +out = sys.argv[1] + +def write_arrow(arr, name='arr'): + tbl = pa.table({name: arr}) + buf = io.BytesIO() + with ipc.new_file(buf, tbl.schema) as w: + w.write_table(tbl) + return bytearray(buf.getvalue()) + +# List(UInt8), int32 offsets. Inner size = 64 bytes (Arrow allocates exactly 64, no padding). +# Valid offsets [0,64,65,66]; patch the last entry 66->64 so they become [0,64,65,64]: row 1 +# starts at inner[64] (OOB) and the uint64 underflow at row 2 wraps last_offset back to 64. +arr = pa.array([[0xAA]*64, [0xBB], [0xCC]], type=pa.list_(pa.uint8())) +d = write_arrow(arr) +idx = d.rfind(struct.pack('= 0 and idx % 4 == 0, f"could not find last offset int32=66 (file={len(d)} bytes)" +d[idx:idx+4] = struct.pack('= 0 and idx64 % 8 == 0, "could not find last offset int64=66" +d64[idx64:idx64+8] = struct.pack(' with a decreasing offset pair that reaches Flatten before the offsets reader runs. +# Valid offsets [0,4,6] are patched to [64,0,6]: Flatten slices values[64..6], which fails inside +# Arrow's ArrayData::Slice when monotonicity is validated only later. The pre-Flatten check rejects it. +arr_decr = pa.array([[1,2,3,4],[5,6]], type=pa.list_(pa.int32())) # offsets [0,4,6] +d_decr = bytearray(write_arrow(arr_decr)) +idx_decr = d_decr.find(struct.pack('<3i', 0, 4, 6)) +assert idx_decr >= 0, "could not find List offsets [0,4,6] in decreasing-offset file" +d_decr[idx_decr:idx_decr+12] = struct.pack('<3i', 64, 0, 6) +open(f'{out}/list_decreasing_offset.arrow', 'wb').write(d_decr) + +# Monotonic, non-negative List/LargeList offsets that point past values.length: an empty list's +# offsets [0,0] are patched to [1,1] while values.length stays 0. Verified via pyarrow round-trip. +def patch_offsets_past_values(arr, packed): + base = bytearray(write_arrow(arr, name='x')) + pos = 0 + while True: + idx = base.find(b'\x00' * len(packed), pos) + assert idx >= 0, "offset buffer of zeros not found" + cand = bytearray(base) + cand[idx:idx+len(packed)] = packed + try: + with ipc.open_file(pa.py_buffer(bytes(cand))) as reader: + a = reader.read_all().column('x').chunks[0] + if a.offsets.to_pylist() == [1, 1] and len(a.values) == 0: + return cand + except Exception: + pass + pos = idx + 1 + +open(f'{out}/list_offset_past_values.arrow', 'wb').write( + patch_offsets_past_values(pa.array([[]], type=pa.list_(pa.int32())), struct.pack('<2i', 1, 1))) +open(f'{out}/largelist_offset_past_values.arrow', 'wb').write( + patch_offsets_past_values(pa.array([[]], type=pa.large_list(pa.int32())), struct.pack('<2q', 1, 1))) +PYEOF + +check_incorrect_data() { + local label="$1"; shift + local actual + actual=$("$@" 2>&1) + local exit_code=$? + if echo "$actual" | grep -qF 'INCORRECT_DATA'; then + echo 'INCORRECT_DATA' + else + local first_line + first_line=$(echo "$actual" | head -1 | cut -c1-200) + echo "FAIL [$label] expected INCORRECT_DATA (exit=${exit_code}); got: ${first_line:-}" + fi +} + +check_incorrect_data list_nonmonotonic \ + $CLICKHOUSE_LOCAL --query "SELECT arrayElement(arr, 2) FROM file('${TMP_DIR}/list_nonmonotonic.arrow', Arrow)" + +check_incorrect_data largelist_nonmonotonic \ + $CLICKHOUSE_LOCAL --query "SELECT arrayElement(arr, 2) FROM file('${TMP_DIR}/largelist_nonmonotonic.arrow', Arrow)" + +check_incorrect_data list_decreasing_offset \ + $CLICKHOUSE_LOCAL --query "SELECT * FROM file('${TMP_DIR}/list_decreasing_offset.arrow', Arrow)" + +check_incorrect_data list_offset_past_values \ + $CLICKHOUSE_LOCAL --query "SELECT * FROM file('${TMP_DIR}/list_offset_past_values.arrow', Arrow)" + +check_incorrect_data largelist_offset_past_values \ + $CLICKHOUSE_LOCAL --query "SELECT * FROM file('${TMP_DIR}/largelist_offset_past_values.arrow', Arrow)" diff --git a/tests/queries/0_stateless/04328_arrow_oob_nested_child_length.reference b/tests/queries/0_stateless/04328_arrow_oob_nested_child_length.reference new file mode 100644 index 000000000000..97564127add2 --- /dev/null +++ b/tests/queries/0_stateless/04328_arrow_oob_nested_child_length.reference @@ -0,0 +1,7 @@ +INCORRECT_DATA +INCORRECT_DATA +INCORRECT_DATA +INCORRECT_DATA +INCORRECT_DATA +INCORRECT_DATA +INCORRECT_DATA diff --git a/tests/queries/0_stateless/04328_arrow_oob_nested_child_length.sh b/tests/queries/0_stateless/04328_arrow_oob_nested_child_length.sh new file mode 100755 index 000000000000..a0a55577c3d1 --- /dev/null +++ b/tests/queries/0_stateless/04328_arrow_oob_nested_child_length.sh @@ -0,0 +1,126 @@ +#!/usr/bin/env bash +# Tags: no-fasttest +# Regression tests for OOB reads from a List/LargeList/FixedSizeList child whose declared length +# is inconsistent with the parent: +# - an empty child with non-zero parent offsets (child length patched to 0) that bypassed the +# ColumnArray empty-child consistency check; +# - a non-empty child shorter than length*stride for a FixedSizeList (child 21 -> 10); +# - a negative child FieldNode.length (-1) that reaches arrow's ArrayData::Slice. +# Each must be rejected as INCORRECT_DATA. + +CUR_DIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd) +# shellcheck source=../shell_config.sh +. "$CUR_DIR"/../shell_config.sh + +TMP_DIR="${CLICKHOUSE_TMP}/${CLICKHOUSE_TEST_UNIQUE_NAME}" +mkdir -p "$TMP_DIR" +trap 'rm -rf "$TMP_DIR"' EXIT + +python3 - "$TMP_DIR" <<'PYEOF' +import struct, io, sys +import pyarrow as pa +import pyarrow.ipc as ipc + +out = sys.argv[1] + +def write_arrow(arr, name='arr'): + tbl = pa.table({name: arr}) + buf = io.BytesIO() + with ipc.new_file(buf, tbl.schema) as w: + w.write_table(tbl) + return bytearray(buf.getvalue()) + +def patch_all_int64(data, old_val, new_val): + """Patch all 8-byte-aligned occurrences of old_val (int64) to new_val.""" + needle = struct.pack('[7] and List with 3 parent rows; child FieldNode.length patched +# 21 -> 0. The declared last offset (21) exceeds the empty child allocation; the ColumnArray +# empty-child short-circuit on unpatched code silently produced offsets past the allocation. +arr_fsl = pa.array([[i]*7 for i in range(3)], type=pa.list_(pa.int32(), 7)) +d_fsl = bytearray(write_arrow(arr_fsl)) +assert patch_all_int64(d_fsl, 21, 0) >= 1, f"no int64=21 found in FSL file ({len(d_fsl)} bytes)" +open(f'{out}/fsl_empty_child.arrow', 'wb').write(d_fsl) + +arr_list = pa.array([[i]*7 for i in range(3)], type=pa.list_(pa.int32())) +d_list = bytearray(write_arrow(arr_list)) +assert patch_all_int64(d_list, 21, 0) >= 1, f"no int64=21 found in List file ({len(d_list)} bytes)" +open(f'{out}/list_empty_child.arrow', 'wb').write(d_list) + +# FixedSizeList[7] with a non-empty child shorter than length*stride: child 21 -> 10. +# Flatten would slice values[0,21] out of a 10-element child; the pre-Flatten stride/size check +# rejects it before Arrow's ArrayData::Slice fails. +arr_f = pa.array([[i]*7 for i in range(3)], type=pa.list_(pa.int32(), 7)) +d_f = bytearray(write_arrow(arr_f, name='x')) +assert patch_all_int64(d_f, 21, 10) >= 1, "no int64=21 found in FSL-short-child file" +open(f'{out}/fsl_short_child.arrow', 'wb').write(d_f) + +# Negative child FieldNode.length (-1): Struct / List / LargeList / FixedSizeList would slice a +# negative-length array inside StructArray::field()/Flatten(). +def patch_fieldnode_length(data, pattern_qs, q_index, value): + pat = struct.pack('<' + 'q'*len(pattern_qs), *pattern_qs) + idx = data.find(pat) + assert idx >= 0, f"FieldNode pattern {pattern_qs} not found" + data[idx + 8*q_index : idx + 8*(q_index+1)] = struct.pack('&1) + local exit_code=$? + if echo "$actual" | grep -qF 'INCORRECT_DATA'; then + echo 'INCORRECT_DATA' + else + local first_line + first_line=$(echo "$actual" | head -1 | cut -c1-200) + echo "FAIL [$label] expected INCORRECT_DATA (exit=${exit_code}); got: ${first_line:-}" + fi +} + +check_incorrect_data fsl_empty_child \ + $CLICKHOUSE_LOCAL --query "SELECT * FROM file('${TMP_DIR}/fsl_empty_child.arrow', Arrow)" + +check_incorrect_data list_empty_child \ + $CLICKHOUSE_LOCAL --query "SELECT * FROM file('${TMP_DIR}/list_empty_child.arrow', Arrow)" + +check_incorrect_data fsl_short_child \ + $CLICKHOUSE_LOCAL --query "SELECT * FROM file('${TMP_DIR}/fsl_short_child.arrow', Arrow)" + +check_incorrect_data struct_child_neg_length \ + $CLICKHOUSE_LOCAL --query "SELECT * FROM file('${TMP_DIR}/struct_child_neg_length.arrow', Arrow)" + +check_incorrect_data list_child_neg_length \ + $CLICKHOUSE_LOCAL --query "SELECT * FROM file('${TMP_DIR}/list_child_neg_length.arrow', Arrow)" + +check_incorrect_data largelist_child_neg_length \ + $CLICKHOUSE_LOCAL --query "SELECT * FROM file('${TMP_DIR}/largelist_child_neg_length.arrow', Arrow)" + +check_incorrect_data fixedlist_child_neg_length \ + $CLICKHOUSE_LOCAL --query "SELECT * FROM file('${TMP_DIR}/fixedlist_child_neg_length.arrow', Arrow)" diff --git a/tests/queries/0_stateless/04329_arrow_oob_struct_fields.reference b/tests/queries/0_stateless/04329_arrow_oob_struct_fields.reference new file mode 100644 index 000000000000..3b5cc179cd0b --- /dev/null +++ b/tests/queries/0_stateless/04329_arrow_oob_struct_fields.reference @@ -0,0 +1,6 @@ +INCORRECT_DATA +INCORRECT_DATA +INCORRECT_DATA +INCORRECT_DATA +INCORRECT_DATA +INCORRECT_DATA diff --git a/tests/queries/0_stateless/04329_arrow_oob_struct_fields.sh b/tests/queries/0_stateless/04329_arrow_oob_struct_fields.sh new file mode 100755 index 000000000000..8aa7d33ba4a8 --- /dev/null +++ b/tests/queries/0_stateless/04329_arrow_oob_struct_fields.sh @@ -0,0 +1,140 @@ +#!/usr/bin/env bash +# Tags: no-fasttest +# Regression tests for OOB reads from malformed Arrow Struct/Map columns: +# - a struct field shorter than the parent struct (field length 5 -> 3), which arrow's +# StructArray::field() silently Slice-clamps, leaving ColumnTuple fields of unequal size +# (also covers Map value vs key length mismatch); +# - a sliced struct whose child field is too short for the slice range (parent offsets [1,2] +# over a 0-length field) and would slice child[1:2] inside ArrayData::Slice; +# - a fields-less (zero-field) struct that declares a forged-huge length while carrying a +# validity bitmap too small for it (the bitmap is validated before the null map is built). +# Each must be rejected as INCORRECT_DATA. +# A fields-less struct with no bitmap is a legitimate empty Tuple() and is not rejected here; a +# forged huge length in that case has no backing buffer and is bounded by max_memory_usage. + +CUR_DIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd) +# shellcheck source=../shell_config.sh +. "$CUR_DIR"/../shell_config.sh + +TMP_DIR="${CLICKHOUSE_TMP}/${CLICKHOUSE_TEST_UNIQUE_NAME}" +mkdir -p "$TMP_DIR" +trap 'rm -rf "$TMP_DIR"' EXIT + +python3 - "$TMP_DIR" <<'PYEOF' +import struct, io, sys +import pyarrow as pa +import pyarrow.ipc as ipc + +out = sys.argv[1] +HUGE = 1 << 62 + +def write_arrow(arr, name='arr'): + tbl = pa.table({name: arr}) + buf = io.BytesIO() + with ipc.new_file(buf, tbl.schema) as w: + w.write_table(tbl) + return bytearray(buf.getvalue()) + +def write_struct_schema(arr, name, nullable=True): + sch = pa.schema([pa.field(name, arr.type, nullable=nullable)]) + buf = io.BytesIO() + with ipc.new_file(buf, sch) as w: + w.write_table(pa.Table.from_arrays([arr], schema=sch)) + return bytearray(buf.getvalue()) + +# Struct with 5 rows; field b FieldNode.length patched 5 -> 3. The FieldNode +# vector is [struct(5,0), a(5,0), b(5,0)]; b's length is the 5th int64 (+32 from the vector start). +a_col = pa.array([1,2,3,4,5], type=pa.int32()) +b_col = pa.array([10,20,30,40,50], type=pa.int32()) +d_struct = write_arrow(pa.StructArray.from_arrays([a_col, b_col], names=['a','b']), name='s') +idx_struct = d_struct.find(struct.pack('<6q', 5,0, 5,0, 5,0)) +assert idx_struct >= 0, "could not find Struct FieldNode pattern in struct file" +d_struct[idx_struct+32:idx_struct+40] = struct.pack(' with 1 row / 5 entries; value FieldNode.length patched 5 -> 3. The FieldNode +# vector is [map(1,0), entries(5,0), key(5,0), value(5,0)]; value's length is the 7th int64 (+48). +arr_map = pa.array([list(zip([1,2,3,4,5], [10,20,30,40,50]))], type=pa.map_(pa.int32(), pa.int32())) +d_map = write_arrow(arr_map, name='m') +idx_map = d_map.find(struct.pack('<8q', 1,0, 5,0, 5,0, 5,0)) +assert idx_map >= 0, "could not find Map FieldNode pattern in map file" +d_map[idx_map+48:idx_map+56] = struct.pack('= 0, f"FieldNode pattern {pattern_qs} not found" + data[idx + 8*struct_len_idx : idx + 8*(struct_len_idx+1)] = struct.pack(' 2 + data[idx + 8*child_len_idx : idx + 8*(child_len_idx+1)] = struct.pack(' 0 + old = struct.pack(off_fmt, 0, 1) + new = struct.pack(off_fmt, 1, 2) + pos = 0 + while True: + oi = data.find(old, pos) + assert oi >= 0, "list offsets [0,1] not found" + cand = bytearray(data) + cand[oi:oi+len(old)] = new + try: + with ipc.open_file(pa.py_buffer(bytes(cand))) as reader: + a = reader.read_all().column(name).chunks[0] + if a.offsets.to_pylist() == [1, 2]: + return cand + except Exception: + pass + pos = oi + 1 + +open(f'{out}/list_struct_offset_child_zero.arrow', 'wb').write( + patch_struct_slice(pa.array([[{'a': 20}]], type=pa.list_(pa.struct([('a', pa.int32())]))), 'x', [1,0,1,0,1,0], 2, 4, '<2i')) +open(f'{out}/largelist_struct_offset_child_zero.arrow', 'wb').write( + patch_struct_slice(pa.array([[{'a': 20}]], type=pa.large_list(pa.struct([('a', pa.int32())]))), 'x', [1,0,1,0,1,0], 2, 4, '<2q')) +open(f'{out}/map_offset_key_zero.arrow', 'wb').write( + patch_struct_slice(pa.array([[(1, 10)]], type=pa.map_(pa.int32(), pa.int32())), 'm', [1,0,1,0,1,0,1,0], 2, 4, '<2i')) + +# Fields-less Struct WITH a validity bitmap (null_count>0) but a forged-huge length; the null-map +# allocation must be rejected by validating the bitmap (too small) before allocating. +# (A fields-less struct with no bitmap is a legitimate empty Tuple() and is not rejected; a forged +# huge length there has no backing buffer and is bounded by max_memory_usage, like the null type.) +d = write_struct_schema(pa.array([{}, None, {}, None, {}], type=pa.struct([])), 's') +pos = [i for i in range(0, len(d) - 7, 8) if struct.unpack_from('= 2 +for p in pos: + d[p:p+8] = struct.pack('&1) + local exit_code=$? + if echo "$actual" | grep -qF 'INCORRECT_DATA'; then + echo 'INCORRECT_DATA' + else + local first_line + first_line=$(echo "$actual" | head -1 | cut -c1-200) + echo "FAIL [$label] expected INCORRECT_DATA (exit=${exit_code}); got: ${first_line:-}" + fi +} + +check_incorrect_data struct_short_b \ + $CLICKHOUSE_LOCAL --query "SELECT * FROM file('${TMP_DIR}/struct_short_b.arrow', Arrow)" + +check_incorrect_data map_value_short \ + $CLICKHOUSE_LOCAL --query "SELECT * FROM file('${TMP_DIR}/map_value_short.arrow', Arrow)" + +check_incorrect_data list_struct_offset_child_zero \ + $CLICKHOUSE_LOCAL --query "SELECT * FROM file('${TMP_DIR}/list_struct_offset_child_zero.arrow', Arrow)" + +check_incorrect_data largelist_struct_offset_child_zero \ + $CLICKHOUSE_LOCAL --query "SELECT * FROM file('${TMP_DIR}/largelist_struct_offset_child_zero.arrow', Arrow)" + +check_incorrect_data map_offset_key_zero \ + $CLICKHOUSE_LOCAL --query "SELECT * FROM file('${TMP_DIR}/map_offset_key_zero.arrow', Arrow)" + +check_incorrect_data empty_struct_huge_nullcount \ + $CLICKHOUSE_LOCAL --query "SELECT * FROM file('${TMP_DIR}/empty_struct_huge_nullcount.arrow', Arrow)" diff --git a/tests/queries/0_stateless/04330_arrow_oob_declared_length.reference b/tests/queries/0_stateless/04330_arrow_oob_declared_length.reference new file mode 100644 index 000000000000..6e3ef492598f --- /dev/null +++ b/tests/queries/0_stateless/04330_arrow_oob_declared_length.reference @@ -0,0 +1,9 @@ +INCORRECT_DATA +INCORRECT_DATA +INCORRECT_DATA +INCORRECT_DATA +INCORRECT_DATA +INCORRECT_DATA +INCORRECT_DATA +INCORRECT_DATA +INCORRECT_DATA diff --git a/tests/queries/0_stateless/04330_arrow_oob_declared_length.sh b/tests/queries/0_stateless/04330_arrow_oob_declared_length.sh new file mode 100755 index 000000000000..1e03f09b51e6 --- /dev/null +++ b/tests/queries/0_stateless/04330_arrow_oob_declared_length.sh @@ -0,0 +1,144 @@ +#!/usr/bin/env bash +# Tags: no-fasttest +# Regression tests for forged top-level Arrow lengths that made a reader reserve column memory +# before validating buffers (previously CANNOT_ALLOCATE_MEMORY instead of INCORRECT_DATA): +# - fixed-width columns (Int32, Bool, Decimal) and an empty Struct whose RecordBatch/FieldNode +# length is patched negative or to 2^62; +# - a LargeList deriving a 2^62 flattened child length from 64-bit offsets over a one-element child; +# - the JSON reader (Binary/LargeBinary with JSON logical type) reserving from the declared +# length before validating the offsets buffer. +# Each must be rejected as INCORRECT_DATA before the reserve. + +CUR_DIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd) +# shellcheck source=../shell_config.sh +. "$CUR_DIR"/../shell_config.sh + +TMP_DIR="${CLICKHOUSE_TMP}/${CLICKHOUSE_TEST_UNIQUE_NAME}" +mkdir -p "$TMP_DIR" +trap 'rm -rf "$TMP_DIR"' EXIT + +python3 - "$TMP_DIR" <<'PYEOF' +import struct, io, sys +import pyarrow as pa +import pyarrow.ipc as ipc + +out = sys.argv[1] +NEG = -1 +HUGE = 1 << 62 + +def write_arrow(arr, name='arr'): + tbl = pa.table({name: arr}) + buf = io.BytesIO() + with ipc.new_file(buf, tbl.schema) as w: + w.write_table(tbl) + return bytearray(buf.getvalue()) + +# Fixed-width / empty-struct columns: patch every aligned int64 equal to the 5-row count +# (RecordBatch.length and FieldNode.length; the data values avoid 5) to a negative or huge value. +def patch_row_count(arr, new, name, count=5): + data = bytearray(write_arrow(arr, name=name)) + pos = [i for i in range(0, len(data) - 7, 8) if struct.unpack_from('= 2, f"expected >=2 aligned int64={count}, got {len(pos)}" + for p in pos: + data[p:p+8] = struct.pack(' [0,2^62] and child FieldNode.length -> 2^62. +ll = bytearray(write_arrow(pa.array([[123]], type=pa.large_list(pa.int32())), name='x')) +fn = ll.find(struct.pack('<4q', 1, 0, 1, 0)) +assert fn >= 0, "LargeList FieldNode pattern not found" +ll[fn+16:fn+24] = struct.pack('= 0, "LargeList offset buffer [0,1] not found" +ll[ob:ob+16] = struct.pack('<2q', 0, HUGE) +open(f'{out}/largelist_huge_child_length.arrow', 'wb').write(ll) + +# JSON reader: Binary/LargeBinary with JSON logical type and a 1-row file whose RecordBatch and +# FieldNode length are forged to 2^30. +def write_json_binary(binary_type): + field = pa.field('x', binary_type, metadata={b'PARQUET:logical_type': b'JSON'}) + sch = pa.schema([field]) + buf = io.BytesIO() + with ipc.new_file(buf, sch) as w: + w.write_table(pa.Table.from_arrays([pa.array([b'{"a":1}'], type=binary_type)], schema=sch)) + return bytearray(buf.getvalue()) + +def forge_one_row_to_huge(data, new_length): + positions = [i for i in range(0, len(data) - 7, 8) if struct.unpack_from(' a 16 MB reserve; the width check must reject it +# as INCORRECT_DATA before reserving (the read below uses a memory limit below that reserve). +open(f'{out}/fixedbinary1_as_uuid.arrow', 'wb').write( + write_arrow(pa.array([b'\x01'] * 1_000_000, type=pa.binary(1)), name='x')) +PYEOF + +check_incorrect_data() { + local label="$1"; shift + local actual + actual=$("$@" 2>&1) + local exit_code=$? + if echo "$actual" | grep -qF 'INCORRECT_DATA'; then + echo 'INCORRECT_DATA' + else + local first_line + first_line=$(echo "$actual" | head -1 | cut -c1-200) + echo "FAIL [$label] expected INCORRECT_DATA (exit=${exit_code}); got: ${first_line:-}" + fi +} + +check_incorrect_data int32_neg_length \ + $CLICKHOUSE_LOCAL --query "SELECT * FROM file('${TMP_DIR}/int32_neg_length.arrow', Arrow)" + +check_incorrect_data int32_huge_length \ + $CLICKHOUSE_LOCAL --query "SELECT * FROM file('${TMP_DIR}/int32_huge_length.arrow', Arrow)" + +check_incorrect_data bool_neg_length \ + $CLICKHOUSE_LOCAL --query "SELECT * FROM file('${TMP_DIR}/bool_neg_length.arrow', Arrow)" + +check_incorrect_data decimal_huge_length \ + $CLICKHOUSE_LOCAL --query "SELECT * FROM file('${TMP_DIR}/decimal_huge_length.arrow', Arrow)" + +check_incorrect_data empty_struct_neg_length \ + $CLICKHOUSE_LOCAL --query "SELECT * FROM file('${TMP_DIR}/empty_struct_neg_length.arrow', Arrow)" + +check_incorrect_data largelist_huge_child_length \ + $CLICKHOUSE_LOCAL --query "SELECT * FROM file('${TMP_DIR}/largelist_huge_child_length.arrow', Arrow)" + +check_incorrect_data binary_json_huge_length \ + $CLICKHOUSE_LOCAL --query "SELECT * FROM file('${TMP_DIR}/binary_json_huge_length.arrow', Arrow) FORMAT Null SETTINGS allow_experimental_json_type=1" + +check_incorrect_data largebinary_json_huge_length \ + $CLICKHOUSE_LOCAL --query "SELECT * FROM file('${TMP_DIR}/largebinary_json_huge_length.arrow', Arrow) FORMAT Null SETTINGS allow_experimental_json_type=1" + +# The 8 MB memory limit is below the 16 MB UUID-column reserve but above the 1 MB input, so an +# implementation that reserves before checking byte_width fails with MEMORY_LIMIT_EXCEEDED; the +# fixed reader rejects the type with INCORRECT_DATA before reserving. +check_incorrect_data fixedbinary1_as_uuid \ + $CLICKHOUSE_LOCAL --query "SELECT * FROM file('${TMP_DIR}/fixedbinary1_as_uuid.arrow', Arrow, 'x UUID') FORMAT Null SETTINGS max_memory_usage=8000000" diff --git a/tests/queries/0_stateless/04331_arrow_oob_validity_bitmap.reference b/tests/queries/0_stateless/04331_arrow_oob_validity_bitmap.reference new file mode 100644 index 000000000000..35233a580cac --- /dev/null +++ b/tests/queries/0_stateless/04331_arrow_oob_validity_bitmap.reference @@ -0,0 +1,4 @@ +INCORRECT_DATA +INCORRECT_DATA +INCORRECT_DATA +INCORRECT_DATA diff --git a/tests/queries/0_stateless/04331_arrow_oob_validity_bitmap.sh b/tests/queries/0_stateless/04331_arrow_oob_validity_bitmap.sh new file mode 100755 index 000000000000..dfe9108c8a7c --- /dev/null +++ b/tests/queries/0_stateless/04331_arrow_oob_validity_bitmap.sh @@ -0,0 +1,141 @@ +#!/usr/bin/env bash +# Tags: no-fasttest +# Regression tests for OOB reads of an Arrow validity bitmap: +# - a truncated child bitmap after Flatten/Slice: a sliced child has an unknown null_count, and +# arrow::ChunkedArray's constructor scans the (shrunk) bitmap (List(Nullable(int32)) child +# bitmap 16 -> 1 byte; Struct field b sliced with bitmap 9 -> 1 byte); +# - an unknown FieldNode.null_count (-1) over a forged-huge length with a 1-byte bitmap, where +# building the arrow Table scans the bitmap over the declared length (heap OOB in CountSetBits). +# Each must be rejected as INCORRECT_DATA before any null_count scan. + +CUR_DIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd) +# shellcheck source=../shell_config.sh +. "$CUR_DIR"/../shell_config.sh + +TMP_DIR="${CLICKHOUSE_TMP}/${CLICKHOUSE_TEST_UNIQUE_NAME}" +mkdir -p "$TMP_DIR" +trap 'rm -rf "$TMP_DIR"' EXIT + +python3 - "$TMP_DIR" <<'PYEOF' +import struct, io, sys +import pyarrow as pa +import pyarrow.ipc as ipc + +out = sys.argv[1] + +def write_arrow(arr, name='arr'): + tbl = pa.table({name: arr}) + buf = io.BytesIO() + with ipc.new_file(buf, tbl.schema) as w: + w.write_table(tbl) + return bytearray(buf.getvalue()) + +def shrink_buffer_len(data, old_len, new_len): + """Shrink the first buffer-length field equal to old_len (a Buffer in the IPC metadata is + {offset:int64, length:int64}, so the length is preceded by a valid offset).""" + needle = struct.pack('= 8: + prev = struct.unpack_from('= 8 and 0 <= struct.unpack_from(' with 65 rows: field b's FieldNode.length is patched 65 -> 70 +# so StructArray::field() Slice-clamps it (unknown null_count), and b's 9-byte bitmap is shrunk to 1. +a_e = pa.array(list(range(65)), type=pa.int32()) +b_e = pa.array([None if i % 7 == 0 else i for i in range(65)], type=pa.int32()) +d_e2 = bytearray(write_arrow(pa.StructArray.from_arrays([a_e, b_e], names=['a', 'b']), name='s')) +bnc = sum(1 for i in range(65) if i % 7 == 0) +idx_e2 = d_e2.find(struct.pack('<6q', 65,0, 65,0, 65,bnc)) +assert idx_e2 >= 0, "could not find Struct FieldNode pattern in struct-bitmap file" +d_e2[idx_e2+32:idx_e2+40] = struct.pack('&1) + local exit_code=$? + if echo "$actual" | grep -qF 'INCORRECT_DATA'; then + echo 'INCORRECT_DATA' + else + local first_line + first_line=$(echo "$actual" | head -1 | cut -c1-200) + echo "FAIL [$label] expected INCORRECT_DATA (exit=${exit_code}); got: ${first_line:-}" + fi +} + +check_incorrect_data list_child_bitmap \ + $CLICKHOUSE_LOCAL --query "SELECT sum(length(x)) FROM file('${TMP_DIR}/list_child_bitmap.arrow', Arrow)" + +check_incorrect_data struct_child_bitmap \ + $CLICKHOUSE_LOCAL --query "SELECT * FROM file('${TMP_DIR}/struct_child_bitmap.arrow', Arrow)" + +check_incorrect_data int32_unknown_nullcount \ + $CLICKHOUSE_LOCAL --query "SELECT * FROM file('${TMP_DIR}/int32_unknown_nullcount.arrow', Arrow)" + +check_incorrect_data list_unknown_nullcount \ + $CLICKHOUSE_LOCAL --query "SELECT * FROM file('${TMP_DIR}/list_unknown_nullcount.arrow', Arrow)" From 7696bab5662bb22abb64a353e3f57fe60ad24173 Mon Sep 17 00:00:00 2001 From: robot-clickhouse Date: Fri, 12 Jun 2026 13:33:01 +0000 Subject: [PATCH 038/146] Backport #105544 to 26.3: Fix ignoring `input_format_try_infer_datetimes` during insertion into shared data in JSON --- src/Formats/JSONExtractTree.cpp | 1 + ...data_respect_try_infer_datetimes.reference | 4 +++ ...shared_data_respect_try_infer_datetimes.sh | 35 +++++++++++++++++++ 3 files changed, 40 insertions(+) create mode 100644 tests/queries/0_stateless/04264_json_shared_data_respect_try_infer_datetimes.reference create mode 100755 tests/queries/0_stateless/04264_json_shared_data_respect_try_infer_datetimes.sh diff --git a/src/Formats/JSONExtractTree.cpp b/src/Formats/JSONExtractTree.cpp index 29934e72dcaf..e127fe77cd67 100644 --- a/src/Formats/JSONExtractTree.cpp +++ b/src/Formats/JSONExtractTree.cpp @@ -2206,6 +2206,7 @@ class ObjectJSONNode : public JSONExtractTreeNode } } + if (format_settings.try_infer_datetimes) { DateTime64 value; if (tryInferDateTime64FromString(data, value, format_settings, time_zone_for_schema_inference, utc_time_zone_for_schema_inference)) diff --git a/tests/queries/0_stateless/04264_json_shared_data_respect_try_infer_datetimes.reference b/tests/queries/0_stateless/04264_json_shared_data_respect_try_infer_datetimes.reference new file mode 100644 index 000000000000..d4e612e75c69 --- /dev/null +++ b/tests/queries/0_stateless/04264_json_shared_data_respect_try_infer_datetimes.reference @@ -0,0 +1,4 @@ +String +2024-01-15 12:30:45 +Date +DateTime diff --git a/tests/queries/0_stateless/04264_json_shared_data_respect_try_infer_datetimes.sh b/tests/queries/0_stateless/04264_json_shared_data_respect_try_infer_datetimes.sh new file mode 100755 index 000000000000..f1f861ccf6ed --- /dev/null +++ b/tests/queries/0_stateless/04264_json_shared_data_respect_try_infer_datetimes.sh @@ -0,0 +1,35 @@ +#!/usr/bin/env bash +# Tags: no-fasttest +# Test that insertIntoSharedData respects input_format_try_infer_datetimes +# setting for DateTime64 inference. +# https://github.com/ClickHouse/ClickHouse/issues/103221 + +CUR_DIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd) +# shellcheck source=../shell_config.sh +. "$CUR_DIR"/../shell_config.sh + +${CLICKHOUSE_CLIENT} -q "SET enable_json_type = 1" + +# Shared data path (max_dynamic_paths=0). +# With try_infer_datetimes=0, datetime strings must be stored as String. +${CLICKHOUSE_CLIENT} -q "DROP TABLE IF EXISTS t_shared" +${CLICKHOUSE_CLIENT} -q "CREATE TABLE t_shared (data JSON(max_dynamic_paths=0)) ENGINE = MergeTree ORDER BY tuple() SETTINGS enable_json_type=1" + +echo '{"data": {"ts": "2024-01-15 12:30:45"}}' | ${CLICKHOUSE_CLIENT} --input_format_try_infer_dates=1 --input_format_try_infer_datetimes=0 -q "INSERT INTO t_shared FORMAT JSONEachRow" + +${CLICKHOUSE_CLIENT} -q "SELECT dynamicType(data.ts) FROM t_shared" +${CLICKHOUSE_CLIENT} -q "SELECT data.ts FROM t_shared" + +# Verify that dates are still inferred when try_infer_dates=1. +${CLICKHOUSE_CLIENT} -q "TRUNCATE TABLE t_shared" +echo '{"data": {"d": "2024-01-15"}}' | ${CLICKHOUSE_CLIENT} --input_format_try_infer_dates=1 --input_format_try_infer_datetimes=0 -q "INSERT INTO t_shared FORMAT JSONEachRow" + +${CLICKHOUSE_CLIENT} -q "SELECT dynamicType(data.d) FROM t_shared" + +# Verify that DateTime64 is inferred when try_infer_datetimes=1. +${CLICKHOUSE_CLIENT} -q "TRUNCATE TABLE t_shared" +echo '{"data": {"ts": "2024-01-15 12:30:45"}}' | ${CLICKHOUSE_CLIENT} --input_format_try_infer_dates=1 --input_format_try_infer_datetimes=1 -q "INSERT INTO t_shared FORMAT JSONEachRow" + +${CLICKHOUSE_CLIENT} -q "SELECT dynamicType(data.ts) FROM t_shared" + +${CLICKHOUSE_CLIENT} -q "DROP TABLE t_shared" From a3ed4511f27a76267366da32ecef2266e85e5e8d Mon Sep 17 00:00:00 2001 From: robot-clickhouse Date: Fri, 12 Jun 2026 13:35:19 +0000 Subject: [PATCH 039/146] Backport #100401 to 26.3: Fix exception in join order optimizer when column names overlap --- .../QueryPlan/Optimizations/optimizeJoin.cpp | 22 +++++++++++++++++++ ...timizer_overlapping_column_names.reference | 0 ...oin_optimizer_overlapping_column_names.sql | 18 +++++++++++++++ 3 files changed, 40 insertions(+) create mode 100644 tests/queries/0_stateless/04054_join_optimizer_overlapping_column_names.reference create mode 100644 tests/queries/0_stateless/04054_join_optimizer_overlapping_column_names.sql diff --git a/src/Processors/QueryPlan/Optimizations/optimizeJoin.cpp b/src/Processors/QueryPlan/Optimizations/optimizeJoin.cpp index eec6001aec3d..9eac300cf500 100644 --- a/src/Processors/QueryPlan/Optimizations/optimizeJoin.cpp +++ b/src/Processors/QueryPlan/Optimizations/optimizeJoin.cpp @@ -1222,6 +1222,28 @@ void optimizeJoinLogicalImpl(JoinStepLogical * join_step, QueryPlan::Node & node return; } + /// Skip join order optimization if children's output headers have overlapping column names. + /// The `JoinExpressionActions` constructor used during join reconstruction requires unique column names + /// across left and right sides. Overlapping names can occur when scalar subquery results and join + /// table aliases collide (e.g. both sides produce `__table2`). + { + auto left_header = node.children[0]->step->getOutputHeader(); + auto right_header = node.children[1]->step->getOutputHeader(); + + std::unordered_set left_names; + for (const auto & col : *left_header) + left_names.insert(col.name); + + for (const auto & col : *right_header) + { + if (left_names.contains(col.name)) + { + join_step->setOptimized(); + return; + } + } + } + QueryGraphBuilder query_graph_builder(optimization_settings, node, join_step->getJoinSettings(), join_step->getSortingSettings()); query_graph_builder.context->dummy_stats = join_step->getDummyStats(); diff --git a/tests/queries/0_stateless/04054_join_optimizer_overlapping_column_names.reference b/tests/queries/0_stateless/04054_join_optimizer_overlapping_column_names.reference new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/tests/queries/0_stateless/04054_join_optimizer_overlapping_column_names.sql b/tests/queries/0_stateless/04054_join_optimizer_overlapping_column_names.sql new file mode 100644 index 000000000000..b2cc11a2ab40 --- /dev/null +++ b/tests/queries/0_stateless/04054_join_optimizer_overlapping_column_names.sql @@ -0,0 +1,18 @@ +-- Regression test: join order optimizer must not throw exception when +-- left and right sides of a join have overlapping internal column names. +-- This can happen when scalar subquery results and join table aliases collide. +-- https://s3.amazonaws.com/clickhouse-test-reports/json.html?PR=100398&sha=d69b29735a13a4e8b1c0b95f263a4d962a8943da&name_0=PR&name_1=AST%20fuzzer%20%28amd_debug%2C%20targeted%2C%20old_compatibility%29 + +SET enable_analyzer = 1; +SET allow_suspicious_low_cardinality_types = 1; +SET allow_deprecated_syntax_for_merge_tree = 1; + +DROP TABLE IF EXISTS t_04054; +CREATE TABLE t_04054 (part_date Date, pk_date LowCardinality(Date), date LowCardinality(Nullable(Date))) ENGINE = MergeTree(part_date, pk_date, 8192); + +-- Fuzzed query that triggered a LOGICAL_ERROR exception in debug builds. +-- The combination of scalar subqueries and NOT IN produces overlapping internal column names +-- (__table2 appears in both left and right join headers). +SELECT materialize(toInt256(-2147483649)) FROM t_04054 PREWHERE intDivOrZero(toInt64(isNotNull(-1)), (SELECT modulo(toNullable(-1), 255))) WHERE (intDiv(multiIf(lessOrEquals(moduloOrZero(intDivOrZero(minus(NULL, divide(intDivOrZero(NULL, minus(NULL, intDiv((SELECT DISTINCT NULL LIMIT 65536), '\0'))), concat(toInt64(plus(divide(intDiv(NULL, moduloOrZero(NULL, (SELECT if(7, (SELECT DISTINCT 255 AS alias980, NULL), in(part_date))))), toLowCardinality(materialize(1025))), toNullable(257)), 100), NULL, toLowCardinality(materialize('ffffffff-ffff-ffff-ffff-ffffffffffff')), (SELECT minus(NULL, '33'))))), divide(1025, NULL)), NULL), pk_date), isNull(plus(plus((pk_date IN toDate(toNullable(toFixedString('2018-04-19', 10)))), modulo(-2147483649, 65535)), toUInt32(toLowCardinality(2147483646)))), modulo((SELECT DISTINCT '-0.00\0000'), (SELECT DISTINCT toNullable(NULL)))), assumeNotNull(-1))) NOT IN (pk_date) FORMAT Null; + +DROP TABLE t_04054; From 887e81a47e91390cc596f83a9d202724fc2dc4e3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ra=C3=BAl=20Mar=C3=ADn?= Date: Fri, 12 Jun 2026 15:54:14 +0000 Subject: [PATCH 040/146] Adapt Arrow OOB regression tests to 26.3 26.3 has no BinaryView/StringView reader and no UUID FixedSizeBinary reader, so the cases targeting them are removed: view types are rejected at schema extraction and the FixedString(1)-to-UUID conversion is caught by the memory limit instead of the width check. --- .../04309_arrow_oob_reads_offsets.reference | 4 -- .../04309_arrow_oob_reads_offsets.sh | 48 +------------------ .../04330_arrow_oob_declared_length.reference | 1 - .../04330_arrow_oob_declared_length.sh | 12 ----- 4 files changed, 1 insertion(+), 64 deletions(-) diff --git a/tests/queries/0_stateless/04309_arrow_oob_reads_offsets.reference b/tests/queries/0_stateless/04309_arrow_oob_reads_offsets.reference index e99e3dad7246..f9f273e5a142 100644 --- a/tests/queries/0_stateless/04309_arrow_oob_reads_offsets.reference +++ b/tests/queries/0_stateless/04309_arrow_oob_reads_offsets.reference @@ -9,7 +9,3 @@ INCORRECT_DATA INCORRECT_DATA INCORRECT_DATA INCORRECT_DATA -INCORRECT_DATA -INCORRECT_DATA -INCORRECT_DATA -INCORRECT_DATA diff --git a/tests/queries/0_stateless/04309_arrow_oob_reads_offsets.sh b/tests/queries/0_stateless/04309_arrow_oob_reads_offsets.sh index 1c4a38394dd4..37f81177071a 100755 --- a/tests/queries/0_stateless/04309_arrow_oob_reads_offsets.sh +++ b/tests/queries/0_stateless/04309_arrow_oob_reads_offsets.sh @@ -1,20 +1,16 @@ #!/usr/bin/env bash # Tags: no-fasttest # Regression tests for heap out-of-bounds reads in Arrow IPC format readers -# (view-struct buffer and offsets-buffer readers). +# (offsets-buffer readers). # -# 17. readColumnWithViewData (StringViewArray) – view-struct buffer (16 bytes/row) -# 18. readColumnWithViewData (BinaryViewArray) – view-struct buffer # 19. readColumnWithStringData – offsets buffer over-read before per-row data check # 20. readOffsetsFromArrowListColumn – List offsets Int32Array via Value() # 21. getNestedArrowColumn (Map) – Map offsets array via ListArray::Flatten() # 22. readColumnWithJSONData – BinaryArray offsets buffer (same path as 19) -# 23. readColumnWithViewData – view struct length field exceeds variadic data buffer # 24. readColumnWithJSONData – data buffer over-read (value_offset+length > data size) # 25. readColumnWithBigNumberFromBinaryData – offsets buffer read before size check # 26. readColumnWithGeoData – offsets buffer read before per-row data-buffer check # 27. readIPv6ColumnFromBinaryData – offsets buffer read before size check -# 28. checkViewStruct – negative view size passes is_inline(), wraps in accumulation # 29. readColumnWithBigNumberFromBinaryData – data buffer over-read in copy loop # 30. readIPv6ColumnFromBinaryData – data buffer over-read in copy loop # 31. getNestedArrowColumn – nullable List: valid offsets/child but truncated parent bitmap @@ -67,16 +63,6 @@ def shrink_first_buffer(data, full_len, new_len): return data pos = idx + 1 -# 17. StringViewArray: the view-struct buffer (buffers[1], 16 bytes/element) must fit -# all declared rows. Strings > 12 bytes force non-inline view structs. -# 7 rows * 16 bytes = 112-byte buffer; shrink declared length to 16 (1 entry). -d = write_arrow(pa.array(['x'*13]*7, type=pa.string_view())) -open(f'{out}/strview.arrow', 'wb').write(shrink_first_buffer(d, 7*16, 16)) - -# 18. BinaryViewArray: same approach. -d = write_arrow(pa.array([b'x'*13]*7, type=pa.binary_view())) -open(f'{out}/binview.arrow', 'wb').write(shrink_first_buffer(d, 7*16, 16)) - # 19. String (BinaryArray): value_offset(i) reads the offsets buffer (buffers[1]) # before the per-row data check; checkBinaryOffsetsBuffer catches it first. # 1 row → tiny offsets buffer, inflated to 16384 rows. @@ -98,21 +84,6 @@ open(f'{out}/map.arrow', 'wb').write(inflate_row_count(d, 1)) d = write_arrow(pa.array([b'{"a":1}'], type=pa.binary())) open(f'{out}/json.arrow', 'wb').write(inflate_row_count(d, 1)) -# 23. View data-buffer: checkedCastView validates the view-struct buffer but the -# length field inside each view struct is attacker-controlled and used as the -# memcpy size against the variadic data buffer. -# 96-char non-inline string → patch last int32 of its view struct to ~512 MB. -d = write_arrow(pa.array(['X'*96], type=pa.string_view())) -def patch_last_i32(data, pattern, newval): - i = data.rfind(pattern) - assert i >= 0 - data[i:i+4] = struct.pack('size(). # 1 row of 96 'A' bytes: offsets = [0, 96]. Patch the final offset (offset[1]) to @@ -139,17 +110,6 @@ open(f'{out}/geo.arrow', 'wb').write(inflate_row_count(d, 1)) d = write_arrow(pa.array([b'\x00'*16], type=pa.binary())) open(f'{out}/ipv6_binary.arrow', 'wb').write(inflate_row_count(d, 1)) -# 28. checkViewStruct: a view-struct with size = -1 satisfies is_inline() (-1 <= 12) -# and was previously returned without error; static_cast(-1) in the -# accumulation loop wraps to SIZE_MAX, corrupting total_bytes_size. -# Build a 2-row StringViewArray with valid inline rows, then patch row-0's size to -1. -d = write_arrow(pa.array(['a', 'b'], type=pa.string_view())) -needle = struct.pack('= 0 -d[idx:idx+4] = struct.pack(' a 16 MB reserve; the width check must reject it -# as INCORRECT_DATA before reserving (the read below uses a memory limit below that reserve). -open(f'{out}/fixedbinary1_as_uuid.arrow', 'wb').write( - write_arrow(pa.array([b'\x01'] * 1_000_000, type=pa.binary(1)), name='x')) PYEOF check_incorrect_data() { @@ -136,9 +130,3 @@ check_incorrect_data binary_json_huge_length \ check_incorrect_data largebinary_json_huge_length \ $CLICKHOUSE_LOCAL --query "SELECT * FROM file('${TMP_DIR}/largebinary_json_huge_length.arrow', Arrow) FORMAT Null SETTINGS allow_experimental_json_type=1" - -# The 8 MB memory limit is below the 16 MB UUID-column reserve but above the 1 MB input, so an -# implementation that reserves before checking byte_width fails with MEMORY_LIMIT_EXCEEDED; the -# fixed reader rejects the type with INCORRECT_DATA before reserving. -check_incorrect_data fixedbinary1_as_uuid \ - $CLICKHOUSE_LOCAL --query "SELECT * FROM file('${TMP_DIR}/fixedbinary1_as_uuid.arrow', Arrow, 'x UUID') FORMAT Null SETTINGS max_memory_usage=8000000" From 2131fd2da7ec984b1f1d848324497563623581ae Mon Sep 17 00:00:00 2001 From: robot-clickhouse Date: Mon, 15 Jun 2026 08:28:57 +0000 Subject: [PATCH 041/146] Backport #106473 to 26.3: Fix vector search with skip indexes and use_skip_indexes_on_data_read --- .../QueryPlan/ReadFromMergeTree.cpp | 14 +++- ..._vector_search_with_second_index.reference | 10 +++ .../02354_vector_search_with_second_index.sql | 75 +++++++++++++++++++ 3 files changed, 98 insertions(+), 1 deletion(-) create mode 100644 tests/queries/0_stateless/02354_vector_search_with_second_index.reference create mode 100644 tests/queries/0_stateless/02354_vector_search_with_second_index.sql diff --git a/src/Processors/QueryPlan/ReadFromMergeTree.cpp b/src/Processors/QueryPlan/ReadFromMergeTree.cpp index ce73387cf692..6a89dd2645af 100644 --- a/src/Processors/QueryPlan/ReadFromMergeTree.cpp +++ b/src/Processors/QueryPlan/ReadFromMergeTree.cpp @@ -2626,7 +2626,9 @@ ReadFromMergeTree::AnalysisResultPtr ReadFromMergeTree::selectRangesToRead( } std::optional condition_hash; - if (reader_settings.use_query_condition_cache && query_info_.filter_actions_dag && !query_info_.isFinal()) + /// Vector search filters through the ORDER BY, so excluded ranges are not described by the WHERE DAG hash alone. + if (reader_settings.use_query_condition_cache && query_info_.filter_actions_dag && !query_info_.isFinal() + && !vector_search_parameters.has_value()) { const auto & outputs = query_info_.filter_actions_dag->getOutputs(); if (outputs.size() == 1 && VirtualColumnUtils::isDeterministic(outputs.front())) @@ -3247,6 +3249,16 @@ bool ReadFromMergeTree::supportsSkipIndexesOnDataRead() const if (!indexes || !indexes->use_skip_indexes || indexes->skip_indexes.empty()) return false; + /// When a vector similarity index is present, disable the use_skip_indexes_on_data_read path entirely and apply + /// all skip indexes during index analysis instead - the vector index runs first (it is the most selective) and the + /// remaining skip indexes run after it. + const bool has_vector_similarity_index = std::ranges::any_of(indexes->skip_indexes.useful_indices, [](const auto & idx) + { + return idx.index->isVectorSimilarityIndex(); + }); + if (has_vector_similarity_index) + return false; + const auto & settings = context->getSettingsRef(); if (!settings[Setting::use_skip_indexes_on_data_read]) return false; diff --git a/tests/queries/0_stateless/02354_vector_search_with_second_index.reference b/tests/queries/0_stateless/02354_vector_search_with_second_index.reference new file mode 100644 index 000000000000..c9ba8f17150b --- /dev/null +++ b/tests/queries/0_stateless/02354_vector_search_with_second_index.reference @@ -0,0 +1,10 @@ +-- Additional WHERE clause on skip index column. Expect vector index usage + skip index usage +Description: vector_similarity GRANULARITY 10000 +Description: minmax GRANULARITY 1 +-- Run the query with use_skip_indexes_on_data_read=0/1 to verify +1 +2 +1 +2 +-- Make sure that query condition cache was not updated by the vector search query +12 diff --git a/tests/queries/0_stateless/02354_vector_search_with_second_index.sql b/tests/queries/0_stateless/02354_vector_search_with_second_index.sql new file mode 100644 index 000000000000..2e433576a285 --- /dev/null +++ b/tests/queries/0_stateless/02354_vector_search_with_second_index.sql @@ -0,0 +1,75 @@ +-- Tags: no-fasttest, no-ordinary-database + +-- Tests 2nd skip index and use_skip_indexes_on_data_read=1/0 + +SET enable_analyzer = 1; +SET parallel_replicas_local_plan = 1; -- this setting is randomized, set it explicitly to have local plan for parallel replicas + +DROP TABLE IF EXISTS tab; + +CREATE TABLE tab +( + id Int32, + date Date, + attr1 Int32, + attr2 Int32, + vec Array(Float32), + INDEX idx_attr1 attr1 TYPE minmax, + INDEX idx_vec vec TYPE vector_similarity('hnsw', 'L2Distance', 2) GRANULARITY 10000 +) +ENGINE = MergeTree +PARTITION BY date +ORDER BY id +SETTINGS index_granularity = 3; + +INSERT INTO tab VALUES + (1, '2025-01-01', 101, 1001, [1.0, 0.0]), + (2, '2025-01-01', 102, 1002, [1.1, 0.0]), + (3, '2025-01-01', 103, 1003, [1.2, 0.0]), + (4, '2025-01-02', 104, 1003, [1.3, 0.0]), + (5, '2025-01-02', 105, 1004, [1.4, 0.0]), + (6, '2025-01-02', 106, 1005, [1.5, 0.0]), + (7, '2025-01-03', 107, 1005, [1.6, 0.0]), + (8, '2025-01-03', 108, 1006, [1.7, 0.0]), + (9, '2025-01-03', 109, 1007, [1.8, 0.0]), + (10, '2025-01-03', 110, 1008, [1.9, 0.0]), + (11, '2025-01-03', 111, 1009, [2.0, 0.0]), + (12, '2025-01-03', 112, 1010, [2.1, 0.0]); + +SELECT '-- Additional WHERE clause on skip index column. Expect vector index usage + skip index usage'; +SELECT trimLeft(explain) FROM ( + EXPLAIN indexes = 1 + SELECT id + FROM tab + WHERE attr1 > 100 + ORDER BY L2Distance(vec, [1.0, 1.0]) + LIMIT 2 + SETTINGS vector_search_filter_strategy = 'postfilter' +) +WHERE explain LIKE '%vector_similarity%' OR explain LIKE '%minmax%'; + +SET use_query_condition_cache = 1; + +SELECT '-- Run the query with use_skip_indexes_on_data_read=0/1 to verify'; + +SELECT id +FROM tab +WHERE attr1 > 100 +ORDER BY L2Distance(vec, [1.0, 1.0]) +LIMIT 2 +SETTINGS vector_search_filter_strategy = 'postfilter', use_skip_indexes_on_data_read = 1; + +SELECT id +FROM tab +WHERE attr1 > 100 +ORDER BY L2Distance(vec, [1.0, 1.0]) +LIMIT 2 +SETTINGS vector_search_filter_strategy = 'postfilter', use_skip_indexes_on_data_read = 0; + +SELECT '-- Make sure that query condition cache was not updated by the vector search query'; + +SELECT count(*) +FROM tab +WHERE attr1 > 100; + +DROP TABLE tab; From 7f8a05840939678c233587f925cb91288e4d2965 Mon Sep 17 00:00:00 2001 From: robot-clickhouse Date: Mon, 15 Jun 2026 17:03:48 +0000 Subject: [PATCH 042/146] Backport #107246 to 26.3: Fix `UPDATE` with parallel replicas in `MergeTree` --- src/Interpreters/MutationsInterpreter.cpp | 5 ++++ .../04207_lwu_parallel_replicas.reference | 1 + .../04207_lwu_parallel_replicas.sql | 27 +++++++++++++++++++ 3 files changed, 33 insertions(+) create mode 100644 tests/queries/0_stateless/04207_lwu_parallel_replicas.reference create mode 100644 tests/queries/0_stateless/04207_lwu_parallel_replicas.sql diff --git a/src/Interpreters/MutationsInterpreter.cpp b/src/Interpreters/MutationsInterpreter.cpp index 408fa88736b9..7b4fe49f768d 100644 --- a/src/Interpreters/MutationsInterpreter.cpp +++ b/src/Interpreters/MutationsInterpreter.cpp @@ -472,6 +472,11 @@ MutationsInterpreter::MutationsInterpreter( new_context->setSetting("allow_experimental_analyzer", false); LOG_TEST(logger, "Will use old analyzer to prepare mutation"); } + + /// Mutation source reads build a synthetic `SELECT` without a table expression, + /// so parallel replicas must not be used for them. + new_context->setSetting("enable_parallel_replicas", Field(0)); + context = std::move(new_context); } diff --git a/tests/queries/0_stateless/04207_lwu_parallel_replicas.reference b/tests/queries/0_stateless/04207_lwu_parallel_replicas.reference new file mode 100644 index 000000000000..0cfbf08886fc --- /dev/null +++ b/tests/queries/0_stateless/04207_lwu_parallel_replicas.reference @@ -0,0 +1 @@ +2 diff --git a/tests/queries/0_stateless/04207_lwu_parallel_replicas.sql b/tests/queries/0_stateless/04207_lwu_parallel_replicas.sql new file mode 100644 index 000000000000..423950cd4763 --- /dev/null +++ b/tests/queries/0_stateless/04207_lwu_parallel_replicas.sql @@ -0,0 +1,27 @@ +DROP TABLE IF EXISTS t_lwu_parallel_replicas; + +CREATE TABLE t_lwu_parallel_replicas +( + c0 Int32 +) +ENGINE = MergeTree +ORDER BY tuple() +SETTINGS enable_block_number_column = 1, enable_block_offset_column = 1; + +INSERT INTO t_lwu_parallel_replicas VALUES (1); + +-- `enable_analyzer = 0` forces the legacy parallel-replica path, which is the one that crashed. +-- `automatic_parallel_replicas_mode = 0` is required so `canUseTaskBasedParallelReplicas` enters that +-- path; `clickhouse-test` otherwise randomizes it and could bypass the legacy code being tested. +UPDATE t_lwu_parallel_replicas SET c0 = 2 WHERE 1 +SETTINGS enable_lightweight_update = 1, + enable_analyzer = 0, + enable_parallel_replicas = 1, + parallel_replicas_only_with_analyzer = 0, + parallel_replicas_for_non_replicated_merge_tree = 1, + automatic_parallel_replicas_mode = 0, + cluster_for_parallel_replicas = 'test_cluster_one_shard_three_replicas_localhost'; + +SELECT c0 FROM t_lwu_parallel_replicas; + +DROP TABLE t_lwu_parallel_replicas; From e177a4a8afb784b23a48af02cbdc37360e9a45b5 Mon Sep 17 00:00:00 2001 From: robot-clickhouse Date: Mon, 15 Jun 2026 17:09:11 +0000 Subject: [PATCH 043/146] Backport #99999 to 26.3: Fix legacy `JoinStep` filter pushdown for equivalent `USING` columns --- src/Interpreters/ActionsDAG.cpp | 111 +++++++++++++++--- ..._pushdown_equivalent_using_types.reference | 1 + ...filter_pushdown_equivalent_using_types.sql | 28 +++++ 3 files changed, 122 insertions(+), 18 deletions(-) create mode 100644 tests/queries/0_stateless/04047_join_filter_pushdown_equivalent_using_types.reference create mode 100644 tests/queries/0_stateless/04047_join_filter_pushdown_equivalent_using_types.sql diff --git a/src/Interpreters/ActionsDAG.cpp b/src/Interpreters/ActionsDAG.cpp index 1d36d0762880..b9f40885979a 100644 --- a/src/Interpreters/ActionsDAG.cpp +++ b/src/Interpreters/ActionsDAG.cpp @@ -58,6 +58,12 @@ namespace ErrorCodes namespace { +template +std::optional buildFilterActionsDAGImpl( + const ActionsDAG::NodeRawConstPtrs & filter_nodes, + ReplacementLookup && replacement_lookup, + bool single_output_condition_node); + std::pair getFunctionArguments(const ActionsDAG::NodeRawConstPtrs & children) { size_t num_arguments = children.size(); @@ -2974,7 +2980,58 @@ ActionsDAG::ActionsForJOINFilterPushDown ActionsDAG::splitActionsForJOINFilterPu const Block & stream_header, const std::unordered_map & columns_to_replace) { - auto updated_filter = *ActionsDAG::buildFilterActionsDAG({filter.getOutputs()[filter_pos]}, columns_to_replace); + std::unordered_map input_nodes_to_replace; + for (const auto & node : filter.getNodes()) + { + auto it = columns_to_replace.find(node.result_name); + if (it == columns_to_replace.end()) + continue; + + std::unordered_set visited_nodes; + std::unordered_set seen_input_nodes; + std::vector input_nodes; + std::stack stack; + stack.push(&node); + visited_nodes.insert(&node); + + while (!stack.empty()) + { + const auto * current = stack.top(); + stack.pop(); + + if (current->type == ActionsDAG::ActionType::INPUT) + { + if (seen_input_nodes.insert(current).second) + input_nodes.push_back(current); + continue; + } + + for (const auto * child : current->children) + { + if (visited_nodes.insert(child).second) + stack.push(child); + } + } + + /// `Cast JOIN USING columns` preserves the original result name on derived nodes. + /// Replacing such nodes directly would drop the cast. Rebind the unique source input + /// of the equivalent expression instead, so the conversion chain is preserved. + if (input_nodes.size() != 1) + continue; + + input_nodes_to_replace.insert_or_assign(input_nodes.front(), it->second); + } + + auto updated_filter = *buildFilterActionsDAGImpl( + {filter.getOutputs()[filter_pos]}, + [&](const ActionsDAG::Node * node) -> const ColumnWithTypeAndName * + { + auto it = input_nodes_to_replace.find(node); + if (it == input_nodes_to_replace.end()) + return nullptr; + return &it->second; + }, + true /* single_output_condition_node */); chassert(updated_filter.getOutputs().size() == 1); /** If result filter to left or right stream has column that is one of the stream inputs, we need distinguish filter column from @@ -3271,9 +3328,13 @@ bool ActionsDAG::isSortingPreserved( return true; } -std::optional ActionsDAG::buildFilterActionsDAG( - const NodeRawConstPtrs & filter_nodes, - const std::unordered_map & node_name_to_input_node_column, +namespace +{ + +template +std::optional buildFilterActionsDAGImpl( + const ActionsDAG::NodeRawConstPtrs & filter_nodes, + ReplacementLookup && replacement_lookup, bool single_output_condition_node) { if (filter_nodes.empty()) @@ -3311,12 +3372,11 @@ std::optional ActionsDAG::buildFilterActionsDAG( const ActionsDAG::Node * result_node = nullptr; - auto input_node_it = node_name_to_input_node_column.find(node->result_name); - if (input_node_it != node_name_to_input_node_column.end()) + if (const auto * replacement = replacement_lookup(node)) { - auto & result_input = result_inputs[input_node_it->second.name]; + auto & result_input = result_inputs[replacement->name]; if (!result_input) - result_input = &result_dag.addInput(input_node_it->second); + result_input = &result_dag.addInput(*replacement); node_to_result_node.emplace(node, result_input); nodes_to_process.pop_back(); @@ -3366,7 +3426,7 @@ std::optional ActionsDAG::buildFilterActionsDAG( } case ActionsDAG::ActionType::FUNCTION: { - NodeRawConstPtrs function_children; + ActionsDAG::NodeRawConstPtrs function_children; function_children.reserve(node->children.size()); FunctionOverloadResolverPtr function_overload_resolver; @@ -3383,8 +3443,8 @@ std::optional ActionsDAG::buildFilterActionsDAG( const auto & index_hint_args = index_hint->getActions().getOutputs(); if (!index_hint_args.empty()) - index_hint_filter_dag = *buildFilterActionsDAG(index_hint_args, - node_name_to_input_node_column, + index_hint_filter_dag = *buildFilterActionsDAGImpl(index_hint_args, + replacement_lookup, false /*single_output_condition_node*/); auto index_hint_function_clone = std::make_shared(); @@ -3402,16 +3462,13 @@ std::optional ActionsDAG::buildFilterActionsDAG( for (const auto & child : node->children) function_children.push_back(node_to_result_node.find(child)->second); - auto [arguments, all_const] = getFunctionArguments(function_children); - auto function_base = function_overload_resolver ? function_overload_resolver->build(arguments) : node->function_base; + auto function_arguments = getFunctionArguments(function_children); + auto function_base = function_overload_resolver ? function_overload_resolver->build(function_arguments.first) : node->function_base; - result_node = &result_dag.addFunctionImpl( + result_node = &result_dag.addFunction( function_base, std::move(function_children), - std::move(arguments), - result_name, - node->result_type, - all_const); + result_name); break; } case ActionsDAG::ActionType::PLACEHOLDER: @@ -3441,6 +3498,24 @@ std::optional ActionsDAG::buildFilterActionsDAG( return result_dag; } +} + +std::optional ActionsDAG::buildFilterActionsDAG( + const NodeRawConstPtrs & filter_nodes, + const std::unordered_map & node_name_to_input_node_column, + bool single_output_condition_node) +{ + auto replacement_lookup = [&](const ActionsDAG::Node * node) -> const ColumnWithTypeAndName * + { + auto it = node_name_to_input_node_column.find(node->result_name); + if (it == node_name_to_input_node_column.end()) + return nullptr; + return &it->second; + }; + + return buildFilterActionsDAGImpl(filter_nodes, replacement_lookup, single_output_condition_node); +} + ActionsDAG::NodeRawConstPtrs ActionsDAG::extractConjunctionAtoms(const Node * predicate) { NodeRawConstPtrs atoms; diff --git a/tests/queries/0_stateless/04047_join_filter_pushdown_equivalent_using_types.reference b/tests/queries/0_stateless/04047_join_filter_pushdown_equivalent_using_types.reference new file mode 100644 index 000000000000..d00491fd7e5b --- /dev/null +++ b/tests/queries/0_stateless/04047_join_filter_pushdown_equivalent_using_types.reference @@ -0,0 +1 @@ +1 diff --git a/tests/queries/0_stateless/04047_join_filter_pushdown_equivalent_using_types.sql b/tests/queries/0_stateless/04047_join_filter_pushdown_equivalent_using_types.sql new file mode 100644 index 000000000000..d3b391826e40 --- /dev/null +++ b/tests/queries/0_stateless/04047_join_filter_pushdown_equivalent_using_types.sql @@ -0,0 +1,28 @@ +-- Regression test for legacy `JoinStep` filter pushdown through equivalent `USING` columns. +-- Equivalent-column replacement must preserve `Cast JOIN USING columns` actions instead of +-- replacing the derived node by name and dropping the type-conversion chain. + +SET enable_analyzer = 1; +SET query_plan_use_new_logical_join_step = 0; +SET enable_join_runtime_filters = 0; +SET join_use_nulls = 1; + +DROP TABLE IF EXISTS t1; +DROP TABLE IF EXISTS t2; +DROP TABLE IF EXISTS t3; + +CREATE TABLE t1 (id UInt8, value String) ENGINE = Memory; +CREATE TABLE t2 (id UInt16, value String) ENGINE = Memory; +CREATE TABLE t3 (id Nullable(UInt64), value String) ENGINE = Memory; + +INSERT INTO t1 VALUES (0, 'a'), (1, 'b'); +INSERT INTO t2 VALUES (0, 'x'), (1, 'y'); +INSERT INTO t3 VALUES (1, 'q'), (NULL, 'r'); + +SELECT id +FROM t1 INNER JOIN t2 USING (id) LEFT JOIN t3 USING (id) +WHERE id = 1; + +DROP TABLE t1; +DROP TABLE t2; +DROP TABLE t3; From 15be9b0cdcde7c2acf5873de0f482623aa7b25e4 Mon Sep 17 00:00:00 2001 From: robot-clickhouse Date: Tue, 16 Jun 2026 08:30:49 +0000 Subject: [PATCH 044/146] Backport #100538 to 26.3: Fix EXECUTE AS ignoring FORMAT and INTO OUTFILE clauses --- src/Parsers/Access/ASTExecuteAsQuery.cpp | 4 +- src/Parsers/Access/ParserExecuteAsQuery.cpp | 40 +++++++++++++++++++ .../04056_execute_as_format.reference | 13 ++++++ .../0_stateless/04056_execute_as_format.sh | 40 +++++++++++++++++++ 4 files changed, 95 insertions(+), 2 deletions(-) create mode 100644 tests/queries/0_stateless/04056_execute_as_format.reference create mode 100755 tests/queries/0_stateless/04056_execute_as_format.sh diff --git a/src/Parsers/Access/ASTExecuteAsQuery.cpp b/src/Parsers/Access/ASTExecuteAsQuery.cpp index e60f40ec1af3..9c840cff6830 100644 --- a/src/Parsers/Access/ASTExecuteAsQuery.cpp +++ b/src/Parsers/Access/ASTExecuteAsQuery.cpp @@ -16,12 +16,12 @@ String ASTExecuteAsQuery::getID(char) const ASTPtr ASTExecuteAsQuery::clone() const { auto res = make_intrusive(*this); - + res->children.clear(); if (target_user) res->set(res->target_user, target_user->clone()); if (subquery) res->set(res->subquery, subquery->clone()); - + cloneOutputOptions(*res); return res; } diff --git a/src/Parsers/Access/ParserExecuteAsQuery.cpp b/src/Parsers/Access/ParserExecuteAsQuery.cpp index d23a6086428c..93fd8d37a306 100644 --- a/src/Parsers/Access/ParserExecuteAsQuery.cpp +++ b/src/Parsers/Access/ParserExecuteAsQuery.cpp @@ -3,6 +3,7 @@ #include #include #include +#include #include @@ -32,7 +33,46 @@ bool ParserExecuteAsQuery::parseImpl(Pos & pos, ASTPtr & node, Expected & expect ASTPtr subquery; if (subquery_parser.parse(pos, subquery, expected)) + { + if (auto * sub_output = dynamic_cast(subquery.get())) + { + /// Hoist output options (FORMAT, INTO OUTFILE, COMPRESSION, SETTINGS) from the subquery + /// to the outer EXECUTE AS query. This is needed because the inner `executeQuery` is called + /// with `QueryFlags{ .internal = true }` and returns raw blocks — the outer `executeQuery` + /// pipeline reads these options from the top-level AST to apply formatting. + if (sub_output->out_file) + { + query->out_file = sub_output->out_file; + query->children.push_back(query->out_file); + query->setIsOutfileAppend(sub_output->isOutfileAppend()); + query->setIsOutfileTruncate(sub_output->isOutfileTruncate()); + query->setIsIntoOutfileWithStdout(sub_output->isIntoOutfileWithStdout()); + } + if (sub_output->format_ast) + { + query->format_ast = sub_output->format_ast; + query->children.push_back(query->format_ast); + } + if (sub_output->compression) + { + query->compression = sub_output->compression; + query->children.push_back(query->compression); + } + if (sub_output->compression_level) + { + query->compression_level = sub_output->compression_level; + query->children.push_back(query->compression_level); + } + if (sub_output->settings_ast) + { + query->settings_ast = sub_output->settings_ast; + query->children.push_back(query->settings_ast); + } + ASTQueryWithOutput::resetOutputASTIfExist(*sub_output); + } + query->set(query->subquery, subquery); + } return true; } diff --git a/tests/queries/0_stateless/04056_execute_as_format.reference b/tests/queries/0_stateless/04056_execute_as_format.reference new file mode 100644 index 000000000000..1860716c2042 --- /dev/null +++ b/tests/queries/0_stateless/04056_execute_as_format.reference @@ -0,0 +1,13 @@ +--- FORMAT JSON --- +1 +--- FORMAT CSV --- +"hello",42 +--- FORMAT TabSeparatedWithNames --- +a b +1 2 +--- INTO OUTFILE with FORMAT CSV --- +"file",100 +--- SETTINGS clause --- +1 +--- INTO OUTFILE with COMPRESSION --- +"compressed",200 diff --git a/tests/queries/0_stateless/04056_execute_as_format.sh b/tests/queries/0_stateless/04056_execute_as_format.sh new file mode 100755 index 000000000000..a510ebfc976e --- /dev/null +++ b/tests/queries/0_stateless/04056_execute_as_format.sh @@ -0,0 +1,40 @@ +#!/usr/bin/env bash + +CUR_DIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd) +# shellcheck source=../shell_config.sh +. "$CUR_DIR"/../shell_config.sh + +SERVICE_USER="svcuser_${CLICKHOUSE_DATABASE}" +TARGET_USER="fmtuser_${CLICKHOUSE_DATABASE}" + +$CLICKHOUSE_CLIENT --query "DROP USER IF EXISTS ${SERVICE_USER}, ${TARGET_USER}" +$CLICKHOUSE_CLIENT --query "CREATE USER ${SERVICE_USER}, ${TARGET_USER}" +$CLICKHOUSE_CLIENT --query "GRANT ALL ON ${CLICKHOUSE_DATABASE}.* TO ${SERVICE_USER} WITH GRANT OPTION" +$CLICKHOUSE_CLIENT --query "GRANT IMPERSONATE ON ${TARGET_USER} TO ${SERVICE_USER}" +$CLICKHOUSE_CLIENT --query "GRANT SELECT ON ${CLICKHOUSE_DATABASE}.* TO ${TARGET_USER}" + +echo "--- FORMAT JSON ---" +$CLICKHOUSE_CLIENT --user ${SERVICE_USER} --query "EXECUTE AS ${TARGET_USER} SELECT 1 AS x FORMAT JSON" | grep -c '"meta"' + +echo "--- FORMAT CSV ---" +$CLICKHOUSE_CLIENT --user ${SERVICE_USER} --query "EXECUTE AS ${TARGET_USER} SELECT 'hello' AS s, 42 AS n FORMAT CSV" + +echo "--- FORMAT TabSeparatedWithNames ---" +$CLICKHOUSE_CLIENT --user ${SERVICE_USER} --query "EXECUTE AS ${TARGET_USER} SELECT 1 AS a, 2 AS b FORMAT TabSeparatedWithNames" + +echo "--- INTO OUTFILE with FORMAT CSV ---" +OUTFILE="${CLICKHOUSE_TMP}/execute_as_outfile_${CLICKHOUSE_DATABASE}.csv" +$CLICKHOUSE_CLIENT --user ${SERVICE_USER} --query "EXECUTE AS ${TARGET_USER} SELECT 'file' AS c1, 100 AS c2 INTO OUTFILE '${OUTFILE}' FORMAT CSV" +cat "${OUTFILE}" +rm -f "${OUTFILE}" + +echo "--- SETTINGS clause ---" +$CLICKHOUSE_CLIENT --user ${SERVICE_USER} --query "EXECUTE AS ${TARGET_USER} SELECT 1 AS x FORMAT TabSeparated SETTINGS max_result_rows=1" + +echo "--- INTO OUTFILE with COMPRESSION ---" +OUTFILE_GZ="${CLICKHOUSE_TMP}/execute_as_outfile_${CLICKHOUSE_DATABASE}.csv.gz" +$CLICKHOUSE_CLIENT --user ${SERVICE_USER} --query "EXECUTE AS ${TARGET_USER} SELECT 'compressed' AS c1, 200 AS c2 INTO OUTFILE '${OUTFILE_GZ}' COMPRESSION 'gzip' FORMAT CSV" +zcat "${OUTFILE_GZ}" +rm -f "${OUTFILE_GZ}" + +$CLICKHOUSE_CLIENT --query "DROP USER ${SERVICE_USER}, ${TARGET_USER}" From ef725ce9abc55241d1929235b7b9f73f2b3f5514 Mon Sep 17 00:00:00 2001 From: Pavel Kruglov <48961922+Avogar@users.noreply.github.com> Date: Tue, 16 Jun 2026 15:17:57 +0200 Subject: [PATCH 045/146] Update SettingsChangesHistory.cpp --- src/Core/SettingsChangesHistory.cpp | 44 ++--------------------------- 1 file changed, 2 insertions(+), 42 deletions(-) diff --git a/src/Core/SettingsChangesHistory.cpp b/src/Core/SettingsChangesHistory.cpp index 4b2a59863880..b5bfff4d6930 100644 --- a/src/Core/SettingsChangesHistory.cpp +++ b/src/Core/SettingsChangesHistory.cpp @@ -39,50 +39,10 @@ const VersionToSettingsChangesMap & getSettingsChangesHistory() /// controls new feature and it's 'true' by default, use 'false' as previous_value). /// It's used to implement `compatibility` setting (see https://github.com/ClickHouse/ClickHouse/issues/35972) /// Note: please check if the key already exists to prevent duplicate entries. - addSettingsChanges(settings_changes_history, "26.4", - { - {"allow_iceberg_remove_orphan_files", false, false, "New setting to gate Iceberg orphan file removal"}, - {"iceberg_orphan_files_older_than_seconds", 259200, 259200, "New setting for default orphan file age threshold"}, - {"output_format_arrow_unsupported_types_as_binary", false, true, "New setting to convert unsupported CH types to arrow binary instead of UNKNOWN_TYPE exception."}, - {"output_format_parquet_unsupported_types_as_binary", false, false, "New setting to convert unsupported CH types to parquet (arrow) binary instead of UNKNOWN_TYPE exception."}, - {"asterisk_include_virtual_columns", false, false, "New setting"}, - {"max_wkb_geometry_elements", 1'000'000, 1'000'000, "New setting to limit element counts in WKB geometry parsing, preventing excessive memory allocation on malformed data."}, - {"max_rand_distribution_trials", 1'000'000'000, 1'000'000'000, "New setting to limit trial counts in random distribution functions, preventing hangs with extreme inputs."}, - {"max_rand_distribution_parameter", 1e6, 1e6, "New setting to limit shape parameters in random distribution functions, preventing hangs with extreme inputs."}, - {"optimize_truncate_order_by_after_group_by_keys", false, true, "Remove trailing ORDER BY elements once all GROUP BY keys are covered in the ORDER BY prefix."}, - {"use_statistics_for_part_pruning", false, true, "New setting to use statistics for part pruning during query execution."}, - {"distributed_index_analysis_only_on_coordinator", false, false, "New setting."}, - {"query_plan_optimize_join_order_randomize", 0, 0, "New setting to randomize join order statistics for testing."}, - {"enable_materialized_cte", false, false, "New setting"}, - {"use_strict_insert_block_limits", false, false, "New setting to use strict min and max insert bounds on inserts. When min < max, max limits take precedence."}, - {"finalize_projection_parts_synchronously", false, false, "New setting to finalize projection parts synchronously during INSERT to reduce peak memory usage."}, - {"parallel_replicas_allow_view_over_mergetree", false, false, "New setting"}, - {"read_in_order_use_virtual_row_per_block", false, false, "Emit virtual row after each block during read-in-order to allow more frequent source reprioritization in MergingSortedTransform."}, - {"distributed_plan_prefer_replicas_over_workers", false, false, "New setting to serialize distributed plan for replicas"}, - {"use_text_index_like_evaluation_by_dictionary_scan", true, true, "New setting"}, - {"text_index_like_min_pattern_length", 4, 4, "New setting"}, - {"text_index_like_max_postings_to_read", 50, 50, "New setting"}, - {"analyzer_inline_views", false, false, "New setting"}, - {"highlight_max_matches_per_row", 10000, 10000, "New setting to limit the number of highlight matches per row to protect against excessive memory usage."}, - {"materialize_statistics_on_insert", true, false, "Disable building statistics on INSERT by default, rely on merges instead"}, - {"enable_join_transitive_predicates", false, false, "New setting to infer transitive equi-join predicates for join order optimization."}, - {"enable_join_fixed_hash_table_conversion", false, true, "New setting to enable converting the hash table to a flat array for joins when the key is a single integer with a small value range."}, - {"allow_experimental_ai_functions", false, false, "New setting"}, - {"ai_function_request_timeout_sec", 60, 60, "New setting"}, - {"ai_function_max_retries", 0, 0, "New setting"}, - {"ai_function_retry_initial_delay_ms", 1000, 1000, "New setting"}, - {"ai_function_throw_on_error", true, true, "New setting"}, - {"ai_function_max_input_tokens_per_query", 1000000, 1000000, "New setting"}, - {"ai_function_max_output_tokens_per_query", 500000, 500000, "New setting"}, - {"ai_function_max_api_calls_per_query", 0, 0, "New setting"}, - {"ai_function_throw_on_quota_exceeded", true, true, "New setting"}, - {"materialize_statistics_on_insert", true, false, "Disable building statistics on INSERT by default, rely on merges instead"}, - {"enable_join_transitive_predicates", false, false, "New setting to infer transitive equi-join predicates for join order optimization."}, - {"variant_throw_on_type_mismatch", true, true, "New setting to control type mismatch behavior in default Variant implementation"}, - {"dynamic_throw_on_type_mismatch", true, true, "New setting to control type mismatch behavior in default Dynamic implementation"}, - }); addSettingsChanges(settings_changes_history, "26.3", { + {"variant_throw_on_type_mismatch", true, true, "New setting to control type mismatch behavior in default Variant implementation"}, + {"dynamic_throw_on_type_mismatch", true, true, "New setting to control type mismatch behavior in default Dynamic implementation"}, {"defer_partition_pruning_after_final", false, true, "Gates the FINAL planner's unconditional skipping of partition pruning when the partition-key column is not in the sorting key. The behavior change itself shipped silently in 26.3 via https://github.com/ClickHouse/ClickHouse/pull/98242; this entry retroactively documents it so `compatibility = '26.2'` restores the pre-regression behavior (0 = prune before FINAL, fast; 1 = defer pruning, correctness-safe)."}, {"http_max_fields", 1000000, 1000, "Reduce default to limit pre-authentication memory usage by HTTP connections."}, {"http_max_field_name_size", 131072, 4096, "Reduce default to limit pre-authentication memory usage by HTTP connections."}, From e6b9558ae32a360c4516e080fa9395d751ba117d Mon Sep 17 00:00:00 2001 From: robot-clickhouse Date: Tue, 16 Jun 2026 14:53:36 +0000 Subject: [PATCH 046/146] Backport #106957 to 26.3: Avoid ColumnNode hash calculation if not needed --- src/Analyzer/IQueryTreeNode.h | 10 + src/Analyzer/Resolve/QueryAnalyzer.cpp | 16 +- src/Planner/PlannerActionsVisitor.cpp | 2 +- .../performance/wide_table_nested_select.xml | 519 ++++++++++++++++++ 4 files changed, 540 insertions(+), 7 deletions(-) create mode 100644 tests/performance/wide_table_nested_select.xml diff --git a/src/Analyzer/IQueryTreeNode.h b/src/Analyzer/IQueryTreeNode.h index d5d342527def..7cc66d99e71c 100644 --- a/src/Analyzer/IQueryTreeNode.h +++ b/src/Analyzer/IQueryTreeNode.h @@ -135,6 +135,16 @@ class IQueryTreeNode : public TypePromotion * * Alias of query tree node is part of query tree hash. * Original AST is not part of query tree hash. + * + * The result is not cached: every call traverses the whole subtree, so the cost is + * proportional to the subtree size. Nodes referenced through weak pointers are hashed too: + * for example, hashing a `ColumnNode` also hashes its column source (a `TableNode` or + * `QueryNode` with all its columns). Because of that, computing the hash per node — e.g. + * looking up each projection node in a container keyed by tree hash, such as + * `QueryTreeNodePtrWithHashSet` — can make query analysis quadratic in the number of + * columns for queries over wide tables. When such a container is usually empty, skip + * the lookup explicitly for the empty case (see `QueryAnalyzer::resolveExpressionNode` + * and `PlannerActionsVisitorImpl::visitColumn`). */ Hash getTreeHash(CompareOptions compare_options = { .compare_aliases = true, .compare_types = true, .ignore_cte = false }) const; diff --git a/src/Analyzer/Resolve/QueryAnalyzer.cpp b/src/Analyzer/Resolve/QueryAnalyzer.cpp index f55197b602ef..3d3d8058c746 100644 --- a/src/Analyzer/Resolve/QueryAnalyzer.cpp +++ b/src/Analyzer/Resolve/QueryAnalyzer.cpp @@ -2162,7 +2162,7 @@ ProjectionNames QueryAnalyzer::resolveMatcher(QueryTreeNodePtr & matcher_node, I } } - if (!scope.expressions_in_resolve_process_stack.hasAggregateFunction()) + if (!scope.nullable_group_by_keys.empty() && !scope.expressions_in_resolve_process_stack.hasAggregateFunction()) { for (auto & [node, _] : matched_expression_nodes_with_names) { @@ -2923,6 +2923,7 @@ ProjectionNames QueryAnalyzer::resolveExpressionNode( ? mat_subquery->as()->getProjectionColumns() : mat_subquery->as()->computeProjectionColumns(); + NamesAndTypesList columns; for (const auto & col : proj_cols) columns.emplace_back(col.name, col.type); @@ -3139,12 +3140,15 @@ ProjectionNames QueryAnalyzer::resolveExpressionNode( { for (const auto * scope_ptr = &scope; scope_ptr; scope_ptr = scope_ptr->parent_scope) { - auto it = scope_ptr->nullable_group_by_keys.find(node); - if (it != scope_ptr->nullable_group_by_keys.end()) + if (!scope_ptr->nullable_group_by_keys.empty()) { - node = it->node->clone(); - node->convertToNullable(); - break; + auto it = scope_ptr->nullable_group_by_keys.find(node); + if (it != scope_ptr->nullable_group_by_keys.end()) + { + node = it->node->clone(); + node->convertToNullable(); + break; + } } /// Check parent scopes until find current query scope. diff --git a/src/Planner/PlannerActionsVisitor.cpp b/src/Planner/PlannerActionsVisitor.cpp index a2753cdc453f..04431bd2554c 100644 --- a/src/Planner/PlannerActionsVisitor.cpp +++ b/src/Planner/PlannerActionsVisitor.cpp @@ -776,7 +776,7 @@ PlannerActionsVisitorImpl::NodeNameAndNodeMinLevel PlannerActionsVisitorImpl::vi const auto & column_node = node->as(); const auto & column_node_ptr = static_pointer_cast(node); - if (correlated_columns_set.contains(column_node_ptr)) + if (!correlated_columns_set.empty() && correlated_columns_set.contains(column_node_ptr)) return visitCorrelatedColumn(column_node_ptr); auto column_node_name = action_node_name_helper.calculateActionNodeName(node); diff --git a/tests/performance/wide_table_nested_select.xml b/tests/performance/wide_table_nested_select.xml new file mode 100644 index 000000000000..1fcaff61a86a --- /dev/null +++ b/tests/performance/wide_table_nested_select.xml @@ -0,0 +1,519 @@ + + + CREATE TABLE wide_table_nested_select + ( + c0 UInt64, + c1 UInt64, + c2 UInt64, + c3 UInt64, + c4 UInt64, + c5 UInt64, + c6 UInt64, + c7 UInt64, + c8 UInt64, + c9 UInt64, + c10 UInt64, + c11 UInt64, + c12 UInt64, + c13 UInt64, + c14 UInt64, + c15 UInt64, + c16 UInt64, + c17 UInt64, + c18 UInt64, + c19 UInt64, + c20 UInt64, + c21 UInt64, + c22 UInt64, + c23 UInt64, + c24 UInt64, + c25 UInt64, + c26 UInt64, + c27 UInt64, + c28 UInt64, + c29 UInt64, + c30 UInt64, + c31 UInt64, + c32 UInt64, + c33 UInt64, + c34 UInt64, + c35 UInt64, + c36 UInt64, + c37 UInt64, + c38 UInt64, + c39 UInt64, + c40 UInt64, + c41 UInt64, + c42 UInt64, + c43 UInt64, + c44 UInt64, + c45 UInt64, + c46 UInt64, + c47 UInt64, + c48 UInt64, + c49 UInt64, + c50 UInt64, + c51 UInt64, + c52 UInt64, + c53 UInt64, + c54 UInt64, + c55 UInt64, + c56 UInt64, + c57 UInt64, + c58 UInt64, + c59 UInt64, + c60 UInt64, + c61 UInt64, + c62 UInt64, + c63 UInt64, + c64 UInt64, + c65 UInt64, + c66 UInt64, + c67 UInt64, + c68 UInt64, + c69 UInt64, + c70 UInt64, + c71 UInt64, + c72 UInt64, + c73 UInt64, + c74 UInt64, + c75 UInt64, + c76 UInt64, + c77 UInt64, + c78 UInt64, + c79 UInt64, + c80 UInt64, + c81 UInt64, + c82 UInt64, + c83 UInt64, + c84 UInt64, + c85 UInt64, + c86 UInt64, + c87 UInt64, + c88 UInt64, + c89 UInt64, + c90 UInt64, + c91 UInt64, + c92 UInt64, + c93 UInt64, + c94 UInt64, + c95 UInt64, + c96 UInt64, + c97 UInt64, + c98 UInt64, + c99 UInt64, + c100 UInt64, + c101 UInt64, + c102 UInt64, + c103 UInt64, + c104 UInt64, + c105 UInt64, + c106 UInt64, + c107 UInt64, + c108 UInt64, + c109 UInt64, + c110 UInt64, + c111 UInt64, + c112 UInt64, + c113 UInt64, + c114 UInt64, + c115 UInt64, + c116 UInt64, + c117 UInt64, + c118 UInt64, + c119 UInt64, + c120 UInt64, + c121 UInt64, + c122 UInt64, + c123 UInt64, + c124 UInt64, + c125 UInt64, + c126 UInt64, + c127 UInt64, + c128 UInt64, + c129 UInt64, + c130 UInt64, + c131 UInt64, + c132 UInt64, + c133 UInt64, + c134 UInt64, + c135 UInt64, + c136 UInt64, + c137 UInt64, + c138 UInt64, + c139 UInt64, + c140 UInt64, + c141 UInt64, + c142 UInt64, + c143 UInt64, + c144 UInt64, + c145 UInt64, + c146 UInt64, + c147 UInt64, + c148 UInt64, + c149 UInt64, + c150 UInt64, + c151 UInt64, + c152 UInt64, + c153 UInt64, + c154 UInt64, + c155 UInt64, + c156 UInt64, + c157 UInt64, + c158 UInt64, + c159 UInt64, + c160 UInt64, + c161 UInt64, + c162 UInt64, + c163 UInt64, + c164 UInt64, + c165 UInt64, + c166 UInt64, + c167 UInt64, + c168 UInt64, + c169 UInt64, + c170 UInt64, + c171 UInt64, + c172 UInt64, + c173 UInt64, + c174 UInt64, + c175 UInt64, + c176 UInt64, + c177 UInt64, + c178 UInt64, + c179 UInt64, + c180 UInt64, + c181 UInt64, + c182 UInt64, + c183 UInt64, + c184 UInt64, + c185 UInt64, + c186 UInt64, + c187 UInt64, + c188 UInt64, + c189 UInt64, + c190 UInt64, + c191 UInt64, + c192 UInt64, + c193 UInt64, + c194 UInt64, + c195 UInt64, + c196 UInt64, + c197 UInt64, + c198 UInt64, + c199 UInt64, + c200 UInt64, + c201 UInt64, + c202 UInt64, + c203 UInt64, + c204 UInt64, + c205 UInt64, + c206 UInt64, + c207 UInt64, + c208 UInt64, + c209 UInt64, + c210 UInt64, + c211 UInt64, + c212 UInt64, + c213 UInt64, + c214 UInt64, + c215 UInt64, + c216 UInt64, + c217 UInt64, + c218 UInt64, + c219 UInt64, + c220 UInt64, + c221 UInt64, + c222 UInt64, + c223 UInt64, + c224 UInt64, + c225 UInt64, + c226 UInt64, + c227 UInt64, + c228 UInt64, + c229 UInt64, + c230 UInt64, + c231 UInt64, + c232 UInt64, + c233 UInt64, + c234 UInt64, + c235 UInt64, + c236 UInt64, + c237 UInt64, + c238 UInt64, + c239 UInt64, + c240 UInt64, + c241 UInt64, + c242 UInt64, + c243 UInt64, + c244 UInt64, + c245 UInt64, + c246 UInt64, + c247 UInt64, + c248 UInt64, + c249 UInt64, + c250 UInt64, + c251 UInt64, + c252 UInt64, + c253 UInt64, + c254 UInt64, + c255 UInt64, + c256 UInt64, + c257 UInt64, + c258 UInt64, + c259 UInt64, + c260 UInt64, + c261 UInt64, + c262 UInt64, + c263 UInt64, + c264 UInt64, + c265 UInt64, + c266 UInt64, + c267 UInt64, + c268 UInt64, + c269 UInt64, + c270 UInt64, + c271 UInt64, + c272 UInt64, + c273 UInt64, + c274 UInt64, + c275 UInt64, + c276 UInt64, + c277 UInt64, + c278 UInt64, + c279 UInt64, + c280 UInt64, + c281 UInt64, + c282 UInt64, + c283 UInt64, + c284 UInt64, + c285 UInt64, + c286 UInt64, + c287 UInt64, + c288 UInt64, + c289 UInt64, + c290 UInt64, + c291 UInt64, + c292 UInt64, + c293 UInt64, + c294 UInt64, + c295 UInt64, + c296 UInt64, + c297 UInt64, + c298 UInt64, + c299 UInt64, + c300 UInt64, + c301 UInt64, + c302 UInt64, + c303 UInt64, + c304 UInt64, + c305 UInt64, + c306 UInt64, + c307 UInt64, + c308 UInt64, + c309 UInt64, + c310 UInt64, + c311 UInt64, + c312 UInt64, + c313 UInt64, + c314 UInt64, + c315 UInt64, + c316 UInt64, + c317 UInt64, + c318 UInt64, + c319 UInt64, + c320 UInt64, + c321 UInt64, + c322 UInt64, + c323 UInt64, + c324 UInt64, + c325 UInt64, + c326 UInt64, + c327 UInt64, + c328 UInt64, + c329 UInt64, + c330 UInt64, + c331 UInt64, + c332 UInt64, + c333 UInt64, + c334 UInt64, + c335 UInt64, + c336 UInt64, + c337 UInt64, + c338 UInt64, + c339 UInt64, + c340 UInt64, + c341 UInt64, + c342 UInt64, + c343 UInt64, + c344 UInt64, + c345 UInt64, + c346 UInt64, + c347 UInt64, + c348 UInt64, + c349 UInt64, + c350 UInt64, + c351 UInt64, + c352 UInt64, + c353 UInt64, + c354 UInt64, + c355 UInt64, + c356 UInt64, + c357 UInt64, + c358 UInt64, + c359 UInt64, + c360 UInt64, + c361 UInt64, + c362 UInt64, + c363 UInt64, + c364 UInt64, + c365 UInt64, + c366 UInt64, + c367 UInt64, + c368 UInt64, + c369 UInt64, + c370 UInt64, + c371 UInt64, + c372 UInt64, + c373 UInt64, + c374 UInt64, + c375 UInt64, + c376 UInt64, + c377 UInt64, + c378 UInt64, + c379 UInt64, + c380 UInt64, + c381 UInt64, + c382 UInt64, + c383 UInt64, + c384 UInt64, + c385 UInt64, + c386 UInt64, + c387 UInt64, + c388 UInt64, + c389 UInt64, + c390 UInt64, + c391 UInt64, + c392 UInt64, + c393 UInt64, + c394 UInt64, + c395 UInt64, + c396 UInt64, + c397 UInt64, + c398 UInt64, + c399 UInt64, + c400 UInt64, + c401 UInt64, + c402 UInt64, + c403 UInt64, + c404 UInt64, + c405 UInt64, + c406 UInt64, + c407 UInt64, + c408 UInt64, + c409 UInt64, + c410 UInt64, + c411 UInt64, + c412 UInt64, + c413 UInt64, + c414 UInt64, + c415 UInt64, + c416 UInt64, + c417 UInt64, + c418 UInt64, + c419 UInt64, + c420 UInt64, + c421 UInt64, + c422 UInt64, + c423 UInt64, + c424 UInt64, + c425 UInt64, + c426 UInt64, + c427 UInt64, + c428 UInt64, + c429 UInt64, + c430 UInt64, + c431 UInt64, + c432 UInt64, + c433 UInt64, + c434 UInt64, + c435 UInt64, + c436 UInt64, + c437 UInt64, + c438 UInt64, + c439 UInt64, + c440 UInt64, + c441 UInt64, + c442 UInt64, + c443 UInt64, + c444 UInt64, + c445 UInt64, + c446 UInt64, + c447 UInt64, + c448 UInt64, + c449 UInt64, + c450 UInt64, + c451 UInt64, + c452 UInt64, + c453 UInt64, + c454 UInt64, + c455 UInt64, + c456 UInt64, + c457 UInt64, + c458 UInt64, + c459 UInt64, + c460 UInt64, + c461 UInt64, + c462 UInt64, + c463 UInt64, + c464 UInt64, + c465 UInt64, + c466 UInt64, + c467 UInt64, + c468 UInt64, + c469 UInt64, + c470 UInt64, + c471 UInt64, + c472 UInt64, + c473 UInt64, + c474 UInt64, + c475 UInt64, + c476 UInt64, + c477 UInt64, + c478 UInt64, + c479 UInt64, + c480 UInt64, + c481 UInt64, + c482 UInt64, + c483 UInt64, + c484 UInt64, + c485 UInt64, + c486 UInt64, + c487 UInt64, + c488 UInt64, + c489 UInt64, + c490 UInt64, + c491 UInt64, + c492 UInt64, + c493 UInt64, + c494 UInt64, + c495 UInt64, + c496 UInt64, + c497 UInt64, + c498 UInt64, + c499 UInt64 + ) + ENGINE = MergeTree + ORDER BY tuple() + + DROP TABLE IF EXISTS wide_table_nested_select + + + EXPLAIN SELECT * FROM (SELECT * FROM (SELECT * FROM (SELECT * FROM wide_table_nested_select))) FORMAT Null + EXPLAIN SELECT * FROM (SELECT * FROM (SELECT * FROM (SELECT * FROM (SELECT * FROM (SELECT * FROM (SELECT * FROM (SELECT * FROM (SELECT * FROM (SELECT * FROM (SELECT * FROM wide_table_nested_select)))))))))) FORMAT Null + SELECT * FROM (SELECT * FROM (SELECT * FROM (SELECT * FROM (SELECT * FROM (SELECT * FROM (SELECT * FROM (SELECT * FROM (SELECT * FROM (SELECT * FROM (SELECT * FROM wide_table_nested_select)))))))))) FORMAT Null + From e6675f6119d69e41bd2a44d2708a345f191280ce Mon Sep 17 00:00:00 2001 From: robot-clickhouse Date: Tue, 16 Jun 2026 14:55:54 +0000 Subject: [PATCH 047/146] Backport #106905 to 26.3: Fix crash in ProtobufRowInputFormat::syncAfterError on null reader --- .../Formats/Impl/ProtobufRowInputFormat.cpp | 35 +- .../tests/gtest_protobuf_sync_after_error.cpp | 348 ++++++++++++++++++ 2 files changed, 381 insertions(+), 2 deletions(-) create mode 100644 src/Processors/Formats/Impl/tests/gtest_protobuf_sync_after_error.cpp diff --git a/src/Processors/Formats/Impl/ProtobufRowInputFormat.cpp b/src/Processors/Formats/Impl/ProtobufRowInputFormat.cpp index bfc13d16c032..359474d5a5d1 100644 --- a/src/Processors/Formats/Impl/ProtobufRowInputFormat.cpp +++ b/src/Processors/Formats/Impl/ProtobufRowInputFormat.cpp @@ -73,6 +73,34 @@ try } catch (...) { + /// Unlike text formats that rely on syncAfterError to scan for a row delimiter, + /// Protobuf knows the message boundary from the length prefix. We can resync + /// here directly via endMessage(true), which skips any unread bytes in the + /// current message. This keeps the logic self-contained in readRow and leaves + /// syncAfterError as a no-op. + if (reader) + { + try + { + /// Skip remaining bytes in the current message to position ReadBuffer + /// at the next message boundary. Without this, if a parse error occurs + /// mid-message (e.g. invalid date string in field 1 while field 2 follows), + /// the ReadBuffer stays inside the bad message and the next readRow would + /// misinterpret the remaining bytes as a new message's length prefix. + reader->endMessage(true); + } + catch (...) + { + /// endMessage fails when the stream is truncated (message length prefix + /// claims more bytes than exist). The stream is unrecoverable — rethrow + /// so IRowInputFormat fails the query instead of silently skipping. + destroyReaderAndSerializer(); + throw; + } + } + /// Resync succeeded — destroy and rethrow the original parse error. + /// IRowInputFormat will skip this row and call readRow again, which + /// recreates reader and serializer at the correct stream position. destroyReaderAndSerializer(); throw; } @@ -86,12 +114,15 @@ void ProtobufRowInputFormat::setReadBuffer(ReadBuffer & in_) bool ProtobufRowInputFormat::allowSyncAfterError() const { - return true; + /// ProtobufSingle (with_length_delimiter = false) has one message per input — + /// there's no next row boundary to skip to after a field-level parse error. + return with_length_delimiter; } void ProtobufRowInputFormat::syncAfterError() { - reader->endMessage(true); + /// endMessage was already called in readRow's catch block before destroying + /// the reader. Nothing left to do here. } size_t ProtobufRowInputFormat::countRows(size_t max_block_size) diff --git a/src/Processors/Formats/Impl/tests/gtest_protobuf_sync_after_error.cpp b/src/Processors/Formats/Impl/tests/gtest_protobuf_sync_after_error.cpp new file mode 100644 index 000000000000..37ef03b6331f --- /dev/null +++ b/src/Processors/Formats/Impl/tests/gtest_protobuf_sync_after_error.cpp @@ -0,0 +1,348 @@ +#include "config.h" + +#if USE_PROTOBUF + +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace DB; + +namespace +{ + +struct ProtobufFormatHelper +{ + static std::shared_ptr createFormat( + ReadBuffer & read_buf, + const Block & header, + const String & proto_schema, + UInt64 allow_errors_num = 10, + bool with_length_delimiter = true) + { + RowInputFormatParams params; + params.max_block_size_rows = 100; + params.allow_errors_num = allow_errors_num; + + FormatSettings format_settings; + format_settings.schema.format_schema = proto_schema; + format_settings.schema.format_schema_message_name = "Row"; + format_settings.schema.format_schema_source = "string"; + format_settings.schema.format_schema_path = "/tmp"; + + ProtobufSchemaInfo schema_info(format_settings, "Protobuf", header, false); + + return std::make_shared( + read_buf, + std::make_shared(header), + params, + schema_info, + with_length_delimiter, + /* flatten_google_wrappers = */ false, + /* oneof_presence = */ false, + /* google_protos_path = */ ""); + } + + static Block uint32Header() + { + Block header; + header.insert({ColumnUInt32::create(), std::make_shared(), "number"}); + return header; + } + + static Block dateHeader() + { + Block header; + header.insert({ColumnUInt16::create(), std::make_shared(), "d"}); + return header; + } +}; + +} + +/// Truncated stream must not crash. Previously syncAfterError() dereferenced +/// a null reader after destroyReaderAndSerializer() in readRow's catch. +/// Now it throws an exception instead of crashing the server. +TEST(ProtobufRowInputFormat, TruncatedMessageDoesNotCrash) +{ + std::string truncated_data; + truncated_data += '\x06'; // message length = 6 + truncated_data += '\x0d'; // field 1, wire type BITS32: tag = (1 << 3) | 5 + truncated_data += '\x01'; // 1 byte instead of the 4 expected + + ReadBufferFromString read_buf(truncated_data); + auto header = ProtobufFormatHelper::uint32Header(); + auto format = ProtobufFormatHelper::createFormat( + read_buf, header, "syntax = \"proto3\";\nmessage Row { fixed32 number = 1; }"); + + EXPECT_THROW(format->read(), Exception); +} + +/// Invalid date string ("bad!") followed by a valid date ("2024-01-15"). +/// The bad row triggers CANNOT_PARSE_DATE (in isParseError). After the error, +/// destroyReaderAndSerializer nulls both. On the next iteration readRow recreates +/// them fresh, reads the valid message, and returns it. +TEST(ProtobufRowInputFormat, BadDateRecovery) +{ + std::string data; + + /// Bad message: field 1 (string) = "bad!" — not a valid date + data += '\x06'; // message length = 6 + data += '\x0a'; // field 1, wire type LENGTH_DELIMITED + data += '\x04'; // string length = 4 + data += "bad!"; + + /// Valid message: field 1 (string) = "2024-01-15" + data += '\x0c'; // message length = 12 + data += '\x0a'; // field 1, wire type LENGTH_DELIMITED + data += '\x0a'; // string length = 10 + data += "2024-01-15"; + + ReadBufferFromString read_buf(data); + auto header = ProtobufFormatHelper::dateHeader(); + auto format = ProtobufFormatHelper::createFormat( + read_buf, header, "syntax = \"proto3\";\nmessage Row { string d = 1; }"); + + Chunk chunk; + ASSERT_NO_THROW(chunk = format->read()); + ASSERT_EQ(chunk.getNumRows(), 1); + const auto & col = chunk.getColumns()[0]; + /// 2024-01-15 = day 19737 since epoch + EXPECT_EQ(col->getUInt(0), 19737); +} + +/// Bad date field followed by another field in the SAME message, then a valid message. +/// The bad message has: field 1 (string "bad!") + field 2 (fixed32 = 99). +/// The valid message has: field 1 (string "2024-01-15") + field 2 (fixed32 = 7). +/// After skipping the bad row, the valid row must still be returned. +TEST(ProtobufRowInputFormat, BadDateWithTrailingFieldRecovery) +{ + std::string data; + + /// Bad message: length = 11, field 1 = "bad!", field 2 = 99 + data += '\x0b'; // message length = 11 + data += '\x0a'; // field 1, wire type LENGTH_DELIMITED + data += '\x04'; // string length = 4 + data += "bad!"; + data += '\x15'; // field 2, wire type BITS32: tag = (2 << 3) | 5 + data += '\x63'; // 99 in little-endian + data += '\x00'; + data += '\x00'; + data += '\x00'; + + /// Valid message: length = 17, field 1 = "2024-01-15", field 2 = 7 + data += '\x11'; // message length = 17 + data += '\x0a'; // field 1, wire type LENGTH_DELIMITED + data += '\x0a'; // string length = 10 + data += "2024-01-15"; + data += '\x15'; // field 2, wire type BITS32 + data += '\x07'; + data += '\x00'; + data += '\x00'; + data += '\x00'; + + ReadBufferFromString read_buf(data); + auto header = ProtobufFormatHelper::dateHeader(); + auto format = ProtobufFormatHelper::createFormat( + read_buf, header, "syntax = \"proto3\";\nmessage Row { string d = 1; fixed32 n = 2; }"); + + Chunk chunk; + ASSERT_NO_THROW(chunk = format->read()); + ASSERT_EQ(chunk.getNumRows(), 1); + const auto & col = chunk.getColumns()[0]; + EXPECT_EQ(col->getUInt(0), 19737); +} + +/// Two valid messages read correctly. +TEST(ProtobufRowInputFormat, ValidMultipleMessages) +{ + std::string data; + + data += '\x05'; + data += '\x0d'; + data += '\x07'; + data += '\x00'; + data += '\x00'; + data += '\x00'; + + data += '\x05'; + data += '\x0d'; + data += '\x2a'; + data += '\x00'; + data += '\x00'; + data += '\x00'; + + ReadBufferFromString read_buf(data); + auto header = ProtobufFormatHelper::uint32Header(); + auto format = ProtobufFormatHelper::createFormat( + read_buf, header, "syntax = \"proto3\";\nmessage Row { fixed32 number = 1; }"); + + Chunk chunk; + ASSERT_NO_THROW(chunk = format->read()); + ASSERT_EQ(chunk.getNumRows(), 2); + const auto & col = chunk.getColumns()[0]; + EXPECT_EQ(col->getUInt(0), 7); + EXPECT_EQ(col->getUInt(1), 42); +} + +/// Multiple consecutive bad rows followed by a valid row. +/// All bad rows are skipped, the valid row at the end is returned. +TEST(ProtobufRowInputFormat, MultipleErrorsThenValidRow) +{ + std::string data; + + /// Bad message 1: field 1 (string) = "xxx" + data += '\x05'; // message length = 5 + data += '\x0a'; // field 1, wire type LENGTH_DELIMITED + data += '\x03'; // string length = 3 + data += "xxx"; + + /// Bad message 2: field 1 (string) = "yyy!" + data += '\x06'; // message length = 6 + data += '\x0a'; + data += '\x04'; // string length = 4 + data += "yyy!"; + + /// Bad message 3: field 1 (string) = "zz" + data += '\x04'; // message length = 4 + data += '\x0a'; + data += '\x02'; // string length = 2 + data += "zz"; + + /// Valid message: field 1 (string) = "2024-01-15" + data += '\x0c'; // message length = 12 + data += '\x0a'; + data += '\x0a'; // string length = 10 + data += "2024-01-15"; + + ReadBufferFromString read_buf(data); + auto header = ProtobufFormatHelper::dateHeader(); + auto format = ProtobufFormatHelper::createFormat( + read_buf, header, "syntax = \"proto3\";\nmessage Row { string d = 1; }"); + + Chunk chunk; + ASSERT_NO_THROW(chunk = format->read()); + ASSERT_EQ(chunk.getNumRows(), 1); + const auto & col = chunk.getColumns()[0]; + EXPECT_EQ(col->getUInt(0), 19737); +} + +/// Non-parse error leaves reader and serializer alive mid-message. +/// Calling read() again should not crash or produce garbage — it should +/// either throw again or return nothing. +TEST(ProtobufRowInputFormat, NonParseErrorReaderLeftAlive) +{ + std::string data; + + /// Bad message: invalid wire type (7) → UNKNOWN_PROTOBUF_FORMAT (not in isParseError) + data += '\x03'; // message length = 3 + data += '\x0f'; // field 1, wire type 7 (invalid): tag = (1 << 3) | 7 + data += '\x00'; + data += '\x00'; + + /// Valid message after it + data += '\x05'; // message length = 5 + data += '\x0d'; // field 1, wire type BITS32 + data += '\x07'; + data += '\x00'; + data += '\x00'; + data += '\x00'; + + ReadBufferFromString read_buf(data); + auto header = ProtobufFormatHelper::uint32Header(); + auto format = ProtobufFormatHelper::createFormat( + read_buf, header, "syntax = \"proto3\";\nmessage Row { fixed32 number = 1; }", + /* allow_errors_num = */ 0); + + /// First read throws (non-parse error, not skippable) + EXPECT_THROW(format->read(), Exception); + + /// Second read: reader/serializer were left alive mid-message from the first call. + /// The valid message follows in the stream. Verify no crash and check what happens. + Chunk chunk; + ASSERT_NO_THROW(chunk = format->read()); + /// The valid message (value=7) should be readable since the serializer's internal + /// catch already called endMessage on the bad message before re-throwing. + EXPECT_EQ(chunk.getNumRows(), 1); + if (chunk.getNumRows() > 0) + EXPECT_EQ(chunk.getColumns()[0]->getUInt(0), 7); +} + +/// A message whose length prefix claims more bytes than exist, with a bad date field. +/// The parse error (CANNOT_PARSE_DATE) fires, then endMessage(true) tries to skip to +/// the declared boundary but the stream is truncated → endMessage throws. +/// The error must NOT be silently skipped — it should propagate as a query failure. +TEST(ProtobufRowInputFormat, ResyncFailureMustNotSkip) +{ + std::string data; + + /// Bad message: length prefix claims 20 bytes but only 6 bytes of body follow. + /// Contains field 1 (string "bad!") which triggers CANNOT_PARSE_DATE. + /// After that, endMessage(true) tries to ignore(20 - 6 = 14 bytes) → EOF → throws. + data += '\x14'; // message length = 20 (but only 6 body bytes provided) + data += '\x0a'; // field 1, wire type LENGTH_DELIMITED + data += '\x04'; // string length = 4 + data += "bad!"; + + /// A "valid" message after it — but we should never reach it because the + /// bad message's boundary extends past EOF. + data += '\x05'; + data += '\x0d'; + data += '\x07'; + data += '\x00'; + data += '\x00'; + data += '\x00'; + + ReadBufferFromString read_buf(data); + auto header = ProtobufFormatHelper::dateHeader(); + auto format = ProtobufFormatHelper::createFormat( + read_buf, header, "syntax = \"proto3\";\nmessage Row { string d = 1; }"); + + /// Must throw — resync to the declared message boundary failed (stream truncated), + /// so the error cannot be safely skipped. + EXPECT_THROW(format->read(), Exception); +} + +/// ProtobufSingle (with_length_delimiter = false): one message = one row = entire input. +/// If a field parse error occurs mid-message, there's no next row to skip to. +/// allowSyncAfterError must return false so no partial row is produced from the +/// remaining fields of the same message. +TEST(ProtobufRowInputFormat, ProtobufSingleNoPartialRow) +{ + /// Single message with two fields: + /// field 1 (string) = "bad!" — invalid date, triggers CANNOT_PARSE_DATE + /// field 2 (fixed32) = 42 + /// Without the fix, after the bad date error the reader is recreated and + /// tries to parse field 2's bytes as a new root message, producing garbage. + std::string data; + data += '\x0a'; // field 1, wire type LENGTH_DELIMITED: tag = (1 << 3) | 2 + data += '\x04'; // string length = 4 + data += "bad!"; + data += '\x15'; // field 2, wire type BITS32: tag = (2 << 3) | 5 + data += '\x2a'; // 42 in little-endian + data += '\x00'; + data += '\x00'; + data += '\x00'; + + ReadBufferFromString read_buf(data); + auto header = ProtobufFormatHelper::dateHeader(); + auto format = ProtobufFormatHelper::createFormat( + read_buf, header, + "syntax = \"proto3\";\nmessage Row { string d = 1; fixed32 n = 2; }", + /* allow_errors_num = */ 10, + /* with_length_delimiter = */ false); + + /// Must throw — ProtobufSingle cannot skip errors because there's no next row. + /// Without the fix, the error is silently swallowed and an empty result is returned. + EXPECT_THROW(format->read(), Exception); +} + +#endif + From c12968ceb9873ea9756eff3fa5f36fd378bec56c Mon Sep 17 00:00:00 2001 From: Pedro Ferreira Date: Tue, 16 Jun 2026 17:12:45 +0000 Subject: [PATCH 048/146] Fix test --- .../0_stateless/04146_iceberg_orc_row_policy_prewhere.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/queries/0_stateless/04146_iceberg_orc_row_policy_prewhere.sh b/tests/queries/0_stateless/04146_iceberg_orc_row_policy_prewhere.sh index 9b0fa3370a9f..80fc11db6f70 100755 --- a/tests/queries/0_stateless/04146_iceberg_orc_row_policy_prewhere.sh +++ b/tests/queries/0_stateless/04146_iceberg_orc_row_policy_prewhere.sh @@ -35,7 +35,7 @@ rm -rf "${ICEBERG_PATH}" # Create an Iceberg table with format=Parquet (so the table-level PREWHERE check # passes) but write a mix of ORC and Parquet data files into it. ${CLICKHOUSE_CLIENT} --query " - SET allow_experimental_insert_into_iceberg = 1; + SET allow_experimental_insert_into_iceberg = 1, input_format_parquet_use_native_reader_v3 = 1; CREATE TABLE ${TEST_TABLE} (c0 Int64, c1 String) ENGINE = IcebergLocal('${ICEBERG_PATH}', 'Parquet'); From b3ddba84e57a596353d1e465a18e5b9724294372 Mon Sep 17 00:00:00 2001 From: robot-clickhouse Date: Tue, 16 Jun 2026 22:04:44 +0000 Subject: [PATCH 049/146] Backport #107516 to 26.3: Speed up analysis of queries with deeply nested or many function calls --- src/Analyzer/Resolve/QueryAnalyzer.h | 10 +- src/Analyzer/Resolve/resolveFunction.cpp | 36 +- .../analyzer_nested_function_resolution.xml | 1412 +++++++++++++++++ .../analyzer_repeat_alias_chain.xml | 35 + ...r_rand_constant_resolution_cache.reference | 6 + ...nalyzer_rand_constant_resolution_cache.sql | 20 + 6 files changed, 1495 insertions(+), 24 deletions(-) create mode 100644 tests/performance/analyzer_nested_function_resolution.xml create mode 100644 tests/performance/analyzer_repeat_alias_chain.xml create mode 100644 tests/queries/0_stateless/04339_analyzer_rand_constant_resolution_cache.reference create mode 100644 tests/queries/0_stateless/04339_analyzer_rand_constant_resolution_cache.sql diff --git a/src/Analyzer/Resolve/QueryAnalyzer.h b/src/Analyzer/Resolve/QueryAnalyzer.h index 01cec113a0d4..13d2692e3270 100644 --- a/src/Analyzer/Resolve/QueryAnalyzer.h +++ b/src/Analyzer/Resolve/QueryAnalyzer.h @@ -329,13 +329,9 @@ class QueryAnalyzer std::unordered_map node_to_scope_map; - struct ResolvedFunctionsCache - { - FunctionOverloadResolverPtr resolver; - FunctionBasePtr function_base; - }; - - std::map functions_cache; + /// Deduplicates the built `FunctionBase` for non-deterministic functions (e.g. `randConstant`) + /// by tree hash, so syntactically-identical calls fold to the same constant. See `resolveFunction`. + std::map functions_cache; const bool only_analyze; }; diff --git a/src/Analyzer/Resolve/resolveFunction.cpp b/src/Analyzer/Resolve/resolveFunction.cpp index 5c77d0a9cae5..41bfee905d89 100644 --- a/src/Analyzer/Resolve/resolveFunction.cpp +++ b/src/Analyzer/Resolve/resolveFunction.cpp @@ -1233,28 +1233,30 @@ ProjectionNames QueryAnalyzer::resolveFunction(QueryTreeNodePtr & node, Identifi } } - ResolvedFunctionsCache * function_cache = nullptr; + FunctionBasePtr * function_base_cache = nullptr; if (!function) { - /// This is a hack to allow a query like `select randConstant(), randConstant(), randConstant()`. - /// Function randConstant() would return the same value for the same arguments (in scope). - /// But we need to exclude getSetting() function because SETTINGS can change its result for every scope. + function = FunctionFactory::instance().tryGet(function_name, scope.context); + can_have_parameters = false; - if (function_name != "getSetting" && function_name != "rowNumberInAllBlocks") + /// This is a hack to allow a query like `select randConstant(), randConstant(), randConstant()`. + /// A non-deterministic function like `randConstant` returns a different value on every `build`, + /// so syntactically-identical calls must share the same built `FunctionBase` to fold to the same + /// constant. We deduplicate by tree hash to achieve that. + /// + /// Deterministic functions never need this (same arguments always produce the same result), and + /// `getTreeHash` walks the whole argument subtree, dominating analysis of deeply nested expressions. + /// So the hash and the cache are computed only for non-deterministic functions. + /// + /// `getSetting` and `rowNumberInAllBlocks` are non-deterministic but must NOT be shared: the cache + /// is global across scopes, and e.g. `SETTINGS` can change `getSetting`'s result for every scope. + if (function && !function->isDeterministic() + && function_name != "getSetting" && function_name != "rowNumberInAllBlocks") { auto hash = function_node_ptr->getTreeHash(); - - function_cache = &functions_cache[hash]; - if (!function_cache->resolver) - function_cache->resolver = FunctionFactory::instance().tryGet(function_name, scope.context); - - function = function_cache->resolver; + function_base_cache = &functions_cache[hash]; } - else - function = FunctionFactory::instance().tryGet(function_name, scope.context); - - can_have_parameters = false; } if (function) @@ -1495,9 +1497,9 @@ ProjectionNames QueryAnalyzer::resolveFunction(QueryTreeNodePtr & node, Identifi * so the same AST structure with different resolved lambda types * would incorrectly share the cached function base. */ - if (function_cache && !has_lambda_arguments) + if (function_base_cache && !has_lambda_arguments) { - auto & cached_function = function_cache->function_base; + auto & cached_function = *function_base_cache; if (!cached_function) cached_function = function->build(argument_columns); diff --git a/tests/performance/analyzer_nested_function_resolution.xml b/tests/performance/analyzer_nested_function_resolution.xml new file mode 100644 index 000000000000..fbf13a26ce86 --- /dev/null +++ b/tests/performance/analyzer_nested_function_resolution.xml @@ -0,0 +1,1412 @@ + + + + 1 + 100000 + 100000 + 10000000 + 10000000 + + + + + + + + + + + + + + + diff --git a/tests/performance/analyzer_repeat_alias_chain.xml b/tests/performance/analyzer_repeat_alias_chain.xml new file mode 100644 index 000000000000..366267933279 --- /dev/null +++ b/tests/performance/analyzer_repeat_alias_chain.xml @@ -0,0 +1,35 @@ + + + + 1 + + + + + diff --git a/tests/queries/0_stateless/04339_analyzer_rand_constant_resolution_cache.reference b/tests/queries/0_stateless/04339_analyzer_rand_constant_resolution_cache.reference new file mode 100644 index 000000000000..539cd695888d --- /dev/null +++ b/tests/queries/0_stateless/04339_analyzer_rand_constant_resolution_cache.reference @@ -0,0 +1,6 @@ +1 +1 +1 +1 +1 +2 diff --git a/tests/queries/0_stateless/04339_analyzer_rand_constant_resolution_cache.sql b/tests/queries/0_stateless/04339_analyzer_rand_constant_resolution_cache.sql new file mode 100644 index 000000000000..7da29881df5b --- /dev/null +++ b/tests/queries/0_stateless/04339_analyzer_rand_constant_resolution_cache.sql @@ -0,0 +1,20 @@ +-- The analyzer caches the built FunctionBase of non-deterministic functions by tree hash, so that +-- syntactically-identical calls (e.g. randConstant()) fold to the same constant. This must keep +-- working after the cache was narrowed to non-deterministic functions only (it used to apply to all +-- functions and recomputed an expensive tree hash for every function node). + +SET enable_analyzer = 1; + +-- Identical randConstant() calls must share the same value and therefore compare equal. +SELECT randConstant() = randConstant(); +SELECT randConstant(10) = randConstant(10); + +-- Sharing must survive wrapping in deterministic functions (the inner calls still share). +SELECT abs(randConstant()) = abs(randConstant()); +SELECT (randConstant() + 1) = (randConstant() + 1); + +-- now() is also non-deterministic and must remain consistent within the query. +SELECT now() = now(); + +-- Deterministic constant folding is unaffected. +SELECT 1 + 1; From a2b6a28cbff944610ce6abc48129a0826fcc25d9 Mon Sep 17 00:00:00 2001 From: robot-clickhouse Date: Wed, 17 Jun 2026 10:03:02 +0000 Subject: [PATCH 050/146] Backport #107485 to 26.3: validate message length before size - 4 in postgres protocol --- src/Core/PostgreSQLProtocol.h | 30 ++- src/Core/tests/gtest_postgresql_protocol.cpp | 211 +++++++++++++++++++ 2 files changed, 239 insertions(+), 2 deletions(-) create mode 100644 src/Core/tests/gtest_postgresql_protocol.cpp diff --git a/src/Core/PostgreSQLProtocol.h b/src/Core/PostgreSQLProtocol.h index 6847ab2c3156..1192b28edf72 100644 --- a/src/Core/PostgreSQLProtocol.h +++ b/src/Core/PostgreSQLProtocol.h @@ -221,6 +221,9 @@ class MessageTransport { Int32 size; readBinaryBigEndian(size, *in); + if (size < 4) + throw Exception(ErrorCodes::UNKNOWN_PACKET_FROM_CLIENT, + "Wrong message length {} received from client, it must be at least 4", size); in->ignore(size - 4); } @@ -516,8 +519,15 @@ class SASLInitialResponse : public Messaging::FrontMessage readNullTerminated(auth_method, in); Int32 size_sasl_mechanism; readBinaryBigEndian(size_sasl_mechanism, in); - sasl_mechanism.resize(size_sasl_mechanism); - in.readStrict(sasl_mechanism.data(), size_sasl_mechanism); + /// -1 is the protocol sentinel for "no initial response"; any other negative value is malformed. + if (size_sasl_mechanism < -1) + throw Exception(ErrorCodes::UNKNOWN_PACKET_FROM_CLIENT, + "Wrong SASL mechanism length {} in SASLInitialResponse, it must not be less than -1", size_sasl_mechanism); + if (size_sasl_mechanism > 0) + { + sasl_mechanism.resize(size_sasl_mechanism); + in.readStrict(sasl_mechanism.data(), size_sasl_mechanism); + } } MessageType getMessageType() const override @@ -566,6 +576,9 @@ class SASLResponse : public Messaging::FrontMessage readBinaryBigEndian(message_type, in); Int32 size; readBinaryBigEndian(size, in); + if (size < 4) + throw Exception(ErrorCodes::UNKNOWN_PACKET_FROM_CLIENT, + "Wrong message length {} in SASLResponse, it must be at least 4", size); sasl_mechanism.resize(size - 4); in.readStrict(sasl_mechanism.data(), size - 4); } @@ -770,6 +783,16 @@ class BindQuery : FrontMessage { Int32 sz_param; readBinaryBigEndian(sz_param, in); + /// -1 is the protocol sentinel for a NULL parameter and no value bytes follow; + /// any other negative value is malformed. + if (sz_param < -1) + throw Exception(ErrorCodes::UNKNOWN_PACKET_FROM_CLIENT, + "Wrong parameter length {} in Bind message, it must not be less than -1", sz_param); + if (sz_param == -1) + { + parameters.emplace_back("NULL"); + continue; + } String current_param(sz_param, 0); in.readStrict(current_param.data(), sz_param); parameters.push_back(current_param); @@ -1144,6 +1167,9 @@ class CopyInData : FrontMessage { Int32 sz; readBinaryBigEndian(sz, in); + if (sz < static_cast(sizeof(Int32))) + throw Exception(ErrorCodes::UNKNOWN_PACKET_FROM_CLIENT, + "Wrong message length {} in CopyData, it must be at least 4", sz); query.reserve(sz - sizeof(Int32)); for (size_t i = 0; i < sz - sizeof(Int32); ++i) { diff --git a/src/Core/tests/gtest_postgresql_protocol.cpp b/src/Core/tests/gtest_postgresql_protocol.cpp new file mode 100644 index 000000000000..5c0d4c3c8e18 --- /dev/null +++ b/src/Core/tests/gtest_postgresql_protocol.cpp @@ -0,0 +1,211 @@ +#include + +#include +#include +#include +#include + +#include + +namespace DB::ErrorCodes +{ + extern const int UNKNOWN_PACKET_FROM_CLIENT; +} + +using namespace DB; +namespace Messaging = DB::PostgreSQLProtocol::Messaging; + +namespace +{ + +void putUInt8(std::string & s, UInt8 v) +{ + s.push_back(static_cast(v)); +} + +void putInt16(std::string & s, Int16 v) +{ + s.push_back(static_cast((v >> 8) & 0xFF)); + s.push_back(static_cast(v & 0xFF)); +} + +void putInt32(std::string & s, Int32 v) +{ + for (int i = 3; i >= 0; --i) + s.push_back(static_cast((v >> (8 * i)) & 0xFF)); +} + +/// Run `body` over the bytes and report whether it threw UNKNOWN_PACKET_FROM_CLIENT. +template +bool throwsUnknownPacket(const std::string & bytes, F && body) +{ + ReadBufferFromMemory in(bytes.data(), bytes.size()); + try + { + body(in); + return false; + } + catch (const Exception & e) + { + EXPECT_EQ(e.code(), ErrorCodes::UNKNOWN_PACKET_FROM_CLIENT); + return e.code() == ErrorCodes::UNKNOWN_PACKET_FROM_CLIENT; + } +} + +} + +TEST(PostgreSQLProtocol, DropMessageRejectsLengthBelowFour) +{ + /// The message-length field includes its own four bytes, so anything below 4 underflows `size - 4`. + for (Int32 size = 0; size < 4; ++size) + { + std::string bytes; + putInt32(bytes, size); + WriteBufferFromOwnString out; + EXPECT_TRUE(throwsUnknownPacket(bytes, [&](ReadBuffer & in) + { + Messaging::MessageTransport mt(&in, &out); + mt.dropMessage(); + })) << "size = " << size; + } + + /// A well-formed length skips the declared number of trailing bytes. + std::string bytes; + putInt32(bytes, 8); + bytes += "abcd"; + ReadBufferFromMemory in(bytes.data(), bytes.size()); + WriteBufferFromOwnString out; + Messaging::MessageTransport mt(&in, &out); + EXPECT_NO_THROW(mt.dropMessage()); +} + +TEST(PostgreSQLProtocol, SASLResponseRejectsLengthBelowFour) +{ + for (Int32 size = 0; size < 4; ++size) + { + std::string bytes; + putUInt8(bytes, 'p'); + putInt32(bytes, size); + EXPECT_TRUE(throwsUnknownPacket(bytes, [](ReadBuffer & in) + { + Messaging::SASLResponse msg; + msg.deserialize(in); + })) << "size = " << size; + } + + /// size == 4 means an empty SASL payload. + std::string bytes; + putUInt8(bytes, 'p'); + putInt32(bytes, 4); + ReadBufferFromMemory in(bytes.data(), bytes.size()); + Messaging::SASLResponse msg; + EXPECT_NO_THROW(msg.deserialize(in)); + EXPECT_TRUE(msg.sasl_mechanism.empty()); +} + +TEST(PostgreSQLProtocol, SASLInitialResponseHandlesMechanismLength) +{ + auto build = [](Int32 size_sasl_mechanism, const std::string & data) + { + std::string bytes; + putUInt8(bytes, 'p'); + putInt32(bytes, 0); /// the outer size field is not used for bounds here + bytes += "SCRAM-SHA-256"; + bytes.push_back('\0'); + putInt32(bytes, size_sasl_mechanism); + bytes += data; + return bytes; + }; + + /// Below -1 is malformed. + EXPECT_TRUE(throwsUnknownPacket(build(-2, ""), [](ReadBuffer & in) + { + Messaging::SASLInitialResponse msg; + msg.deserialize(in); + })); + + /// -1 is the protocol sentinel for "no initial response". + { + std::string bytes = build(-1, ""); + ReadBufferFromMemory in(bytes.data(), bytes.size()); + Messaging::SASLInitialResponse msg; + EXPECT_NO_THROW(msg.deserialize(in)); + EXPECT_TRUE(msg.sasl_mechanism.empty()); + } + + /// A non-negative length reads exactly that many bytes. + { + std::string bytes = build(3, "abc"); + ReadBufferFromMemory in(bytes.data(), bytes.size()); + Messaging::SASLInitialResponse msg; + EXPECT_NO_THROW(msg.deserialize(in)); + EXPECT_EQ(msg.sasl_mechanism, "abc"); + } +} + +TEST(PostgreSQLProtocol, BindHandlesParameterLength) +{ + auto build = [](Int32 sz_param, const std::string & data) + { + std::string bytes; + putInt32(bytes, 0); /// the outer size field is not used for bounds here + bytes.push_back('\0'); /// empty portal name + bytes.push_back('\0'); /// empty statement name + putInt16(bytes, 0); /// no parameter format codes + putInt16(bytes, 1); /// one parameter + putInt32(bytes, sz_param); + bytes += data; + putInt16(bytes, 0); /// no result format codes + return bytes; + }; + + /// Below -1 is malformed. + EXPECT_TRUE(throwsUnknownPacket(build(-2, ""), [](ReadBuffer & in) + { + Messaging::BindQuery msg; + msg.deserialize(in); + })); + + /// -1 is the protocol sentinel for a NULL parameter; no value bytes follow. + { + std::string bytes = build(-1, ""); + ReadBufferFromMemory in(bytes.data(), bytes.size()); + Messaging::BindQuery msg; + EXPECT_NO_THROW(msg.deserialize(in)); + ASSERT_EQ(msg.parameters.size(), 1u); + EXPECT_EQ(msg.parameters[0], "NULL"); + } + + /// A non-negative length reads exactly that many bytes. + { + std::string bytes = build(2, "hi"); + ReadBufferFromMemory in(bytes.data(), bytes.size()); + Messaging::BindQuery msg; + EXPECT_NO_THROW(msg.deserialize(in)); + ASSERT_EQ(msg.parameters.size(), 1u); + EXPECT_EQ(msg.parameters[0], "hi"); + } +} + +TEST(PostgreSQLProtocol, CopyDataRejectsLengthBelowFour) +{ + for (Int32 size = 0; size < 4; ++size) + { + std::string bytes; + putInt32(bytes, size); + EXPECT_TRUE(throwsUnknownPacket(bytes, [](ReadBuffer & in) + { + Messaging::CopyInData msg; + msg.deserialize(in); + })) << "size = " << size; + } + + /// A well-formed length carries `size - 4` payload bytes. + std::string bytes; + putInt32(bytes, 6); + bytes += "ab"; + ReadBufferFromMemory in(bytes.data(), bytes.size()); + Messaging::CopyInData msg; + EXPECT_NO_THROW(msg.deserialize(in)); + EXPECT_EQ(msg.query, "ab"); +} From d3a3455bfd55d05f112f3e117f44bdc90150736d Mon Sep 17 00:00:00 2001 From: robot-clickhouse Date: Wed, 17 Jun 2026 11:54:54 +0000 Subject: [PATCH 051/146] Backport #100500 to 26.3: Sanitize query_id to prevent CRLF injection in HTTP response headers --- src/Server/HTTPHandler.cpp | 8 +++++- src/Server/PrometheusRequestHandler.cpp | 9 ++++++- ...04055_http_header_crlf_injection.reference | 5 ++++ .../04055_http_header_crlf_injection.sh | 25 +++++++++++++++++++ 4 files changed, 45 insertions(+), 2 deletions(-) create mode 100644 tests/queries/0_stateless/04055_http_header_crlf_injection.reference create mode 100755 tests/queries/0_stateless/04055_http_header_crlf_injection.sh diff --git a/src/Server/HTTPHandler.cpp b/src/Server/HTTPHandler.cpp index fe3c2db8bc78..479268879491 100644 --- a/src/Server/HTTPHandler.cpp +++ b/src/Server/HTTPHandler.cpp @@ -251,7 +251,13 @@ void HTTPHandler::processQuery( setReadOnlyIfHTTPMethodIdempotent(context, request.getMethod()); /// Set the query id supplied by the user, if any, and also update the OpenTelemetry fields. - context->setCurrentQueryId(params.get("query_id", request.get("X-ClickHouse-Query-Id", ""))); + String query_id = params.get("query_id", request.get("X-ClickHouse-Query-Id", "")); + + /// Sanitize query_id: remove ASCII control characters to prevent CRLF injection + /// into HTTP response headers (the query_id is reflected in X-ClickHouse-Query-Id). + std::erase_if(query_id, [](unsigned char c) { return isControlASCII(c) || c == 0x7F; }); + + context->setCurrentQueryId(query_id); bool has_external_data = startsWith(request.getContentType(), "multipart/form-data"); diff --git a/src/Server/PrometheusRequestHandler.cpp b/src/Server/PrometheusRequestHandler.cpp index e8d0c28fa70d..9bdb6c7f993f 100644 --- a/src/Server/PrometheusRequestHandler.cpp +++ b/src/Server/PrometheusRequestHandler.cpp @@ -15,6 +15,7 @@ #include #include +#include #include #include #include @@ -196,7 +197,13 @@ class PrometheusRequestHandler::ImplWithContext : public Impl context->applySettingsChanges(settings_changes); /// Set the query id supplied by the user, if any, and also update the OpenTelemetry fields. - context->setCurrentQueryId(params->get("query_id", request.get("X-ClickHouse-Query-Id", ""))); + String query_id = params->get("query_id", request.get("X-ClickHouse-Query-Id", "")); + + /// Sanitize query_id: remove ASCII control characters to prevent CRLF injection + /// into HTTP response headers (the query_id is reflected in X-ClickHouse-Query-Id). + std::erase_if(query_id, [](unsigned char c) { return isControlASCII(c) || c == 0x7F; }); + + context->setCurrentQueryId(query_id); } void onException() override diff --git a/tests/queries/0_stateless/04055_http_header_crlf_injection.reference b/tests/queries/0_stateless/04055_http_header_crlf_injection.reference new file mode 100644 index 000000000000..3798e8f2fbea --- /dev/null +++ b/tests/queries/0_stateless/04055_http_header_crlf_injection.reference @@ -0,0 +1,5 @@ +--- CRLF in query_id via URL parameter --- +injectLocation: evil.com +no injected headers +--- Normal query_id --- +normal_test_id_04055 diff --git a/tests/queries/0_stateless/04055_http_header_crlf_injection.sh b/tests/queries/0_stateless/04055_http_header_crlf_injection.sh new file mode 100755 index 000000000000..5bc2cdcd1caa --- /dev/null +++ b/tests/queries/0_stateless/04055_http_header_crlf_injection.sh @@ -0,0 +1,25 @@ +#!/usr/bin/env bash + +# Verify that CRLF characters in user-supplied HTTP parameters (like query_id) +# cannot be used to inject additional HTTP response headers. + +CUR_DIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd) +# shellcheck source=../shell_config.sh +. "$CUR_DIR"/../shell_config.sh + +extract_query_id() { + grep '< X-ClickHouse-Query-Id:' | sed 's/\r$//' | sed 's/^< X-ClickHouse-Query-Id: //' +} + +count_injected_headers() { + grep -F -ci "< $1:" | sed 's/^0$/no injected headers/' | sed '/^[1-9]/s/.*/FAIL: injected header detected/' +} + +echo "--- CRLF in query_id via URL parameter ---" +RESPONSE=$(${CLICKHOUSE_CURL} -sS --globoff -v "${CLICKHOUSE_URL}&query=SELECT+1&query_id=inject%0d%0aLocation:%20evil.com" 2>&1) +echo "$RESPONSE" | extract_query_id +echo "$RESPONSE" | count_injected_headers Location + +echo "--- Normal query_id ---" +${CLICKHOUSE_CURL} -sS --globoff -v "${CLICKHOUSE_URL}&query=SELECT+1&query_id=normal_test_id_04055" 2>&1 \ + | extract_query_id From f3ffa9eb7b3c843679d6f56b3945e5cc96a8813a Mon Sep 17 00:00:00 2001 From: Anton Popov Date: Tue, 16 Jun 2026 22:36:28 +0000 Subject: [PATCH 052/146] Backport text index prewhere filter type fix to release/26.2 Manual backport of 1c1c0315839229ff3f32612a51df66c8ad8b133f ("Fix text index prewhere mismatch with multiple indexes") from https://github.com/ClickHouse/ClickHouse/pull/98882. With `query_plan_direct_read_from_text_index` and `use_skip_indexes_on_data_read`, the text index read is a `None`-type prewhere step. `MergeTreeRangeReader` executed that step's actions on the sample block in its constructor, while `executePrewhereActionsAndFilterColumns` skips `None`-type steps at runtime. When a row-level filter (e.g. a `ROW POLICY`) reads a `String` column before the `None` step, the sample-block column order diverged from the actual data, so the raw `String` column landed in a filter position and the query raised `ILLEGAL_TYPE_OF_COLUMN_FOR_FILTER` ("Illegal type String of column for filter"). The fix skips executing step actions on the sample block for `None`-type steps, to match the runtime behavior. It also switches `IndexReadTasks` / `IndexReadColumns` to `std::map` for deterministic iteration order (the multiple-text-index variant of the same class of mismatch). Adds `04332_text_index_direct_read_row_policy` reproducing the single-index `ROW POLICY` case. Verified against public builds bracketing the fix: it raised `ILLEGAL_TYPE_OF_COLUMN_FOR_FILTER` on 26.4.1.689 (commit c1225a670d2e, before PR #98882) and returns 1000 on 26.4.1.691 (commit 4d79f6ea35f, the PR #98882 merge). Co-Authored-By: Claude Opus 4.8 (1M context) --- .../MergeTree/MergeTreeRangeReader.cpp | 16 +++++-- src/Storages/MergeTree/MergeTreeReadTask.h | 10 ++++- ...ext_index_direct_read_row_policy.reference | 2 + ...4332_text_index_direct_read_row_policy.sql | 42 +++++++++++++++++++ 4 files changed, 64 insertions(+), 6 deletions(-) create mode 100644 tests/queries/0_stateless/04332_text_index_direct_read_row_policy.reference create mode 100644 tests/queries/0_stateless/04332_text_index_direct_read_row_policy.sql diff --git a/src/Storages/MergeTree/MergeTreeRangeReader.cpp b/src/Storages/MergeTree/MergeTreeRangeReader.cpp index 748d37421388..db7da52fdaf7 100644 --- a/src/Storages/MergeTree/MergeTreeRangeReader.cpp +++ b/src/Storages/MergeTree/MergeTreeRangeReader.cpp @@ -906,11 +906,19 @@ MergeTreeRangeReader::MergeTreeRangeReader( if (prewhere_info) { const auto & step = *prewhere_info; - if (step.actions) - step.actions->execute(result_sample_block, true); + /// Must match the runtime behavior: `executePrewhereActionsAndFilterColumns` + /// returns early for `None`-type steps (e.g. text index read steps) without + /// executing actions or removing filter columns. If we execute them here on + /// the sample block, the column order diverges from the actual data, causing + /// column type mismatches in downstream filter steps. + if (step.type != PrewhereExprStep::None) + { + if (step.actions) + step.actions->execute(result_sample_block, true); - if (step.remove_filter_column) - result_sample_block.erase(step.filter_column_name); + if (step.remove_filter_column) + result_sample_block.erase(step.filter_column_name); + } } } diff --git a/src/Storages/MergeTree/MergeTreeReadTask.h b/src/Storages/MergeTree/MergeTreeReadTask.h index 3afd1eac4ab7..445631c30281 100644 --- a/src/Storages/MergeTree/MergeTreeReadTask.h +++ b/src/Storages/MergeTree/MergeTreeReadTask.h @@ -1,5 +1,6 @@ #pragma once +#include #include #include #include @@ -71,8 +72,13 @@ struct IndexReadTask bool is_final = false; }; -using IndexReadTasks = std::unordered_map; -using IndexReadColumns = std::unordered_map; +/// Ordered map to ensure deterministic iteration order. +/// `IndexReadTasks` may be copied (e.g. into `MergeTreeReadPoolBase`) and then +/// iterated independently in `getPrewhereActions` / `getReadTaskColumns`. +/// `std::unordered_map` does not guarantee the same iteration order after copy, +/// which leads to mismatched prewhere readers and actions. +using IndexReadTasks = std::map; +using IndexReadColumns = std::map; struct MergeTreeReadTaskColumns { diff --git a/tests/queries/0_stateless/04332_text_index_direct_read_row_policy.reference b/tests/queries/0_stateless/04332_text_index_direct_read_row_policy.reference new file mode 100644 index 000000000000..5878ba472255 --- /dev/null +++ b/tests/queries/0_stateless/04332_text_index_direct_read_row_policy.reference @@ -0,0 +1,2 @@ +1000 +1000 diff --git a/tests/queries/0_stateless/04332_text_index_direct_read_row_policy.sql b/tests/queries/0_stateless/04332_text_index_direct_read_row_policy.sql new file mode 100644 index 000000000000..2a2ee895da97 --- /dev/null +++ b/tests/queries/0_stateless/04332_text_index_direct_read_row_policy.sql @@ -0,0 +1,42 @@ +-- Regression test for a prewhere column mismatch in the text index direct read +-- optimization (`query_plan_direct_read_from_text_index`) when combined with a +-- row-level filter (a `ROW POLICY`) that reads a String column. +-- +-- The text index read is a `None`-type prewhere step. Its actions were executed on +-- the sample block in the `MergeTreeRangeReader` constructor, but skipped at runtime +-- by `executePrewhereActionsAndFilterColumns`. With a preceding row-level filter that +-- reads a String column, the sample-block column order diverged from the actual data, +-- so the raw String column landed in a filter position and the query failed with +-- `ILLEGAL_TYPE_OF_COLUMN_FOR_FILTER`. + +SET query_plan_direct_read_from_text_index = 1; +SET use_skip_indexes_on_data_read = 1; +SET use_query_condition_cache = 0; + +DROP TABLE IF EXISTS tab; + +CREATE TABLE tab +( + id UInt64, + category LowCardinality(String), + message String, + INDEX idx_message message TYPE text(tokenizer = splitByNonAlpha) GRANULARITY 1 +) +ENGINE = MergeTree +ORDER BY (category, id); + +INSERT INTO tab SELECT number, 'a', concat('hello world token ', toString(number)) FROM numbers(1000); + +DROP ROW POLICY IF EXISTS pol_04332 ON tab; +CREATE ROW POLICY pol_04332 ON tab FOR SELECT USING category != 'hidden' TO ALL; + +-- The row policy reads the String column `category` before the text index `None` step. +-- This used to raise `ILLEGAL_TYPE_OF_COLUMN_FOR_FILTER`; it must now return 1000. +SELECT count() FROM tab WHERE category = 'a' AND hasToken(message, 'token'); + +-- Sanity check: the same query without the direct read optimization returns the same result. +SELECT count() FROM tab WHERE category = 'a' AND hasToken(message, 'token') +SETTINGS query_plan_direct_read_from_text_index = 0; + +DROP ROW POLICY pol_04332 ON tab; +DROP TABLE tab; From 7c86709d68fdf0eb9b38eb2f66e5285e4b2766b1 Mon Sep 17 00:00:00 2001 From: robot-clickhouse Date: Wed, 17 Jun 2026 21:56:22 +0000 Subject: [PATCH 053/146] Backport #106357 to 26.3: Fix Azure endpoint settings being dropped on SYSTEM RELOAD CONFIG --- src/Interpreters/Context.cpp | 2 +- .../test.py | 98 +++++++++++++++++++ 2 files changed, 99 insertions(+), 1 deletion(-) diff --git a/src/Interpreters/Context.cpp b/src/Interpreters/Context.cpp index 837b2b385a55..58463cefa62a 100644 --- a/src/Interpreters/Context.cpp +++ b/src/Interpreters/Context.cpp @@ -6289,7 +6289,7 @@ void Context::updateStorageConfiguration(const Poco::Util::AbstractConfiguration { std::lock_guard lock(shared->mutex); if (shared->storage_azure_settings) - shared->storage_azure_settings->loadFromConfig(config, /* config_prefix */"configuration.disks.", getSettingsRef()); + shared->storage_azure_settings->loadFromConfig(config, /* config_prefix */"storage_configuration.disks", getSettingsRef()); } } diff --git a/tests/integration/test_backup_restore_azure_blob_storage/test.py b/tests/integration/test_backup_restore_azure_blob_storage/test.py index 0aaa79714294..c4ba085d643c 100644 --- a/tests/integration/test_backup_restore_azure_blob_storage/test.py +++ b/tests/integration/test_backup_restore_azure_blob_storage/test.py @@ -71,6 +71,48 @@ def generate_cluster_def(port): return path +def generate_cluster_def_native_copy(port): + # Dedicated node with an Azure disk that enables native copy, used to test config reload. + # The disk uses the same connection string as the backup target: the per-endpoint settings + # map is keyed by the raw endpoint string, so both must use the identical form to match. + worker_id = os.environ.get("PYTEST_XDIST_WORKER", "") + suffix = f"_{worker_id}" if worker_id else "" + path = os.path.join( + os.path.dirname(os.path.realpath(__file__)), + f"./_gen/native_copy{suffix}.xml", + ) + os.makedirs(os.path.dirname(path), exist_ok=True) + with open(path, "w") as f: + f.write( + f""" + + + + local + object_storage + azure_blob_storage + DefaultEndpointsProtocol=http;AccountName=devstoreaccount1;AccountKey=Eby8vdM02xNOcqFlqUwJPLlmEtlCDXJ1OUzFT50uSRZ6IFsuFq2UVErCz4I6tq/K1SZFPTOtr/KBHBeksoGMGw==;BlobEndpoint=http://azurite1:{port}/devstoreaccount1; + cont + false + true + + + + + +
+ blob_storage_disk_native_copy +
+
+
+
+
+
+""" + ) + return path + + @pytest.fixture(scope="module") def cluster(): try: @@ -82,6 +124,13 @@ def cluster(): main_configs=[path], with_azurite=True, ) + cluster.add_instance( + "node_native_copy", + main_configs=[generate_cluster_def_native_copy(port)], + with_azurite=True, + # Otherwise database-metadata copies also bump AzureCopyObject. + with_remote_database_disk=False, + ) cluster.start() yield cluster @@ -559,3 +608,52 @@ def test_backup_restore_on_merge_tree_with_checksum_data_file_name(cluster): assert azure_query(node, f"SELECT * from test_restored") == "1\ta\n" azure_query(node, f"DROP TABLE test") azure_query(node, f"DROP TABLE test_restored") + + +def get_profile_event_count(node, event): + return int( + azure_query( + node, f"SELECT sum(value) FROM system.events WHERE event = '{event}'" + ).strip() + ) + + +def test_reload_config_keeps_azure_endpoint_settings(cluster): + # use_native_copy=true must survive SYSTEM RELOAD CONFIG (settings reloaded, not dropped). + node = cluster.instances["node_native_copy"] + azure_query(node, "DROP TABLE IF EXISTS test_reload_native_copy SYNC") + azure_query( + node, + "CREATE TABLE test_reload_native_copy(key UInt64, data String) Engine = MergeTree() ORDER BY tuple() SETTINGS storage_policy='blob_storage_policy_native_copy'", + ) + azure_query(node, "INSERT INTO test_reload_native_copy VALUES (1, 'a')") + + # First BACKUP/RESTORE also lazily loads the endpoint settings map. + backup_destination = f"AzureBlobStorage('{cluster.env_variables['AZURITE_CONNECTION_STRING']}', 'cont', '{new_backup_name()}')" + azure_query(node, f"BACKUP TABLE test_reload_native_copy TO {backup_destination}") + azure_query(node, "DROP TABLE IF EXISTS test_reload_native_copy_r1") + before = get_profile_event_count(node, "AzureCopyObject") + azure_query( + node, + f"RESTORE TABLE test_reload_native_copy AS test_reload_native_copy_r1 FROM {backup_destination};", + ) + after = get_profile_event_count(node, "AzureCopyObject") + assert after > before, "disk use_native_copy=true must enable native copy" + + # Settings must be reloaded here, not wiped. + azure_query(node, "SYSTEM RELOAD CONFIG") + + backup_destination = f"AzureBlobStorage('{cluster.env_variables['AZURITE_CONNECTION_STRING']}', 'cont', '{new_backup_name()}')" + azure_query(node, f"BACKUP TABLE test_reload_native_copy TO {backup_destination}") + azure_query(node, "DROP TABLE IF EXISTS test_reload_native_copy_r2") + before = get_profile_event_count(node, "AzureCopyObject") + azure_query( + node, + f"RESTORE TABLE test_reload_native_copy AS test_reload_native_copy_r2 FROM {backup_destination};", + ) + after = get_profile_event_count(node, "AzureCopyObject") + assert after > before, "use_native_copy=true must survive SYSTEM RELOAD CONFIG" + + azure_query(node, "DROP TABLE test_reload_native_copy") + azure_query(node, "DROP TABLE test_reload_native_copy_r1") + azure_query(node, "DROP TABLE test_reload_native_copy_r2") From a5921113a1744265cb1b3267d7b313a129a77d12 Mon Sep 17 00:00:00 2001 From: robot-clickhouse Date: Thu, 18 Jun 2026 03:26:04 +0000 Subject: [PATCH 054/146] Backport #107693 to 26.3: Length-encode the `info` field of the MySQL `OK` packet --- .../integration/mysql_java_client/Dockerfile | 11 +++-- src/Core/MySQL/PacketsGeneric.cpp | 46 ++++++------------- 2 files changed, 21 insertions(+), 36 deletions(-) diff --git a/ci/docker/integration/mysql_java_client/Dockerfile b/ci/docker/integration/mysql_java_client/Dockerfile index c4d60bece83d..4926170553f4 100644 --- a/ci/docker/integration/mysql_java_client/Dockerfile +++ b/ci/docker/integration/mysql_java_client/Dockerfile @@ -6,10 +6,13 @@ FROM eclipse-temurin:21-jdk-alpine RUN apk --no-cache add curl -# Pin to 8.1.0 (original version): Connector/J 8.2.0+ and 9.x introduce breaking -# changes to the ClickHouse MySQL protocol handshake. CLASSPATH=. is required so -# javac-compiled .class files in the WORKDIR are found at runtime. -ARG ver=8.1.0 +# Connector/J 8.2.0+ (including 9.x) reads the `info` field of the MySQL `OK` +# packet as a length-encoded string; it used to be pinned to 8.1.0 because +# ClickHouse wrote that field as a plain string and newer drivers failed to +# connect. Fixed by length-encoding `info` (see #84542), so test against a +# modern driver. CLASSPATH=. is required so javac-compiled .class files in the +# WORKDIR are found at runtime. +ARG ver=9.7.0 RUN curl -L -o /mysql-connector-j-${ver}.jar https://repo1.maven.org/maven2/com/mysql/mysql-connector-j/${ver}/mysql-connector-j-${ver}.jar ENV CLASSPATH=.:/mysql-connector-j-${ver}.jar diff --git a/src/Core/MySQL/PacketsGeneric.cpp b/src/Core/MySQL/PacketsGeneric.cpp index af80797d5c16..5e9b442ab176 100644 --- a/src/Core/MySQL/PacketsGeneric.cpp +++ b/src/Core/MySQL/PacketsGeneric.cpp @@ -46,16 +46,14 @@ size_t OKPacket::getPayloadSize() const result += 2; } - if (capabilities & CLIENT_SESSION_TRACK) - { - result += getLengthEncodedStringSize(info); - if (status_flags & SERVER_SESSION_STATE_CHANGED) - result += getLengthEncodedStringSize(session_state_changes); - } - else - { - result += info.size(); - } + /// `info` is always a length-encoded string, matching real MySQL. The protocol documentation + /// describes it as a rest-of-packet string when `CLIENT_SESSION_TRACK` is not negotiated, but the + /// MySQL server length-encodes it unconditionally, and `MySQL Connector/J` >= 8.2 reads it as a + /// length-encoded string regardless of the negotiated capabilities. Writing a plain string here + /// made `Connector/J` interpret the first byte of `info` as a length and fail to connect. + result += getLengthEncodedStringSize(info); + if ((capabilities & CLIENT_SESSION_TRACK) && (status_flags & SERVER_SESSION_STATE_CHANGED)) + result += getLengthEncodedStringSize(session_state_changes); return result; } @@ -77,18 +75,9 @@ void OKPacket::readPayloadImpl(ReadBuffer & payload) payload.readStrict(reinterpret_cast(&status_flags), 2); } - if (capabilities & CLIENT_SESSION_TRACK) - { - readLengthEncodedString(info, payload); - if (status_flags & SERVER_SESSION_STATE_CHANGED) - { - readLengthEncodedString(session_state_changes, payload); - } - } - else - { - readString(info, payload); - } + readLengthEncodedString(info, payload); + if ((capabilities & CLIENT_SESSION_TRACK) && (status_flags & SERVER_SESSION_STATE_CHANGED)) + readLengthEncodedString(session_state_changes, payload); } void OKPacket::writePayloadImpl(WriteBuffer & buffer) const @@ -108,16 +97,9 @@ void OKPacket::writePayloadImpl(WriteBuffer & buffer) const buffer.write(reinterpret_cast(&status_flags), 2); } - if (capabilities & CLIENT_SESSION_TRACK) - { - writeLengthEncodedString(info, buffer); - if (status_flags & SERVER_SESSION_STATE_CHANGED) - writeLengthEncodedString(session_state_changes, buffer); - } - else - { - writeString(info, buffer); - } + writeLengthEncodedString(info, buffer); + if ((capabilities & CLIENT_SESSION_TRACK) && (status_flags & SERVER_SESSION_STATE_CHANGED)) + writeLengthEncodedString(session_state_changes, buffer); } EOFPacket::EOFPacket() : warnings(0x00), status_flags(0x00) From b69008ff30f4a0207d87cf5e5212f064f516e69e Mon Sep 17 00:00:00 2001 From: robot-clickhouse Date: Thu, 18 Jun 2026 09:48:27 +0000 Subject: [PATCH 055/146] Backport #106739 to 26.3: Fix memory-safety and resource-exhaustion issues in Parquet/Geo/Avro readers with untrusted input --- contrib/avro | 2 +- src/Functions/geometryConverters.h | 14 +- .../Formats/Impl/Parquet/Reader.cpp | 96 +++++++++- .../Formats/Impl/Parquet/SchemaConverter.cpp | 6 + ...04324_parquet_v3_datapage_v2_oob.reference | 10 + .../04324_parquet_v3_datapage_v2_oob.sh | 174 ++++++++++++++++++ ...parquet_v3_deep_schema_recursion.reference | 2 + .../04325_parquet_v3_deep_schema_recursion.sh | 96 ++++++++++ ..._parquet_v3_geoparquet_wkb_alloc.reference | 1 + .../04326_parquet_v3_geoparquet_wkb_alloc.sh | 47 +++++ .../04327_avro_string_length_alloc.reference | 1 + .../04327_avro_string_length_alloc.sh | 58 ++++++ 12 files changed, 495 insertions(+), 12 deletions(-) create mode 100644 tests/queries/0_stateless/04324_parquet_v3_datapage_v2_oob.reference create mode 100755 tests/queries/0_stateless/04324_parquet_v3_datapage_v2_oob.sh create mode 100644 tests/queries/0_stateless/04325_parquet_v3_deep_schema_recursion.reference create mode 100755 tests/queries/0_stateless/04325_parquet_v3_deep_schema_recursion.sh create mode 100644 tests/queries/0_stateless/04326_parquet_v3_geoparquet_wkb_alloc.reference create mode 100755 tests/queries/0_stateless/04326_parquet_v3_geoparquet_wkb_alloc.sh create mode 100644 tests/queries/0_stateless/04327_avro_string_length_alloc.reference create mode 100755 tests/queries/0_stateless/04327_avro_string_length_alloc.sh diff --git a/contrib/avro b/contrib/avro index 3b5d52bdf9f9..7b7f6a5bc42e 160000 --- a/contrib/avro +++ b/contrib/avro @@ -1 +1 @@ -Subproject commit 3b5d52bdf9f9f66121b663741f4270b33c91c1a6 +Subproject commit 7b7f6a5bc42e56b6f74de9fc8f52a2a629c53145 diff --git a/src/Functions/geometryConverters.h b/src/Functions/geometryConverters.h index bce5487ffb2d..ef2c82763c57 100644 --- a/src/Functions/geometryConverters.h +++ b/src/Functions/geometryConverters.h @@ -28,20 +28,24 @@ namespace ErrorCodes extern const int ILLEGAL_TYPE_OF_ARGUMENT; } +/// Use AllocatorWithMemoryTracking so that building geometries (in particular parsing untrusted +/// WKB/WKT, where element counts come straight off the wire) charges the MemoryTracker through its +/// throwing path. Otherwise a declared-but-absent element count drives a large `reserve` that +/// `max_memory_usage` does not bound (the default container tracking is non-throwing). template -using LineString = boost::geometry::model::linestring; +using LineString = boost::geometry::model::linestring; template -using MultiLineString = boost::geometry::model::multi_linestring>; +using MultiLineString = boost::geometry::model::multi_linestring, std::vector, AllocatorWithMemoryTracking>; template -using Ring = boost::geometry::model::ring; +using Ring = boost::geometry::model::ring; template -using Polygon = boost::geometry::model::polygon; +using Polygon = boost::geometry::model::polygon; template -using MultiPolygon = boost::geometry::model::multi_polygon>; +using MultiPolygon = boost::geometry::model::multi_polygon, std::vector, AllocatorWithMemoryTracking>; using CartesianPoint = boost::geometry::model::d2::point_xy; using CartesianLineString = LineString; diff --git a/src/Processors/Formats/Impl/Parquet/Reader.cpp b/src/Processors/Formats/Impl/Parquet/Reader.cpp index b0ec0df2e7c1..4930d3df2dec 100644 --- a/src/Processors/Formats/Impl/Parquet/Reader.cpp +++ b/src/Processors/Formats/Impl/Parquet/Reader.cpp @@ -5,6 +5,7 @@ #include #include #include +#include #include #include #include @@ -45,6 +46,25 @@ namespace ProfileEvents namespace DB::Parquet { +/// Thrift deserialization can store an out-of-range value into an unscoped enum field when the +/// input file is malformed. Loading such an enum directly is undefined behavior (caught by +/// -fsanitize=enum); reading the raw underlying integer via memcpy is well-defined. We use it to +/// validate page-header enums up front, so the rest of the reader only ever loads valid values. +template +static int thriftEnumToInt(const E & e) +{ + std::underlying_type_t v; + memcpy(&v, &e, sizeof(v)); + return static_cast(v); +} + +template +static void checkThriftEnum(const E & e, const std::map & valid_values, const char * what) +{ + if (!valid_values.contains(thriftEnumToInt(e))) + throw Exception(ErrorCodes::INCORRECT_DATA, "Invalid {} in Parquet metadata", what); +} + static void decompressLZ4Raw(const char * data, size_t compressed_size, size_t uncompressed_size, char * out) { if (compressed_size > INT32_MAX || uncompressed_size > INT32_MAX) @@ -842,9 +862,12 @@ void Reader::decodeDictionaryPageImpl(const parq::PageHeader & header, std::span throw Exception(ErrorCodes::INCORRECT_DATA, "Dictionary page size out of bounds: {} > {}", header.compressed_page_size, data.size()); data = data.subspan(0, size_t(header.compressed_page_size)); + checkThriftEnum(column.meta->meta_data.codec, parq::_CompressionCodec_VALUES_TO_NAMES, "compression codec"); auto codec = column.meta->meta_data.codec; if (codec != parq::CompressionCodec::UNCOMPRESSED) { + if (header.uncompressed_page_size < 0) + throw Exception(ErrorCodes::INCORRECT_DATA, "Negative uncompressed dictionary page size"); size_t uncompressed_size = size_t(header.uncompressed_page_size); auto & buf = column.dictionary.decompressed_buf; buf.resize(uncompressed_size); @@ -852,6 +875,10 @@ void Reader::decodeDictionaryPageImpl(const parq::PageHeader & header, std::span data = std::span(buf.data(), buf.size()); } + /// Signed i32 from the thrift header; a negative count would sign-extend to a huge size_t and + /// drive a huge `reserve`/`resize` inside Dictionary::decode. + if (header.dictionary_page_header.num_values < 0) + throw Exception(ErrorCodes::INCORRECT_DATA, "Negative number of values in dictionary page"); column.dictionary.decode(header.dictionary_page_header.encoding, column_info.decoder, size_t(header.dictionary_page_header.num_values), data, *column_info.decoded_type); } @@ -1516,6 +1543,27 @@ std::tuple> Reader::decodeAndCheckPageHe { parq::PageHeader header; data_ptr += deserializeThriftStruct(header, data_ptr, data_end - data_ptr); + + /// Validate enum fields before anything loads them (a malformed file can carry out-of-range + /// values, and loading an unscoped enum out of range is undefined behavior). + checkThriftEnum(header.type, parq::_PageType_VALUES_TO_NAMES, "page type"); + switch (header.type) + { + case parq::PageType::DATA_PAGE: + checkThriftEnum(header.data_page_header.encoding, parq::_Encoding_VALUES_TO_NAMES, "encoding"); + checkThriftEnum(header.data_page_header.definition_level_encoding, parq::_Encoding_VALUES_TO_NAMES, "definition level encoding"); + checkThriftEnum(header.data_page_header.repetition_level_encoding, parq::_Encoding_VALUES_TO_NAMES, "repetition level encoding"); + break; + case parq::PageType::DATA_PAGE_V2: + checkThriftEnum(header.data_page_header_v2.encoding, parq::_Encoding_VALUES_TO_NAMES, "encoding"); + break; + case parq::PageType::DICTIONARY_PAGE: + checkThriftEnum(header.dictionary_page_header.encoding, parq::_Encoding_VALUES_TO_NAMES, "encoding"); + break; + default: + break; + } + size_t compressed_page_size = size_t(header.compressed_page_size); if (header.compressed_page_size < 0 || compressed_page_size > size_t(data_end - data_ptr)) throw Exception(ErrorCodes::INCORRECT_DATA, "Page size out of bounds: {} > {}", header.compressed_page_size, data_end - data_ptr); @@ -1554,12 +1602,24 @@ bool Reader::initializeDataPage(const char * & data_ptr, const char * data_end, /// Check if all rows of the page are filtered out, if we have enough information. + /// These signed i32 row counts are consumed below to compute `page.end_row_idx` (and the + /// row-skip shortcut returns before the later num_values check). A negative value would + /// sign-extend to a huge size_t and wrap `next_row_idx + num_rows`, moving the row cursor + /// backwards or skipping the page instead of failing, so reject it here. std::optional num_rows_in_page; if (header.type == parq::PageType::DATA_PAGE_V2) + { + if (header.data_page_header_v2.num_rows < 0) + throw Exception(ErrorCodes::INCORRECT_DATA, "Negative number of rows in DataPageV2"); num_rows_in_page = header.data_page_header_v2.num_rows; + } else if (header.type == parq::PageType::DATA_PAGE && column_info.levels.back().rep == 0) // no arrays => num_values == num_rows + { + if (header.data_page_header.num_values < 0) + throw Exception(ErrorCodes::INCORRECT_DATA, "Negative number of values in data page"); num_rows_in_page = header.data_page_header.num_values; + } if (num_rows_in_page.has_value()) { @@ -1577,8 +1637,13 @@ bool Reader::initializeDataPage(const char * & data_ptr, const char * data_end, /// Get information about page layout and encoding out of page header. + checkThriftEnum(column.meta->meta_data.codec, parq::_CompressionCodec_VALUES_TO_NAMES, "compression codec"); page.codec = column.meta->meta_data.codec; - page.values_uncompressed_size = header.uncompressed_page_size; + /// Signed i32 from the thrift header; a negative value would sign-extend to a huge size_t and + /// later cause a huge allocation in decompressPageIfCompressed. + if (header.uncompressed_page_size < 0) + throw Exception(ErrorCodes::INCORRECT_DATA, "Negative uncompressed page size"); + page.values_uncompressed_size = size_t(header.uncompressed_page_size); if (page.codec == parq::CompressionCodec::UNCOMPRESSED && header.uncompressed_page_size != header.compressed_page_size) throw Exception(ErrorCodes::INCORRECT_DATA, "No compression, but compressed and uncompressed page size are different"); @@ -1592,7 +1657,9 @@ bool Reader::initializeDataPage(const char * & data_ptr, const char * data_end, if (header.type == parq::PageType::DATA_PAGE) { - page.num_values = header.data_page_header.num_values; + if (header.data_page_header.num_values < 0) + throw Exception(ErrorCodes::INCORRECT_DATA, "Negative number of values in data page"); + page.num_values = size_t(header.data_page_header.num_values); page.encoding = header.data_page_header.encoding; def_encoding = header.data_page_header.definition_level_encoding; rep_encoding = header.data_page_header.repetition_level_encoding; @@ -1633,10 +1700,20 @@ bool Reader::initializeDataPage(const char * & data_ptr, const char * data_end, } else if (header.type == parq::PageType::DATA_PAGE_V2) { - page.num_values = header.data_page_header_v2.num_values; + if (header.data_page_header_v2.num_values < 0) + throw Exception(ErrorCodes::INCORRECT_DATA, "Negative number of values in data page"); + page.num_values = size_t(header.data_page_header_v2.num_values); page.encoding = header.data_page_header_v2.encoding; - encoded_def_size = header.data_page_header_v2.definition_levels_byte_length; - encoded_rep_size = header.data_page_header_v2.repetition_levels_byte_length; + + /// These come from the thrift header as signed i32. A negative value would sign-extend to a + /// huge size_t, so reject it before assigning to the size_t fields. Otherwise the bounds + /// check below could be bypassed by integer overflow, and a huge std::span would reach the + /// rep/def level decoder, reading far past the page buffer (heap out-of-bounds read). + if (header.data_page_header_v2.definition_levels_byte_length < 0 || + header.data_page_header_v2.repetition_levels_byte_length < 0) + throw Exception(ErrorCodes::INCORRECT_DATA, "Negative definition/repetition levels byte length in DataPageV2"); + encoded_def_size = size_t(header.data_page_header_v2.definition_levels_byte_length); + encoded_rep_size = size_t(header.data_page_header_v2.repetition_levels_byte_length); if (header.data_page_header_v2.__isset.is_compressed && !header.data_page_header_v2.is_compressed) @@ -1644,12 +1721,15 @@ bool Reader::initializeDataPage(const char * & data_ptr, const char * data_end, page.codec = parq::CompressionCodec::UNCOMPRESSED; } - if (encoded_def_size + encoded_rep_size > page.data.size()) + /// Non-wrapping bounds check: `encoded_def_size + encoded_rep_size` could overflow. + if (encoded_rep_size > page.data.size() || encoded_def_size > page.data.size() - encoded_rep_size) throw Exception(ErrorCodes::INCORRECT_DATA, "Page data is too short (def+rep)"); encoded_rep = page.data.data(); encoded_def = page.data.data() + encoded_rep_size; size_t uncompressed_part = encoded_def_size + encoded_rep_size; page.data = page.data.subspan(uncompressed_part); + if (page.values_uncompressed_size < uncompressed_part) + throw Exception(ErrorCodes::INCORRECT_DATA, "DataPageV2 uncompressed page size is smaller than the rep/def levels"); page.values_uncompressed_size -= uncompressed_part; } else if (header.type == parq::PageType::DICTIONARY_PAGE) @@ -2023,6 +2103,10 @@ void Reader::decompressPageIfCompressed(PageState & page) MutableColumnPtr Reader::formOutputColumn(RowSubgroup & row_subgroup, size_t output_column_idx, size_t num_rows) { + /// Recurses over nested output types; bound the depth so a deeply nested schema cannot + /// overflow the stack. + checkStackSize(); + const OutputColumnInfo & output_info = output_columns.at(output_column_idx); MutableColumnPtr res; diff --git a/src/Processors/Formats/Impl/Parquet/SchemaConverter.cpp b/src/Processors/Formats/Impl/Parquet/SchemaConverter.cpp index f6ea1a4bd6ce..2be4a0073730 100644 --- a/src/Processors/Formats/Impl/Parquet/SchemaConverter.cpp +++ b/src/Processors/Formats/Impl/Parquet/SchemaConverter.cpp @@ -1,5 +1,6 @@ #include +#include #include #include #include @@ -168,6 +169,11 @@ std::string_view SchemaConverter::useColumnMapperIfNeeded(const parq::SchemaElem void SchemaConverter::processSubtree(TraversalNode & node) { + /// A deeply nested schema (e.g. a long chain of REQUIRED groups) recurses here per level. + /// The definition-level cap below only counts OPTIONAL/REPEATED nesting, so without this an + /// untrusted file could overflow the stack (uncatchable crash) instead of throwing. + checkStackSize(); + if (node.type_hint) chassert(node.requested); if (schema_idx >= file_metadata.schema.size()) diff --git a/tests/queries/0_stateless/04324_parquet_v3_datapage_v2_oob.reference b/tests/queries/0_stateless/04324_parquet_v3_datapage_v2_oob.reference new file mode 100644 index 000000000000..1cc545929d9b --- /dev/null +++ b/tests/queries/0_stateless/04324_parquet_v3_datapage_v2_oob.reference @@ -0,0 +1,10 @@ +nint_wrap: rejected +nstr_wrap: rejected +arr_wrap: rejected +nint_negdef: rejected +nint_neguncomp: rejected +nint_badtype: rejected +nint_badenc: rejected +nint_negnumvals: rejected +strdict_negnumvals: rejected +multipage_negnrows: rejected diff --git a/tests/queries/0_stateless/04324_parquet_v3_datapage_v2_oob.sh b/tests/queries/0_stateless/04324_parquet_v3_datapage_v2_oob.sh new file mode 100755 index 000000000000..f665fb605e8c --- /dev/null +++ b/tests/queries/0_stateless/04324_parquet_v3_datapage_v2_oob.sh @@ -0,0 +1,174 @@ +#!/usr/bin/env bash +# Tags: no-fasttest +# no-fasttest: needs pyarrow to craft the malformed Parquet files. + +# Regression test for a heap out-of-bounds read in the Parquet V3 native reader. +# +# DataPageV2 headers carry definition_levels_byte_length / repetition_levels_byte_length +# as signed i32. A negative value sign-extended to size_t, and the bounds check used +# wrapping addition (`def + rep > page.data.size()`), so a pair like (def=-1, rep=1) +# wrapped the sum to 0 and bypassed the check. A 2^64-sized std::span then reached the +# RLE level decoder, whose raw memcpy read far past the page buffer (heap disclosure). +# +# The reader must reject every crafted file with a clean INCORRECT_DATA exception +# instead of reading out of bounds. On an unpatched ASan/UBSan build each "wrap" case +# triggers a sanitizer error. + +CUR_DIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd) +# shellcheck source=../shell_config.sh +. "$CUR_DIR"/../shell_config.sh + +WORK_DIR="${CLICKHOUSE_TMP}/${CLICKHOUSE_TEST_UNIQUE_NAME}" +rm -rf "$WORK_DIR" +mkdir -p "$WORK_DIR" +trap 'rm -rf "$WORK_DIR"' EXIT + +python3 - "$WORK_DIR" <<'PYEOF' +import io, sys +import pyarrow as pa +import pyarrow.parquet as pq + +work = sys.argv[1] + +# --- minimal Thrift compact protocol walker: record offset of every scalar field --- +CT_INT = {0x03, 0x04, 0x05, 0x06} # byte, i16, i32, i64 + +def zigzag_decode(n): return (n >> 1) ^ -(n & 1) +def zigzag_encode(n, bits=64): + m = (1 << bits) - 1; u = n & m + return ((u << 1) ^ (-(u >> (bits - 1)) & m)) & m +def encode_varint(n): + out = bytearray() + while True: + b = n & 0x7F; n >>= 7 + out.append(b | (0x80 if n else 0)) + if not n: break + return bytes(out) + +class Walker: + def __init__(self, buf, pos): self.buf, self.pos, self.rec = buf, pos, [] + def byte(self): b = self.buf[self.pos]; self.pos += 1; return b + def varint(self): + r = s = 0 + while True: + b = self.byte(); r |= (b & 0x7F) << s + if not (b & 0x80): return r + s += 7 + def zz(self): return zigzag_decode(self.varint()) + def struct(self, path): + last = 0 + while True: + h = self.byte() + if h == 0: return + d = (h & 0xF0) >> 4; ct = h & 0x0F + fid = self.zz() if d == 0 else last + d + last = fid + self.field(ct, path + [fid]) + def field(self, ct, path): + vs = self.pos; val = None + if ct in (0x01, 0x02): val = (ct == 0x01) + elif ct == 0x03: val = self.byte() + elif ct in (0x04, 0x05, 0x06): val = self.zz() + elif ct == 0x07: self.pos += 8 + elif ct == 0x08: self.pos += self.varint() + elif ct in (0x09, 0x0A): self.lst(path) + elif ct == 0x0C: self.struct(path) + else: raise ValueError(f"ctype {ct}") + if ct in CT_INT: self.rec.append((tuple(path), vs, self.pos, val)) + def lst(self, path): + st = self.byte(); n = (st & 0xF0) >> 4; et = st & 0x0F + if n == 0xF: n = self.varint() + for i in range(n): self.field(et, path + [i]) + +def first_page_offset(buf): + col = pq.ParquetFile(io.BytesIO(bytes(buf))).metadata.row_group(0).column(0) + off = col.data_page_offset + if col.has_dictionary_page and col.dictionary_page_offset is not None: + off = min(off, col.dictionary_page_offset) + return off + +def patch(buf, off, field_path, signed_value): + w = Walker(buf, off); w.struct([]) + for pi, vs, ve, val in w.rec: + if pi == tuple(field_path): + nb = encode_varint(zigzag_encode(signed_value)) + assert len(nb) == ve - vs, f"len change for {field_path}: {ve-vs}->{len(nb)} ({val}->{signed_value})" + buf[vs:ve] = nb + return buf + raise KeyError(field_path) + +def make(path, dtype, vals, compression="none", use_dictionary=False, data_page_size=None): + kw = {} if data_page_size is None else {"data_page_size": data_page_size} + pq.write_table(pa.table({"v": pa.array(vals, type=dtype)}), path, + data_page_version="2.0", compression=compression, + use_dictionary=use_dictionary, write_statistics=False, **kw) + +def page_offsets(buf): + """Offsets of every data page header (walks header + compressed_page_size), bounded by the + column chunk's compressed size so we never run into the file footer.""" + col = pq.ParquetFile(io.BytesIO(bytes(buf))).metadata.row_group(0).column(0) + p = col.data_page_offset + end = col.data_page_offset + col.total_compressed_size + offs = [] + while p < end: + w = Walker(buf, p); w.struct([]) + cps = next(v for (pi, vs, ve, v) in w.rec if pi == (3,)) # compressed_page_size + offs.append(p) + p = w.pos + cps + return offs + +# PageHeader field ids: 2=uncompressed_page_size; 8=data_page_header_v2 struct, +# whose 5=definition_levels_byte_length, 6=repetition_levels_byte_length. +NINT = [i if i % 3 else None for i in range(64)] +NSTR = [("x" * ((i % 7) + 1) if i % 3 else None) for i in range(64)] +ARR = [list(range(i % 5)) for i in range(64)] + +def craft(name, dtype, vals, patches, compression="none", use_dictionary=False): + base = f"{work}/{name}.parquet" + make(base, dtype, vals, compression, use_dictionary) + buf = bytearray(open(base, "rb").read()) + off = first_page_offset(buf) + for fp, v in patches: + patch(buf, off, fp, v) + open(f"{work}/{name}_evil.parquet", "wb").write(buf) + +# Wrapping bypass (def + rep wraps to a small value -> huge def/rep span -> OOB read). +craft("nint_wrap", pa.int32(), NINT, [((8, 5), -1), ((8, 6), 1)]) +craft("nstr_wrap", pa.string(), NSTR, [((8, 5), -2), ((8, 6), 2)]) +craft("arr_wrap", pa.list_(pa.int32()), ARR, [((8, 5), 1), ((8, 6), -1)]) +# Negative definition length alone (no wrap, must still be rejected). +craft("nint_negdef", pa.int32(), NINT, [((8, 5), -1)]) +# Negative uncompressed_page_size on a compressed page (sizes the decompression buffer). +craft("nint_neguncomp", pa.int32(), NINT, [((2,), -8192)], compression="snappy") +# Out-of-range enum values (loading an unscoped enum out of range is undefined behavior). +# PageHeader field 1 = type (PageType); data_page_header_v2 field 4 = encoding (Encoding). +craft("nint_badtype", pa.int32(), NINT, [((1,), -64)]) +craft("nint_badenc", pa.int32(), NINT, [((8, 4), -64)]) +# Negative num_values (data_page_header_v2 field 1). +craft("nint_negnumvals", pa.int32(), NINT, [((8, 1), -8192)]) +# Negative num_values in the DICTIONARY page header (dictionary_page_header field 1). The first page +# of a dictionary-encoded column is the DICTIONARY_PAGE, so this patches its header. +DICTSTR = [("v%d" % (i % 5)) for i in range(64)] # few distinct values -> small dict num_values +craft("strdict_negnumvals", pa.string(), DICTSTR, [((7, 1), -64)], use_dictionary=True) +# Negative num_rows in a NON-FIRST DataPageV2. num_rows is consumed early (before the num_values +# check) to compute the row cursor, so a negative value on a later page wraps it backwards. Build a +# multi-page file and patch the second page's num_rows (-1024 keeps the 2-byte varint length of 1024). +make(f"{work}/multipage.parquet", pa.int32(), list(range(4000)), data_page_size=256) +mp = bytearray(open(f"{work}/multipage.parquet", "rb").read()) +offs = page_offsets(mp) +assert len(offs) >= 2, f"expected multiple pages, got {len(offs)}" +patch(mp, offs[1], (8, 3), -1024) +open(f"{work}/multipage_negnrows_evil.parquet", "wb").write(mp) +PYEOF + +# Each crafted file must be rejected with INCORRECT_DATA (code 117), not crash or leak. +for f in nint_wrap nstr_wrap arr_wrap nint_negdef nint_neguncomp nint_badtype nint_badenc nint_negnumvals strdict_negnumvals multipage_negnrows; do + out=$(${CLICKHOUSE_LOCAL} --query " + SELECT * FROM file('${WORK_DIR}/${f}_evil.parquet', Parquet) FORMAT Null + SETTINGS input_format_parquet_use_native_reader_v3 = 1" 2>&1) + if echo "$out" | grep -q "INCORRECT_DATA"; then + echo "$f: rejected" + else + echo "$f: UNEXPECTED: $out" + fi +done diff --git a/tests/queries/0_stateless/04325_parquet_v3_deep_schema_recursion.reference b/tests/queries/0_stateless/04325_parquet_v3_deep_schema_recursion.reference new file mode 100644 index 000000000000..29deae098da5 --- /dev/null +++ b/tests/queries/0_stateless/04325_parquet_v3_deep_schema_recursion.reference @@ -0,0 +1,2 @@ +deep: rejected +shallow: ok diff --git a/tests/queries/0_stateless/04325_parquet_v3_deep_schema_recursion.sh b/tests/queries/0_stateless/04325_parquet_v3_deep_schema_recursion.sh new file mode 100755 index 000000000000..c5feaaffc8e3 --- /dev/null +++ b/tests/queries/0_stateless/04325_parquet_v3_deep_schema_recursion.sh @@ -0,0 +1,96 @@ +#!/usr/bin/env bash +# Tags: no-fasttest +# no-fasttest: the Parquet format is not available in the fast test build. + +# Regression test for unbounded recursion in the Parquet V3 native reader's schema converter. +# A chain of nested REQUIRED groups maps to nested Tuples and recurses through +# SchemaConverter::processSubtree per level. The definition-level cap (255) only counts +# OPTIONAL/REPEATED nesting, so a deep REQUIRED chain recursed without bound and overflowed the +# stack -> uncatchable SIGSEGV (server crash / DoS), triggerable even by schema inference (DESC). +# checkStackSize() now turns it into a catchable TOO_DEEP_RECURSION exception. + +CUR_DIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd) +# shellcheck source=../shell_config.sh +. "$CUR_DIR"/../shell_config.sh + +WORK_DIR="${CLICKHOUSE_TMP}/${CLICKHOUSE_TEST_UNIQUE_NAME}" +rm -rf "$WORK_DIR" +mkdir -p "$WORK_DIR" +trap 'rm -rf "$WORK_DIR"' EXIT + +# Hand-build a Parquet file (PAR1 + FileMetaData footer, no row groups) whose schema is a root with +# N nested REQUIRED groups (num_children=1 each) and an INT32 leaf. Pure stdlib, no pyarrow. +python3 - "$WORK_DIR" <<'PYEOF' +import struct, sys +work = sys.argv[1] + +class W: + def __init__(s): s.out = bytearray() + def u8(s, v): s.out.append(v & 0xff) + def varint(s, v): + v &= (1 << 64) - 1 + while True: + b = v & 0x7f; v >>= 7 + s.out.append(b | 0x80) if v else s.out.append(b) + if not v: return + def zz(s, n): s.varint(((n << 1) ^ (n >> 63)) & ((1 << 64) - 1)) + +CT_I32, CT_I64, CT_BIN, CT_LIST, CT_STRUCT = 5, 6, 8, 9, 12 + +def field(w, last, fid, ctype): + d = fid - last + if 1 <= d <= 15: w.u8((d << 4) | ctype) + else: w.u8(ctype); w.zz(fid) + return fid + +def schema_elem(w, name, repetition=None, num_children=None, ptype=None): + last = 0 + if ptype is not None: + last = field(w, last, 1, CT_I32); w.zz(ptype) # type + if repetition is not None: + last = field(w, last, 3, CT_I32); w.zz(repetition) # repetition_type (REQUIRED=0) + last = field(w, last, 4, CT_BIN); w.varint(len(name)); w.out += name.encode() # name + if num_children is not None: + last = field(w, last, 5, CT_I32); w.zz(num_children) # num_children + w.u8(0) + +def build(n): + fmd = W(); last = 0 + last = field(fmd, last, 1, CT_I32); fmd.zz(2) # version + last = field(fmd, last, 2, CT_LIST) # schema list + total = n + 2 # root + n groups + leaf + if total < 15: fmd.u8((total << 4) | CT_STRUCT) + else: fmd.u8((15 << 4) | CT_STRUCT); fmd.varint(total) + schema_elem(fmd, "root", repetition=None, num_children=1) + for _ in range(n): + schema_elem(fmd, "g", repetition=0, num_children=1) # REQUIRED group + schema_elem(fmd, "leaf", repetition=0, ptype=1) # INT32 leaf + last = field(fmd, last, 3, CT_I64); fmd.zz(0) # num_rows + last = field(fmd, last, 4, CT_LIST); fmd.u8((0 << 4) | CT_STRUCT) # row_groups (empty) + fmd.u8(0) + meta = bytes(fmd.out) + return b'PAR1' + meta + struct.pack('&1) +if echo "$out" | grep -q "TOO_DEEP_RECURSION"; then + echo "deep: rejected" +else + echo "deep: UNEXPECTED: $out" +fi + +# A reasonably nested schema must still work. +shallow=$(${CLICKHOUSE_LOCAL} --query " + DESC file('${WORK_DIR}/shallow.parquet', Parquet) + SETTINGS input_format_parquet_use_native_reader_v3 = 1" 2>&1) +if echo "$shallow" | grep -q "Tuple"; then + echo "shallow: ok" +else + echo "shallow: UNEXPECTED: $shallow" +fi diff --git a/tests/queries/0_stateless/04326_parquet_v3_geoparquet_wkb_alloc.reference b/tests/queries/0_stateless/04326_parquet_v3_geoparquet_wkb_alloc.reference new file mode 100644 index 000000000000..d070e1a4f42c --- /dev/null +++ b/tests/queries/0_stateless/04326_parquet_v3_geoparquet_wkb_alloc.reference @@ -0,0 +1 @@ +geo_bomb: memory limit enforced diff --git a/tests/queries/0_stateless/04326_parquet_v3_geoparquet_wkb_alloc.sh b/tests/queries/0_stateless/04326_parquet_v3_geoparquet_wkb_alloc.sh new file mode 100755 index 000000000000..ab20e680abd3 --- /dev/null +++ b/tests/queries/0_stateless/04326_parquet_v3_geoparquet_wkb_alloc.sh @@ -0,0 +1,47 @@ +#!/usr/bin/env bash +# Tags: no-fasttest +# no-fasttest: needs pyarrow to write the GeoParquet file. + +# Regression test for an untracked-allocation DoS in the Parquet V3 GeoParquet (WKB) reader. +# The WKB reader took an element count straight off the wire and `reserve`d a std::vector of that +# many points before reading any coordinate bytes. The default container tracking is non-throwing, +# so a 9-byte WKB value claiming 100M points forced a ~1.6 GB allocation that bypassed +# max_memory_usage (the query failed later with CANNOT_READ_ALL_DATA, never MEMORY_LIMIT_EXCEEDED). +# The geometry containers now use AllocatorWithMemoryTracking, so the oversized reserve is charged +# to the memory tracker and rejected with MEMORY_LIMIT_EXCEEDED under a small limit. + +CUR_DIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd) +# shellcheck source=../shell_config.sh +. "$CUR_DIR"/../shell_config.sh + +WORK_DIR="${CLICKHOUSE_TMP}/${CLICKHOUSE_TEST_UNIQUE_NAME}" +rm -rf "$WORK_DIR" +mkdir -p "$WORK_DIR" +trap 'rm -rf "$WORK_DIR"' EXIT + +python3 - "$WORK_DIR" <<'PYEOF' +import sys, struct, json +import pyarrow as pa +import pyarrow.parquet as pq +work = sys.argv[1] +# WKB LineString header: little-endian, type=2 (LineString), num_points=100M, no coordinates. +wkb = struct.pack('&1) +if echo "$out" | grep -q "MEMORY_LIMIT_EXCEEDED"; then + echo "geo_bomb: memory limit enforced" +else + echo "geo_bomb: UNEXPECTED: $out" +fi diff --git a/tests/queries/0_stateless/04327_avro_string_length_alloc.reference b/tests/queries/0_stateless/04327_avro_string_length_alloc.reference new file mode 100644 index 000000000000..13cd1855ba1e --- /dev/null +++ b/tests/queries/0_stateless/04327_avro_string_length_alloc.reference @@ -0,0 +1 @@ +strbomb: rejected diff --git a/tests/queries/0_stateless/04327_avro_string_length_alloc.sh b/tests/queries/0_stateless/04327_avro_string_length_alloc.sh new file mode 100755 index 000000000000..c657d8dfa8bb --- /dev/null +++ b/tests/queries/0_stateless/04327_avro_string_length_alloc.sh @@ -0,0 +1,58 @@ +#!/usr/bin/env bash +# Tags: no-fasttest +# no-fasttest: Avro format is not available in the fast test build. + +# Regression test for an untracked-allocation DoS in the Avro reader. +# BinaryDecoder::decodeString / decodeBytes read a length prefix and resized a std::string/vector to +# that length BEFORE reading any bytes. A ~70-byte Avro file declaring a ~2 GB string length (with no +# data) forced a ~2 GB allocation that bypassed max_memory_usage (peak RSS ~2.9 GB; the query then +# failed with an Avro EOF error). The decoder now reads incrementally, so a truncated/over-declared +# value only allocates the bytes actually present (RSS stays bounded) and is rejected cleanly. + +CUR_DIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd) +# shellcheck source=../shell_config.sh +. "$CUR_DIR"/../shell_config.sh + +WORK_DIR="${CLICKHOUSE_TMP}/${CLICKHOUSE_TEST_UNIQUE_NAME}" +rm -rf "$WORK_DIR" +mkdir -p "$WORK_DIR" +trap 'rm -rf "$WORK_DIR"' EXIT + +# Hand-build a minimal Avro OCF whose single string value declares a ~2 GB length with no data bytes. +python3 - "$WORK_DIR" <<'PYEOF' +import json, sys +work = sys.argv[1] + +def vlong(n): # Avro zigzag varint long + u = ((n << 1) ^ (n >> 63)) & ((1 << 64) - 1) + out = bytearray() + while True: + b = u & 0x7f; u >>= 7 + out.append(b | 0x80) if u else out.append(b) + if not u: break + return bytes(out) + +def avstr(b): return vlong(len(b)) + b + +schema = json.dumps({"type": "record", "name": "r", + "fields": [{"name": "s", "type": "string"}]}).encode() +meta = (vlong(2) + avstr(b'avro.schema') + avstr(schema) + + avstr(b'avro.codec') + avstr(b'null') + vlong(0)) +sync = b'\x00' * 16 +header = b'Obj\x01' + meta + sync +payload = vlong(2_000_000_000) # string length prefix ~2 GB, no bytes follow +block = vlong(1) + vlong(len(payload)) + payload + sync +open(f"{work}/strbomb.avro", "wb").write(header + block) +PYEOF + +# The over-declared string must be rejected cleanly (and with bounded memory) instead of allocating +# ~2 GB up front. The data genuinely ends after the length prefix, so this surfaces as an Avro +# EOF / INCORRECT_DATA error. +out=$(${CLICKHOUSE_LOCAL} --input-format Avro -S "s String" \ + --query "SELECT s FROM table FORMAT Null SETTINGS max_memory_usage = 104857600" \ + < "${WORK_DIR}/strbomb.avro" 2>&1) +if echo "$out" | grep -qE "AVRO_EXCEPTION|EOF reached|INCORRECT_DATA|CANNOT_READ_ALL_DATA"; then + echo "strbomb: rejected" +else + echo "strbomb: UNEXPECTED: $out" +fi From 13b848943af7daec5d2f1b68765f4a5de168a642 Mon Sep 17 00:00:00 2001 From: robot-clickhouse Date: Thu, 18 Jun 2026 15:21:41 +0000 Subject: [PATCH 056/146] Backport #107606 to 26.3: Fix path traversal in replicated part fetch --- src/Storages/MergeTree/DataPartsExchange.cpp | 30 ++++++++++++++++---- 1 file changed, 25 insertions(+), 5 deletions(-) diff --git a/src/Storages/MergeTree/DataPartsExchange.cpp b/src/Storages/MergeTree/DataPartsExchange.cpp index 90a0188f9a44..b8173750f588 100644 --- a/src/Storages/MergeTree/DataPartsExchange.cpp +++ b/src/Storages/MergeTree/DataPartsExchange.cpp @@ -19,6 +19,7 @@ #include #include #include +#include #include #include #include @@ -101,6 +102,15 @@ struct ReplicatedFetchReadCallback } }; +/// Validate a projection name from an untrusted replica before it is used to build a path. +/// It becomes a single directory component (".proj"), so it must be non-empty and contain +/// no '/'. Standalone "." and ".." are safe here ("..proj"/"...proj" are single components). +bool isProjectionNameSafe(const std::string & projection_name) +{ + return !projection_name.empty() + && projection_name.find('/') == std::string::npos; +} + } @@ -688,14 +698,15 @@ void Fetcher::downloadBaseOrProjectionPartToDisk( readStringBinary(file_name, in); readBinary(file_size, in); - /// File must be inside "absolute_part_path" directory. - /// Otherwise malicious ClickHouse replica may force us to write to arbitrary path. - String absolute_file_path = fs::weakly_canonical(fs::path(data_part_storage->getRelativePath()) / file_name); - if (!startsWith(absolute_file_path, fs::weakly_canonical(data_part_storage->getRelativePath()).string())) + /// Guard against a malicious replica writing outside the part directory. + /// Runs for both the base part and projection parts. + const auto absolute_file_path = fs::weakly_canonical(fs::path(data_part_storage->getRelativePath()) / file_name); + if (!pathStartsWith(absolute_file_path, fs::path(data_part_storage->getRelativePath()))) throw Exception(ErrorCodes::INSECURE_PATH, "File path ({}) doesn't appear to be inside part path ({}). " "This may happen if we are trying to download part from malicious replica or logical error.", - absolute_file_path, data_part_storage->getRelativePath()); + absolute_file_path.string(), + data_part_storage->getRelativePath()); written_files.emplace_back(output_buffer_getter(*data_part_storage, file_name, file_size)); HashingWriteBuffer hashing_out(*written_files.back()); @@ -815,6 +826,15 @@ MergeTreeData::MutableDataPartPtr Fetcher::downloadPartToDisk( { String projection_name; readStringBinary(projection_name, in); + + /// Validate before getProjection()/createDirectories() so no attacker-named + /// directory is ever created from an untrusted replica's projection name. + if (!isProjectionNameSafe(projection_name)) + throw Exception(ErrorCodes::INSECURE_PATH, + "Projection name ({}) doesn't appear to be a valid name. " + "This may happen if we are trying to download part from malicious replica or logical error.", + projection_name); + MergeTreeData::DataPart::Checksums projection_checksum; auto projection_part_storage = part_storage_for_loading->getProjection(projection_name + ".proj"); From 2081c0c6adedb00cba4f1a17bf40f0358c76d704 Mon Sep 17 00:00:00 2001 From: robot-clickhouse Date: Thu, 18 Jun 2026 15:23:53 +0000 Subject: [PATCH 057/146] Backport #107580 to 26.3: Add missing bounds checks in ORC reader --- .../Impl/NativeORCBlockInputFormat.cpp | 37 +++++++++++++++++-- ...0_orc_fast_decoder_char_overflow.reference | 0 .../04340_orc_fast_decoder_char_overflow.sql | 23 ++++++++++++ 3 files changed, 57 insertions(+), 3 deletions(-) create mode 100644 tests/queries/0_stateless/04340_orc_fast_decoder_char_overflow.reference create mode 100644 tests/queries/0_stateless/04340_orc_fast_decoder_char_overflow.sql diff --git a/src/Processors/Formats/Impl/NativeORCBlockInputFormat.cpp b/src/Processors/Formats/Impl/NativeORCBlockInputFormat.cpp index bfb8a02683d7..9460888d9b0d 100644 --- a/src/Processors/Formats/Impl/NativeORCBlockInputFormat.cpp +++ b/src/Processors/Formats/Impl/NativeORCBlockInputFormat.cpp @@ -1334,7 +1334,16 @@ static ColumnWithTypeAndName readColumnWithEncodedStringOrFixedStringData( { const auto * buf = orc_dict.dictionaryBlob.data() + orc_dict.dictionaryOffset[i]; size_t buf_size = orc_dict.dictionaryOffset[i + 1] - orc_dict.dictionaryOffset[i]; + if (buf_size > n) + throw Exception( + ErrorCodes::INCORRECT_DATA, + "ORC dictionary entry {} has size {} that exceeds the declared FixedString length {}", + i, buf_size, n); memcpy(&column_chars[curr_offset], buf, buf_size); + /// resize_exact does not zero-initialize, so pad shorter entries to keep FixedString values + /// deterministic and to avoid leaking uninitialized heap memory. + if (buf_size < n) + memset(&column_chars[curr_offset + buf_size], 0, n - buf_size); curr_offset += n; } } @@ -1363,6 +1372,16 @@ static ColumnWithTypeAndName readColumnWithEncodedStringOrFixedStringData( auto index_column = dynamic_cast(dictionary_column.get())->uniqueInsertRangeFrom(*holder_column, 0, holder_column->size()); + auto check_index = [&](Int64 orc_index, size_t row) -> Int64 + { + if (orc_index < 0 || static_cast(orc_index) >= dict_size) + throw Exception( + ErrorCodes::INCORRECT_DATA, + "ORC dictionary index {} at row {} is out of range [0, {})", + orc_index, row, dict_size); + return orc_index; + }; + /// Fill index_column and wrap it with LowCardinality auto call_by_type = [&](auto index_type) -> MutableColumnPtr { @@ -1380,7 +1399,7 @@ static ColumnWithTypeAndName readColumnWithEncodedStringOrFixedStringData( for (size_t i = 0; i < rows; ++i) { /// First map row index to ORC dictionary index, then map ORC dictionary index to CH dictionary index - new_index_data[i] = index_data[orc_str_column.index[i]]; + new_index_data[i] = index_data[check_index(orc_str_column.index[i], i)]; } } else @@ -1389,7 +1408,7 @@ static ColumnWithTypeAndName readColumnWithEncodedStringOrFixedStringData( { /// Set index 0 if we meet null value. If dictionary_column is nullable, 0 represents null value. /// Otherwise 0 represents default string value, it is reasonable because null values are converted to default values when casting nullable column to non-nullable. - new_index_data[i] = orc_str_column.notNull[i] ? index_data[orc_str_column.index[i]] : 0; + new_index_data[i] = orc_str_column.notNull[i] ? index_data[check_index(orc_str_column.index[i], i)] : 0; } } @@ -1470,7 +1489,19 @@ readColumnWithFixedStringData(const orc::ColumnVectorBatch * orc_column, const o for (size_t i = 0; i < orc_str_column->numElements; ++i) { if (!orc_str_column->hasNulls || orc_str_column->notNull[i]) - column_chars.insert_assume_reserved(orc_str_column->data[i], orc_str_column->data[i] + orc_str_column->length[i]); + { + const Int64 length = orc_str_column->length[i]; + if (length < 0 || static_cast(length) > fixed_len) + throw Exception( + ErrorCodes::INCORRECT_DATA, + "ORC string value at row {} has size {} that doesn't fit into FixedString({})", + i, length, fixed_len); + + column_chars.insert_assume_reserved(orc_str_column->data[i], orc_str_column->data[i] + length); + /// Zero-pad shorter values to the fixed width to keep the FixedString layout consistent. + if (static_cast(length) < fixed_len) + column_chars.resize_fill(column_chars.size() + (fixed_len - static_cast(length))); + } else column_chars.resize_fill(column_chars.size() + fixed_len); } diff --git a/tests/queries/0_stateless/04340_orc_fast_decoder_char_overflow.reference b/tests/queries/0_stateless/04340_orc_fast_decoder_char_overflow.reference new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/tests/queries/0_stateless/04340_orc_fast_decoder_char_overflow.sql b/tests/queries/0_stateless/04340_orc_fast_decoder_char_overflow.sql new file mode 100644 index 000000000000..3460f19124b5 --- /dev/null +++ b/tests/queries/0_stateless/04340_orc_fast_decoder_char_overflow.sql @@ -0,0 +1,23 @@ +-- Tags: no-fasttest +-- no-fasttest: requires the ORC input format, which is not built in fasttest. + +-- Regression test for heap buffer overflows / out-of-bounds reads in the native ORC fast decoder +-- (`NativeORCBlockInputFormat`) when a crafted ORC file violates the invariants the decoder assumes +-- about CHAR-typed and dictionary-encoded columns. Before the fix these wrote/read past the bounds of +-- a heap buffer; now they are rejected with INCORRECT_DATA. The crafted files were produced by writing +-- a benign String column with a standard ORC writer and surgically patching the ORC metadata. + +SET input_format_orc_use_fast_decoder = 1; +SET input_format_orc_dictionary_as_low_cardinality = 1; + +-- A dictionary-encoded CHAR(1) column whose dictionary entry is 2 bytes long: the decoder allocated +-- dict_size * 1 bytes and memcpy'd the 2-byte entry into it. +SELECT * FROM format(ORC, unhex('4f52430a07120508800150000a180a0200001212088001220b0a026161120262621880045000407f55555555555555555555555555555555616162624201a00a060806100018090a0608061001181a0a060801100118120a060803100118040a060802100118031204080010001204080310021a03474d540a1b0a0508800150000a12088001220b0a026161120262621880045000080310751a0b0803102318192039288001220e080c1201011a0173200028003000220a081120002800300020013080013a0508800150003a12088001220b0a02616112026262188004500040904e48016205322e322e3208571000188080042202000c281d300682f403034f524317')) FORMAT Null; -- { serverError INCORRECT_DATA } + +-- A non-dictionary CHAR(1) column whose per-row value is longer than the declared width: the decoder +-- reserved numElements * 1 bytes and inserted the longer value without a capacity check. +SELECT * FROM format(ORC, unhex('4f52430a061204084050000a1c0a0300000012150840220f0a04303078781204363378781880045000c03f0400303078783031787830327878303378783034787830357878303678783037787830387878303978783130787831317878313278783133787831347878313578783136787831377878313878783139787832307878323178783232787832337878323478783235787832367878323778783238787832397878333078783331787833327878333378783334787833357878333678783337787833387878333978783430787834317878343278783433787834347878343578783436787834377878343878783439787835307878353178783532787835337878353478783535787835367878353778783538787835397878363078783631787836327878363378780a060806100018080a0608061001181e0a060802100118040a07080110011880021204080010001204080210001a03474d540a1d0a04084050000a150840220f0a04303078781204363378781880045000080310dc021a0b0803102618840220322840220e080c1201011a0173200028003000220a0811200028003000200130403a04084050003a150840220f0a0430307878120436337878188004500040904e48016205322e322e3208591000188080042202000c281f300682f403034f524317')) FORMAT Null; -- { serverError INCORRECT_DATA } + +-- A dictionary-encoded String column with a per-row dictionary index (255) past the end of the +-- dictionary (size 2): the decoder indexed the dictionary array without a bound check. +SELECT * FROM format(ORC, unhex('4f52430a061204080650000a140a020000120e080622080a0161120162180c50004e05ffffffffffff61624001c00a060806100018080a060806100118160a060801100118080a060803100118020a060802100118031204080010001204080310021a03474d540a160a04080650000a0e080622080a0161120162180c5000080310641a0a0803101e180d20392806220e080c1201011a01732000280030002208080720002800300030063a04080650003a0e080622080a0161120162180c500040904e48016205322e322e32084e1000188080042202000c2818300682f403034f524317')) FORMAT Null; -- { serverError INCORRECT_DATA } From 47a30c4a8970e8257e1994a505baacd03038bb5d Mon Sep 17 00:00:00 2001 From: robot-clickhouse Date: Fri, 19 Jun 2026 10:47:31 +0000 Subject: [PATCH 058/146] Backport #106349 to 26.3: Fix crash in constraint optimization with correlated subqueries --- src/Analyzer/Passes/ConvertQueryToCNFPass.cpp | 55 +++++++++++++++++-- ...t_subst_correlated_subquery_root.reference | 2 + ...straint_subst_correlated_subquery_root.sql | 29 ++++++++++ ...substitution_correlated_subquery.reference | 2 + ...const_substitution_correlated_subquery.sql | 21 +++++++ 5 files changed, 105 insertions(+), 4 deletions(-) create mode 100644 tests/queries/0_stateless/04299_constraint_subst_correlated_subquery_root.reference create mode 100644 tests/queries/0_stateless/04299_constraint_subst_correlated_subquery_root.sql create mode 100644 tests/queries/0_stateless/04305_constraint_const_substitution_correlated_subquery.reference create mode 100644 tests/queries/0_stateless/04305_constraint_const_substitution_correlated_subquery.sql diff --git a/src/Analyzer/Passes/ConvertQueryToCNFPass.cpp b/src/Analyzer/Passes/ConvertQueryToCNFPass.cpp index 2eac795974dc..9268b5f8f88e 100644 --- a/src/Analyzer/Passes/ConvertQueryToCNFPass.cpp +++ b/src/Analyzer/Passes/ConvertQueryToCNFPass.cpp @@ -30,6 +30,13 @@ namespace Setting namespace { +/// Constraint-based optimization is scoped to a single query node: each (sub)query is optimized +/// independently. Subqueries (`isSubqueryNodeType`) must therefore be treated as opaque boundaries, +/// both when one is a clause root (e.g. constraint reduction collapses `cond OR (subquery)` to just +/// the subquery) and when one is nested inside an expression (e.g. `exists((subquery))`). Crossing +/// the boundary would rewrite a subquery's correlated columns, which must stay plain ColumnNodes for +/// decorrelation. + std::optional tryConvertQueryToCNF(const QueryTreeNodePtr & node, const ContextPtr & context) { auto cnf_form = Analyzer::CNF::tryBuildCNF(node, context); @@ -158,6 +165,12 @@ bool checkIfAtomAlwaysFalseGraph(const Analyzer::CNFAtomicFormula & atom, const void replaceToConstants(QueryTreeNodePtr & term, const ComparisonGraph & graph) { + /// Do not cross into subqueries: replacing a correlated column with its constant equivalent + /// would corrupt the subquery's correlated_columns_list, which the planner expects to hold + /// ColumnNodes (see isSubqueryNodeType). + if (isSubqueryNodeType(term->getNodeType())) + return; + const auto equal_constant = graph.getEqualConst(term); if (equal_constant) { @@ -418,6 +431,11 @@ class ComponentCollectorVisitor : public ConstInDepthQueryTreeVisitorgetNodeType()); + } + void visitImpl(const QueryTreeNodePtr & node) { if (auto id = graph.getComponentId(node)) @@ -472,6 +490,11 @@ class SubstituteColumnVisitor : public InDepthQueryTreeVisitorgetNodeType()); + } + void visitImpl(QueryTreeNodePtr & node) { auto component_id_it = query_node_to_component.find(node); @@ -581,16 +604,24 @@ void substituteColumns(QueryNode & query_node, const QueryTreeNodes & table_expr auto run_for_all = [&](const auto function) { - function(query_node.getProjectionNode()); + /// The needChildVisit guards in the visitors only stop subqueries nested inside a larger + /// expression; a clause that is itself a subquery is the visit root, so guard it here. + const auto run_unless_subquery = [&](QueryTreeNodePtr & node) + { + if (!isSubqueryNodeType(node->getNodeType())) + function(node); + }; + + run_unless_subquery(query_node.getProjectionNode()); if (query_node.hasWhere()) - function(query_node.getWhere()); + run_unless_subquery(query_node.getWhere()); if (query_node.hasPrewhere()) - function(query_node.getPrewhere()); + run_unless_subquery(query_node.getPrewhere()); if (query_node.hasHaving()) - function(query_node.getHaving()); + run_unless_subquery(query_node.getHaving()); }; std::set components; @@ -704,6 +735,22 @@ void optimizeNode(QueryTreeNodePtr & node, const QueryTreeNodes & table_expressi optimizeWithConstraints(*cnf, table_expressions, context); auto new_node = cnf->toQueryTree(); + + /// Constraint reduction can collapse a filter into a standalone correlated subquery. For example, + /// with `CONSTRAINT c ASSUME (a <= b) AND (b <= c) AND (c <= d) AND (d <= a)` (so all columns are + /// equal), the filter + /// WHERE (b < d) OR (SELECT a < c) + /// reduces to + /// WHERE (SELECT a < c) + /// because `b < d` is always false. A standalone correlated subquery predicate is not always + /// decorrelated correctly today: the outer plan may not keep the captured columns (e.g. + /// `SELECT count() ... WHERE (SELECT a < c)` raises NOT_FOUND_COLUMN_IN_BLOCK, while `SELECT *` + /// works). Keep the original filter so the optimization never turns a working query into a + /// failing one. `new_node` is null when the filter reduces to a constant and is dropped, which is + /// fine to apply. + if (new_node && isCorrelatedQueryOrUnionNode(new_node)) + return; + node = std::move(new_node); } diff --git a/tests/queries/0_stateless/04299_constraint_subst_correlated_subquery_root.reference b/tests/queries/0_stateless/04299_constraint_subst_correlated_subquery_root.reference new file mode 100644 index 000000000000..6ed281c757a9 --- /dev/null +++ b/tests/queries/0_stateless/04299_constraint_subst_correlated_subquery_root.reference @@ -0,0 +1,2 @@ +1 +1 diff --git a/tests/queries/0_stateless/04299_constraint_subst_correlated_subquery_root.sql b/tests/queries/0_stateless/04299_constraint_subst_correlated_subquery_root.sql new file mode 100644 index 000000000000..cab011f5f783 --- /dev/null +++ b/tests/queries/0_stateless/04299_constraint_subst_correlated_subquery_root.sql @@ -0,0 +1,29 @@ +-- Constraint reduction can collapse a filter into a bare correlated subquery +-- (here `b < d` is always false given the constraint, leaving only the subquery). +-- A correlated subquery used as a whole predicate cannot be decorrelated, so the +-- optimization is skipped for such a filter and the query runs as written. + +DROP TABLE IF EXISTS t_constraint_corr_root; + +CREATE TABLE t_constraint_corr_root +( + a Nullable(String), + b String, + c LowCardinality(String), + d String, + CONSTRAINT c1 ASSUME (a <= b) AND (b <= c) AND (c <= d) AND (d <= a) +) +ENGINE = TinyLog; + +INSERT INTO t_constraint_corr_root VALUES ('1', '2', '3', '4'); + +SELECT count() FROM t_constraint_corr_root +WHERE (b < d) OR (SELECT a < c) +SETTINGS enable_analyzer = 1, convert_query_to_cnf = 1, optimize_substitute_columns = 1, optimize_using_constraints = 1; + +-- The same query with the optimization disabled must return the same value. +SELECT count() FROM t_constraint_corr_root +WHERE (b < d) OR (SELECT a < c) +SETTINGS enable_analyzer = 1, convert_query_to_cnf = 0, optimize_substitute_columns = 0, optimize_using_constraints = 0; + +DROP TABLE t_constraint_corr_root; diff --git a/tests/queries/0_stateless/04305_constraint_const_substitution_correlated_subquery.reference b/tests/queries/0_stateless/04305_constraint_const_substitution_correlated_subquery.reference new file mode 100644 index 000000000000..6ed281c757a9 --- /dev/null +++ b/tests/queries/0_stateless/04305_constraint_const_substitution_correlated_subquery.reference @@ -0,0 +1,2 @@ +1 +1 diff --git a/tests/queries/0_stateless/04305_constraint_const_substitution_correlated_subquery.sql b/tests/queries/0_stateless/04305_constraint_const_substitution_correlated_subquery.sql new file mode 100644 index 000000000000..402e55ece639 --- /dev/null +++ b/tests/queries/0_stateless/04305_constraint_const_substitution_correlated_subquery.sql @@ -0,0 +1,21 @@ +-- Constraint-based constant substitution must not rewrite a correlated column inside a +-- subquery into a constant: correlated columns must stay ColumnNodes for decorrelation. + +DROP TABLE IF EXISTS t_const_outer; +DROP TABLE IF EXISTS t_const_inner; + +CREATE TABLE t_const_outer (a String, CONSTRAINT c1 ASSUME a = 'x') ENGINE = TinyLog; +INSERT INTO t_const_outer VALUES ('x'); + +CREATE TABLE t_const_inner (a String) ENGINE = TinyLog; +INSERT INTO t_const_inner VALUES ('x'); + +SELECT count() FROM t_const_outer WHERE EXISTS (SELECT 1 FROM t_const_inner WHERE t_const_inner.a = t_const_outer.a) +SETTINGS enable_analyzer = 1, optimize_using_constraints = 1, convert_query_to_cnf = 1; + +-- The same query with the optimization disabled must return the same value. +SELECT count() FROM t_const_outer WHERE EXISTS (SELECT 1 FROM t_const_inner WHERE t_const_inner.a = t_const_outer.a) +SETTINGS enable_analyzer = 1, optimize_using_constraints = 0, convert_query_to_cnf = 0; + +DROP TABLE t_const_inner; +DROP TABLE t_const_outer; From 75d9b0c50209fdb7af26702700a691f03cd192f2 Mon Sep 17 00:00:00 2001 From: robot-clickhouse Date: Fri, 19 Jun 2026 14:16:32 +0000 Subject: [PATCH 059/146] Backport #107915 to 26.3: Fix position-dependent column-wise insert deduplication hash --- src/Columns/ColumnArray.cpp | 7 +- src/Columns/ColumnString.cpp | 7 +- src/Interpreters/InsertDeduplication.h | 1 + .../MergeTree/tests/gtest_async_inserts.cpp | 65 +++++++++++++++++++ .../test_migration_deduplication_hash/test.py | 41 ++++++++++++ 5 files changed, 119 insertions(+), 2 deletions(-) diff --git a/src/Columns/ColumnArray.cpp b/src/Columns/ColumnArray.cpp index ef0944568430..d572873dca0b 100644 --- a/src/Columns/ColumnArray.cpp +++ b/src/Columns/ColumnArray.cpp @@ -308,7 +308,12 @@ void ColumnArray::updateHashWithValueRange(size_t begin, size_t end, SipHash & h size_t nested_begin = offsetAt(begin); size_t nested_end = offsetAt(end); getData().updateHashWithValueRange(nested_begin, nested_end, hash); - hash.update(reinterpret_cast(&getOffsets()[begin]), (end - begin) * sizeof(getOffsets()[0])); + /// Relative offsets so equal data hashes equally regardless of position (insert deduplication). + for (size_t i = begin; i < end; ++i) + { + UInt64 relative_offset = getOffsets()[i] - nested_begin; + hash.update(relative_offset); + } } WeakHash32 ColumnArray::getWeakHash32() const diff --git a/src/Columns/ColumnString.cpp b/src/Columns/ColumnString.cpp index c1de5ab59cdd..867bb852ec41 100644 --- a/src/Columns/ColumnString.cpp +++ b/src/Columns/ColumnString.cpp @@ -732,7 +732,12 @@ void ColumnString::updateHashWithValueRange(size_t begin, size_t end, SipHash & size_t chars_begin = offsetAt(begin); size_t chars_end = offsetAt(end); hash.update(reinterpret_cast(&chars[chars_begin]), chars_end - chars_begin); - hash.update(reinterpret_cast(&offsets[begin]), (end - begin) * sizeof(offsets[0])); + /// Relative offsets so equal data hashes equally regardless of position (insert deduplication). + for (size_t i = begin; i < end; ++i) + { + UInt64 relative_offset = offsets[i] - chars_begin; + hash.update(relative_offset); + } } void ColumnString::updateHashFast(SipHash & hash) const diff --git a/src/Interpreters/InsertDeduplication.h b/src/Interpreters/InsertDeduplication.h index c61e782711b6..26cb79464cee 100644 --- a/src/Interpreters/InsertDeduplication.h +++ b/src/Interpreters/InsertDeduplication.h @@ -67,6 +67,7 @@ class DeduplicationInfo : public ChunkInfo friend class InsertDependenciesBuilder; /// src/Storages/MergeTree/tests/gtest_async_inserts.cpp friend std::vector testSelfDeduplicate(std::vector data, std::vector offsets, std::vector hashes); + friend std::vector testSelfDeduplicateStrings(std::vector data, std::vector offsets, std::vector hashes); public: using Ptr = std::shared_ptr; diff --git a/src/Storages/MergeTree/tests/gtest_async_inserts.cpp b/src/Storages/MergeTree/tests/gtest_async_inserts.cpp index 4b30fe48deb9..723c8ec08001 100644 --- a/src/Storages/MergeTree/tests/gtest_async_inserts.cpp +++ b/src/Storages/MergeTree/tests/gtest_async_inserts.cpp @@ -2,6 +2,7 @@ #include #include #include +#include #include #include @@ -70,4 +71,68 @@ TEST(AsyncInsertsTest, testSelfDeduplicate) test_impl({1,2,1,2,1,2,1,2,1,2},{2,4,6,8,10},{"a","a","a","a","a"},{1,2}); } + +/// Self-deduplication must be position-invariant for variable-length columns. With the unified hash +/// (NEW_UNIFIED_HASHES) the data hash is computed column-wise over a row range; if it folded in +/// absolute string/array offsets, two equal rows located at different offsets would get different +/// block ids and fail to deduplicate (e.g. repeated rows combined into one async insert). +std::vector testSelfDeduplicateStrings(std::vector data, std::vector offsets, std::vector hashes) +{ + MutableColumnPtr column = DataTypeString().createColumn(); + for (const auto & datum : data) + { + column->insert(datum); + } + Block block({ColumnWithTypeAndName(std::move(column), std::make_shared(), "a")}); + + auto deduplication_info = DeduplicationInfo::create(true, InsertDeduplicationVersions::NEW_UNIFIED_HASHES); + deduplication_info->setRootViewID({}); + deduplication_info->disabled = false; // there is no insert dependencies instance in this test + deduplication_info->updateOriginalBlock(Chunk(block.getColumns(), block.rows()), std::make_shared(block.cloneEmpty())); + + chassert(offsets.size() == hashes.size()); + chassert(!offsets.empty()); + + deduplication_info->setUserToken(hashes[0], offsets[0]); + + for (size_t i = 1; i < offsets.size(); ++i) + deduplication_info->setUserToken(hashes[i], offsets[i] - offsets[i-1]); + + chassert(offsets.size() == deduplication_info->getCount()); + chassert(offsets.back() == deduplication_info->getRows()); + + auto filtered = deduplication_info->filterImpl(deduplication_info->filterSelf("all")); + + /// Nothing was deduplicated — all rows survive in their original order. + if (filtered.removed_rows == 0 || !filtered.filtered_block) + return data; + + ColumnPtr col = filtered.filtered_block->getColumns()[0]; + + std::vector result; + result.reserve(col->size()); + + for (size_t i = 0; i < col->size(); i++) + { + result.push_back(String(col->getDataAt(i))); + } + + return result; +} + +TEST(AsyncInsertsTest, testSelfDeduplicateStrings) +{ + auto test_impl = [](std::vector data, std::vector offsets, std::vector hashes, std::vector answer) + { + auto result = testSelfDeduplicateStrings(data, offsets, hashes); + ASSERT_EQ(answer, result); + }; + /// Two equal single-row blocks with no user token must collapse to one row. + test_impl({"one line","one line"},{1,2},{"",""},{"one line"}); + /// Equal multi-row blocks with no user token must collapse, keeping the first occurrence. + test_impl({"a","bb","a","bb","ccc"},{2,4,5},{"","",""},{"a","bb","ccc"}); + /// Distinct blocks must survive (no false deduplication from relative offsets). + test_impl({"ab","c","a","bc"},{2,4},{"",""},{"ab","c","a","bc"}); +} + } diff --git a/tests/integration/test_migration_deduplication_hash/test.py b/tests/integration/test_migration_deduplication_hash/test.py index 77d6aa1260b1..726287953075 100644 --- a/tests/integration/test_migration_deduplication_hash/test.py +++ b/tests/integration/test_migration_deduplication_hash/test.py @@ -270,3 +270,44 @@ def test_sync_async_deduplicated_with_2512(cluster, insert_type): for node in nodes: node.query("SYSTEM SYNC REPLICA test_sync_async_deduplicated_with_2512") + + +def test_new_unified_hash_self_deduplication_variable_length(cluster): + # Regression test for a deduplication hash bug with new_unified_hash. + # ColumnString/ColumnArray::updateHashWithValueRange hashed absolute offsets, so equal rows + # located at different offsets within one async-insert flush produced different unified + # deduplication ids and were not self-deduplicated. Fixed-width columns (e.g. UInt32) are + # position-invariant and did not expose the bug, hence the String/Array columns here. + node_new = cluster.instances["node_new"] + + create_table_query = \ +""" + DROP TABLE IF EXISTS test_unified_self_dedup; + + CREATE TABLE test_unified_self_dedup (key UInt32, s String, a Array(UInt32)) + ENGINE=ReplicatedMergeTree('/clickhouse/tables/test_unified_self_dedup/', '{replica}') + ORDER BY key +""" + + drop_table_query = "DROP TABLE IF EXISTS test_unified_self_dedup SYNC" + + # Keep the busy timeout high and do not wait for the flush, so both inserts accumulate in the + # queue and are combined into a single flush (one block with two offsets) by SYSTEM FLUSH. + async_settings = ( + "async_insert=1, wait_for_async_insert=0, async_insert_use_adaptive_busy_timeout=0, " + "async_insert_busy_timeout_min_ms=10000, async_insert_busy_timeout_max_ms=50000, " + "deduplicate_insert='enable'" + ) + + with with_tables( + nodes=[node_new], + create_query=create_table_query, + drop_query=drop_table_query, + ): + node_new.query(f"INSERT INTO test_unified_self_dedup SETTINGS {async_settings} VALUES (1, 'one line', [10, 20])") + node_new.query(f"INSERT INTO test_unified_self_dedup SETTINGS {async_settings} VALUES (1, 'one line', [10, 20])") + node_new.query("SYSTEM FLUSH ASYNC INSERT QUEUE") + + # Two identical inserts combined in one flush must self-deduplicate to a single row. + result = node_new.query("SELECT key, s, a FROM test_unified_self_dedup ORDER BY key") + assert result == "1\tone line\t[10,20]\n" From fb63cfd2baf75c978c354f543102056ee31ed786 Mon Sep 17 00:00:00 2001 From: robot-clickhouse Date: Fri, 19 Jun 2026 14:17:15 +0000 Subject: [PATCH 060/146] Backport #107852 to 26.3: Move per-query_id async insert logging to test level --- src/Interpreters/AsynchronousInsertQueue.cpp | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/Interpreters/AsynchronousInsertQueue.cpp b/src/Interpreters/AsynchronousInsertQueue.cpp index a1dfcf44b907..5a6e3914b801 100644 --- a/src/Interpreters/AsynchronousInsertQueue.cpp +++ b/src/Interpreters/AsynchronousInsertQueue.cpp @@ -571,7 +571,9 @@ AsynchronousInsertQueue::PushResult AsynchronousInsertQueue::pushDataChunk(ASTPt data->entries.emplace_back(entry); progress_future = entry->getFuture(); - LOG_TRACE(log, "Have {} pending inserts in shard {} with total {} bytes of data for the async insert queries '{}'", + LOG_TRACE(log, "Have {} pending inserts in shard {} with total {} bytes of data", + data->entries.size(), size_t(shard_num), data->size_in_bytes); + LOG_TEST(log, "Have {} pending inserts in shard {} with total {} bytes of data for the async insert queries '{}'", data->entries.size(), size_t(shard_num), data->size_in_bytes, fmt::join(getInsertQueryIds(*data), ", ")); bool has_enough_bytes = data->size_in_bytes >= (*key.settings)[Setting::async_insert_max_data_size]; @@ -1006,7 +1008,8 @@ try else query_scope = QueryScope::create(insert_context); - LOG_DEBUG(log, "Processing batch insert for the async inserts '{}'", fmt::join(getInsertQueryIds(*data), ", ")); + LOG_TRACE(log, "Processing batch insert of {} async inserts with {} bytes of data", data->entries.size(), data->size_in_bytes); + LOG_TEST(log, "Processing batch insert for the async inserts '{}'", fmt::join(getInsertQueryIds(*data), ", ")); String query_for_logging = serializeQuery(*key.query, insert_context->getSettingsRef()[Setting::log_queries_cut_to_length]); UInt64 normalized_query_hash = normalizedQueryHash(query_for_logging, false); From e52a2a3daa27e7596df9004c5e4741be425e2818 Mon Sep 17 00:00:00 2001 From: robot-clickhouse Date: Mon, 22 Jun 2026 08:30:22 +0000 Subject: [PATCH 061/146] Backport #107672 to 26.3: Fix quadratic access entity cache recomputation during refresh (may lead to login hungs) --- src/Access/AccessChangesNotifier.cpp | 40 +++++++ src/Access/AccessChangesNotifier.h | 9 ++ src/Access/AccessControl.cpp | 5 + src/Access/AccessControl.h | 5 + src/Access/QuotaCache.cpp | 35 +++++- src/Access/QuotaCache.h | 4 + src/Access/RoleCache.cpp | 50 +++++++-- src/Access/RoleCache.h | 6 ++ src/Access/RowPolicyCache.cpp | 34 +++++- src/Access/RowPolicyCache.h | 4 + src/Access/SettingsProfilesCache.cpp | 36 ++++++- src/Access/SettingsProfilesCache.h | 4 + .../tests/gtest_access_changes_notifier.cpp | 56 ++++++++++ src/Common/ProfileEvents.cpp | 8 ++ .../__init__.py | 0 .../configs/config.xml | 22 ++++ .../test.py | 102 ++++++++++++++++++ 17 files changed, 410 insertions(+), 10 deletions(-) create mode 100644 src/Access/tests/gtest_access_changes_notifier.cpp create mode 100644 tests/integration/test_access_cache_recompute_coalescing/__init__.py create mode 100644 tests/integration/test_access_cache_recompute_coalescing/configs/config.xml create mode 100644 tests/integration/test_access_cache_recompute_coalescing/test.py diff --git a/src/Access/AccessChangesNotifier.cpp b/src/Access/AccessChangesNotifier.cpp index c933fafa5f29..ada0b0465225 100644 --- a/src/Access/AccessChangesNotifier.cpp +++ b/src/Access/AccessChangesNotifier.cpp @@ -83,17 +83,33 @@ scope_guard AccessChangesNotifier::subscribeForChanges(const std::vector & return subscriptions; } +scope_guard AccessChangesNotifier::subscribeForBatchFinished(const OnBatchFinishedHandler & handler) +{ + std::lock_guard lock{handlers->mutex}; + auto & list = handlers->on_batch_finished; + list.push_back(handler); + auto handler_it = std::prev(list.end()); + + return [my_handlers = handlers, handler_it] + { + std::lock_guard lock2{my_handlers->mutex}; + my_handlers->on_batch_finished.erase(handler_it); + }; +} + void AccessChangesNotifier::sendNotifications() { /// Only one thread can send notification at any time. std::lock_guard sending_notifications_lock{sending_notifications}; + bool sent_any = false; std::unique_lock queue_lock{queue_mutex}; while (!queue.empty()) { auto event = std::move(queue.front()); queue.pop(); queue_lock.unlock(); + sent_any = true; std::vector current_handlers; { @@ -118,6 +134,30 @@ void AccessChangesNotifier::sendNotifications() queue_lock.lock(); } + queue_lock.unlock(); + + if (!sent_any) + return; + + /// Queue drained (e.g. a full refresh); run the coalesced per-batch work. Copied out so handlers + /// run without `handlers->mutex` held. + std::vector batch_finished_handlers; + { + std::lock_guard handlers_lock{handlers->mutex}; + boost::range::copy(handlers->on_batch_finished, std::back_inserter(batch_finished_handlers)); + } + + for (const auto & handler : batch_finished_handlers) + { + try + { + handler(); + } + catch (...) + { + tryLogCurrentException(__PRETTY_FUNCTION__); + } + } } } diff --git a/src/Access/AccessChangesNotifier.h b/src/Access/AccessChangesNotifier.h index 7ccfe0fcbdf2..250d58a7ec19 100644 --- a/src/Access/AccessChangesNotifier.h +++ b/src/Access/AccessChangesNotifier.h @@ -36,6 +36,14 @@ class AccessChangesNotifier scope_guard subscribeForChanges(const UUID & id, const OnChangedHandler & handler); scope_guard subscribeForChanges(const std::vector & ids, const OnChangedHandler & handler); + using OnBatchFinishedHandler = std::function; + + /// Subscribes for the end of a notification batch: the handler is called once after sendNotifications() + /// has dispatched all the per-entity notifications queued so far. Lets subscribers coalesce expensive + /// per-entity recomputations into a single one per batch (e.g. a full refresh delivers one notification + /// per entity, but the derived state only needs to be rebuilt once). + scope_guard subscribeForBatchFinished(const OnBatchFinishedHandler & handler); + /// Called by access storages after a new access entity has been added. void onEntityAdded(const UUID & id, const AccessEntityPtr & new_entity); @@ -54,6 +62,7 @@ class AccessChangesNotifier { std::unordered_map> by_id; std::list by_type[static_cast(AccessEntityType::MAX)]; + std::list on_batch_finished; std::mutex mutex; }; diff --git a/src/Access/AccessControl.cpp b/src/Access/AccessControl.cpp index d7b8f94108ae..d8c8e5720df0 100644 --- a/src/Access/AccessControl.cpp +++ b/src/Access/AccessControl.cpp @@ -559,6 +559,11 @@ scope_guard AccessControl::subscribeForChanges(const std::vector & ids, co return changes_notifier->subscribeForChanges(ids, handler); } +scope_guard AccessControl::subscribeForBatchFinished(const OnBatchFinishedHandler & handler) const +{ + return changes_notifier->subscribeForBatchFinished(handler); +} + bool AccessControl::insertImpl(const UUID & id, const AccessEntityPtr & entity, bool replace_if_exists, bool throw_if_exists, UUID * conflicting_id) { if (MultipleAccessStorage::insertImpl(id, entity, replace_if_exists, throw_if_exists, conflicting_id)) diff --git a/src/Access/AccessControl.h b/src/Access/AccessControl.h index 2fa35306d915..58a1b8a8c9af 100644 --- a/src/Access/AccessControl.h +++ b/src/Access/AccessControl.h @@ -128,6 +128,11 @@ class AccessControl : public MultipleAccessStorage scope_guard subscribeForChanges(const UUID & id, const OnChangedHandler & handler) const; scope_guard subscribeForChanges(const std::vector & ids, const OnChangedHandler & handler) const; + using OnBatchFinishedHandler = std::function; + + /// Subscribes for the end of a notification batch (see AccessChangesNotifier::subscribeForBatchFinished). + scope_guard subscribeForBatchFinished(const OnBatchFinishedHandler & handler) const; + AuthResult authenticate(const Credentials & credentials, const Poco::Net::IPAddress & address, const ClientInfo & client_info) const; /// Makes a backup of access entities. diff --git a/src/Access/QuotaCache.cpp b/src/Access/QuotaCache.cpp index 9aa872fa9940..d5a019639a2f 100644 --- a/src/Access/QuotaCache.cpp +++ b/src/Access/QuotaCache.cpp @@ -4,6 +4,10 @@ #include #include #include +#include +#include +#include +#include #include #include #include @@ -12,6 +16,12 @@ #include +namespace ProfileEvents +{ + extern const Event QuotaCacheRecalculations; + extern const Event QuotaCacheRecalculationMicroseconds; +} + namespace DB { namespace ErrorCodes @@ -221,6 +231,8 @@ void QuotaCache::ensureAllQuotasRead() quotaRemoved(id); }); + batch_subscription = access_control.subscribeForBatchFinished([this] { chooseQuotaToConsumeIfNeeded(); }); + for (const UUID & quota_id : access_control.findAll()) { auto quota = access_control.tryRead(quota_id); @@ -246,7 +258,7 @@ void QuotaCache::quotaAddedOrChanged(const UUID & quota_id, const std::shared_pt auto & info = it->second; info.setQuota(new_quota, quota_id); - chooseQuotaToConsume(); + need_choose_quota = true; } @@ -254,7 +266,18 @@ void QuotaCache::quotaRemoved(const UUID & quota_id) { std::lock_guard lock{mutex}; all_quotas.erase(quota_id); + need_choose_quota = true; +} + + +void QuotaCache::chooseQuotaToConsumeIfNeeded() +{ + std::lock_guard lock{mutex}; + if (!need_choose_quota) + return; + /// Clear the flag only after a successful rebuild, so a throwing recompute is retried next batch. chooseQuotaToConsume(); + need_choose_quota = false; } @@ -262,6 +285,8 @@ void QuotaCache::chooseQuotaToConsume() { /// `mutex` is already locked. + ProfileEvents::increment(ProfileEvents::QuotaCacheRecalculations); + Stopwatch watch; for (auto i = enabled_quotas.begin(), e = enabled_quotas.end(); i != e;) { auto elem = i->second.lock(); @@ -273,6 +298,14 @@ void QuotaCache::chooseQuotaToConsume() ++i; } } + + const auto elapsed_ms = watch.elapsedMilliseconds(); + ProfileEvents::increment(ProfileEvents::QuotaCacheRecalculationMicroseconds, watch.elapsedMicroseconds()); + /// O(enabled sets * quotas), under `mutex` that the ContextAccess build path also takes. + if (elapsed_ms >= 1000) + LOG_WARNING(getLogger("QuotaCache"), "Re-chose quotas for {} enabled set(s) over {} quotas in {} ms", enabled_quotas.size(), all_quotas.size(), elapsed_ms); + else + LOG_DEBUG(getLogger("QuotaCache"), "Re-chose quotas for {} enabled set(s) over {} quotas in {} ms", enabled_quotas.size(), all_quotas.size(), elapsed_ms); } void QuotaCache::chooseQuotaToConsumeFor(EnabledQuota & enabled, bool throw_if_client_key_empty) diff --git a/src/Access/QuotaCache.h b/src/Access/QuotaCache.h index a11d6a4e7e65..7a47eb539e7c 100644 --- a/src/Access/QuotaCache.h +++ b/src/Access/QuotaCache.h @@ -57,13 +57,17 @@ class QuotaCache void quotaAddedOrChanged(const UUID & quota_id, const std::shared_ptr & new_quota); void quotaRemoved(const UUID & quota_id); void chooseQuotaToConsume(); + void chooseQuotaToConsumeIfNeeded(); void chooseQuotaToConsumeFor(EnabledQuota & enabled_quota, bool throw_if_client_key_empty); const AccessControl & access_control; mutable std::mutex mutex; std::unordered_map all_quotas; bool all_quotas_read = false; + /// Set by the per-entity handler; the rebuild is coalesced to once per notification batch. + bool need_choose_quota = false; scope_guard subscription; + scope_guard batch_subscription; std::map> enabled_quotas; }; } diff --git a/src/Access/RoleCache.cpp b/src/Access/RoleCache.cpp index 257811fce0ed..b30a4b57e409 100644 --- a/src/Access/RoleCache.cpp +++ b/src/Access/RoleCache.cpp @@ -4,8 +4,18 @@ #include #include #include +#include +#include +#include +#include +namespace ProfileEvents +{ + extern const Event RoleCacheRecalculations; + extern const Event RoleCacheRecalculationMicroseconds; +} + namespace DB { namespace @@ -79,6 +89,7 @@ std::shared_ptr RoleCache::getEnabledRoles(boost::container::flat_set roles, boost::container::flat_set roles_with_admin_option) { std::lock_guard lock{mutex}; + EnabledRoles::Params params; params.current_roles = std::move(roles); params.current_roles_with_admin_option = std::move(roles_with_admin_option); @@ -102,6 +113,8 @@ void RoleCache::collectEnabledRoles(scope_guard * notifications) { /// `mutex` is already locked. + ProfileEvents::increment(ProfileEvents::RoleCacheRecalculations); + Stopwatch watch; for (auto i = enabled_roles_by_params.begin(), e = enabled_roles_by_params.end(); i != e;) { auto & item = i->second; @@ -115,6 +128,14 @@ void RoleCache::collectEnabledRoles(scope_guard * notifications) i = enabled_roles_by_params.erase(i); } } + + const auto elapsed_ms = watch.elapsedMilliseconds(); + ProfileEvents::increment(ProfileEvents::RoleCacheRecalculationMicroseconds, watch.elapsedMicroseconds()); + /// O(enabled sets * roles), under `mutex` that the ContextAccess build path also takes. + if (elapsed_ms >= 1000) + LOG_WARNING(getLogger("RoleCache"), "Recalculated enabled roles for {} enabled set(s) in {} ms", enabled_roles_by_params.size(), elapsed_ms); + else + LOG_DEBUG(getLogger("RoleCache"), "Recalculated enabled roles for {} enabled set(s) in {} ms", enabled_roles_by_params.size(), elapsed_ms); } @@ -152,6 +173,13 @@ RolePtr RoleCache::getRole(const UUID & role_id, SubscriptionsOnRoles & subscrip { /// `mutex` is already locked. + /// Lazy (not in the ctor): `changes_notifier` is constructed after the caches in `AccessControl`. + if (!batch_subscribed) + { + batch_subscription = access_control.subscribeForBatchFinished([this] { collectEnabledRolesIfNeeded(); }); + batch_subscribed = true; + } + auto role_from_cache = cache.get(role_id); if (role_from_cache) { @@ -186,9 +214,6 @@ RolePtr RoleCache::getRole(const UUID & role_id, SubscriptionsOnRoles & subscrip void RoleCache::roleChanged(const UUID & role_id, const RolePtr & changed_role) { - /// Declared before `lock` to send notifications after the mutex will be unlocked. - scope_guard notifications; - std::lock_guard lock{mutex}; auto role_from_cache = cache.get(role_id); @@ -200,22 +225,33 @@ void RoleCache::roleChanged(const UUID & role_id, const RolePtr & changed_role) } /// An enabled role for some users has been changed, we need to recalculate the access rights. - collectEnabledRoles(¬ifications); /// collectEnabledRoles() must be called with the `mutex` locked. + need_collect_enabled_roles = true; } void RoleCache::roleRemoved(const UUID & role_id) { - /// Declared before `lock` to send notifications after the mutex will be unlocked. - scope_guard notifications; - std::lock_guard lock{mutex}; /// If a cache entry with the role has expired already, that remove() will do nothing. cache.remove(role_id); /// An enabled role for some users has been removed, we need to recalculate the access rights. + need_collect_enabled_roles = true; +} + + +void RoleCache::collectEnabledRolesIfNeeded() +{ + /// Declared before `lock` to send notifications after the mutex will be unlocked. + scope_guard notifications; + + std::lock_guard lock{mutex}; + if (!need_collect_enabled_roles) + return; + /// Clear the flag only after a successful rebuild, so a throwing recompute is retried next batch. collectEnabledRoles(¬ifications); /// collectEnabledRoles() must be called with the `mutex` locked. + need_collect_enabled_roles = false; } } diff --git a/src/Access/RoleCache.h b/src/Access/RoleCache.h index b707a05346fa..9b06f0a2b85f 100644 --- a/src/Access/RoleCache.h +++ b/src/Access/RoleCache.h @@ -31,11 +31,17 @@ class RoleCache void collectEnabledRoles(scope_guard * notifications) TSA_REQUIRES(mutex); void collectEnabledRoles(EnabledRoles & enabled_roles, SubscriptionsOnRoles & subscriptions_on_roles, scope_guard * notifications) TSA_REQUIRES(mutex); + void collectEnabledRolesIfNeeded(); RolePtr getRole(const UUID & role_id, SubscriptionsOnRoles & subscriptions_on_roles) TSA_REQUIRES(mutex); void roleChanged(const UUID & role_id, const RolePtr & changed_role); void roleRemoved(const UUID & role_id); const AccessControl & access_control; + scope_guard batch_subscription; + bool batch_subscribed TSA_GUARDED_BY(mutex) = false; + + /// Set by the per-entity handler; the recalculation is coalesced to once per notification batch. + bool need_collect_enabled_roles TSA_GUARDED_BY(mutex) = false; Poco::AccessExpireCache>> TSA_GUARDED_BY(mutex) cache; diff --git a/src/Access/RowPolicyCache.cpp b/src/Access/RowPolicyCache.cpp index ace9745681f6..6cd0aa18b0e8 100644 --- a/src/Access/RowPolicyCache.cpp +++ b/src/Access/RowPolicyCache.cpp @@ -7,12 +7,21 @@ #include #include #include +#include +#include +#include #include #include #include #include +namespace ProfileEvents +{ + extern const Event RowPolicyCacheRecalculations; + extern const Event RowPolicyCacheRecalculationMicroseconds; +} + namespace DB { namespace @@ -148,6 +157,8 @@ void RowPolicyCache::ensureAllRowPoliciesRead() rowPolicyRemoved(id); }); + batch_subscription = access_control.subscribeForBatchFinished([this] { mixFiltersIfNeeded(); }); + for (const UUID & id : access_control.findAll()) { auto policy = access_control.tryRead(id); @@ -175,7 +186,7 @@ void RowPolicyCache::rowPolicyAddedOrChanged(const UUID & policy_id, const RowPo auto & info = it->second; info.setPolicy(new_policy); - mixFilters(); + need_mix_filters = true; } @@ -183,13 +194,26 @@ void RowPolicyCache::rowPolicyRemoved(const UUID & policy_id) { std::lock_guard lock{mutex}; all_policies.erase(policy_id); + need_mix_filters = true; +} + + +void RowPolicyCache::mixFiltersIfNeeded() +{ + std::lock_guard lock{mutex}; + if (!need_mix_filters) + return; + /// Clear the flag only after a successful rebuild, so a throwing mixFilters() is retried next batch. mixFilters(); + need_mix_filters = false; } void RowPolicyCache::mixFilters() { /// `mutex` is already locked. + ProfileEvents::increment(ProfileEvents::RowPolicyCacheRecalculations); + Stopwatch watch; for (auto i = enabled_row_policies.begin(), e = enabled_row_policies.end(); i != e;) { auto elem = i->second.lock(); @@ -201,6 +225,14 @@ void RowPolicyCache::mixFilters() ++i; } } + + const auto elapsed_ms = watch.elapsedMilliseconds(); + ProfileEvents::increment(ProfileEvents::RowPolicyCacheRecalculationMicroseconds, watch.elapsedMicroseconds()); + /// O(enabled sets * policies), under `mutex` that the ContextAccess build path also takes. + if (elapsed_ms >= 1000) + LOG_WARNING(getLogger("RowPolicyCache"), "Re-mixed row policy filters for {} enabled set(s) over {} policies in {} ms", enabled_row_policies.size(), all_policies.size(), elapsed_ms); + else + LOG_DEBUG(getLogger("RowPolicyCache"), "Re-mixed row policy filters for {} enabled set(s) over {} policies in {} ms", enabled_row_policies.size(), all_policies.size(), elapsed_ms); } diff --git a/src/Access/RowPolicyCache.h b/src/Access/RowPolicyCache.h index df2634165099..d68b9076b478 100644 --- a/src/Access/RowPolicyCache.h +++ b/src/Access/RowPolicyCache.h @@ -39,13 +39,17 @@ class RowPolicyCache void ensureAllRowPoliciesRead(); void rowPolicyAddedOrChanged(const UUID & policy_id, const RowPolicyPtr & new_policy); void rowPolicyRemoved(const UUID & policy_id); + void mixFiltersIfNeeded(); void mixFilters(); void mixFiltersFor(EnabledRowPolicies & enabled); const AccessControl & access_control; std::unordered_map all_policies; bool all_policies_read = false; + /// Set by the per-entity handler; the rebuild is coalesced to once per notification batch. + bool need_mix_filters TSA_GUARDED_BY(mutex) = false; scope_guard subscription; + scope_guard batch_subscription; std::map> enabled_row_policies; std::mutex mutex; }; diff --git a/src/Access/SettingsProfilesCache.cpp b/src/Access/SettingsProfilesCache.cpp index 4fb75e75e5a2..230cf3ac876d 100644 --- a/src/Access/SettingsProfilesCache.cpp +++ b/src/Access/SettingsProfilesCache.cpp @@ -2,9 +2,20 @@ #include #include #include +#include +#include +#include +#include #include +namespace ProfileEvents +{ + /// NOLINT: not settings; the names contain "Settings" so check-settings-style mistakes them for setting externs. + extern const Event SettingsProfileCacheRecalculations; // NOLINT + extern const Event SettingsProfileCacheRecalculationMicroseconds; // NOLINT +} + namespace DB { namespace ErrorCodes @@ -34,6 +45,8 @@ void SettingsProfilesCache::ensureAllProfilesRead() profileRemoved(id); }); + batch_subscription = access_control.subscribeForBatchFinished([this] { mergeSettingsAndConstraintsIfNeeded(); }); + for (const UUID & id : access_control.findAll()) { auto profile = access_control.tryRead(id); @@ -64,7 +77,7 @@ void SettingsProfilesCache::profileAddedOrChanged(const UUID & profile_id, const profiles_by_name[new_profile->getName()] = profile_id; } profile_infos_cache.clear(); - mergeSettingsAndConstraints(); + need_merge_settings_and_constraints = true; } @@ -77,7 +90,18 @@ void SettingsProfilesCache::profileRemoved(const UUID & profile_id) profiles_by_name.erase(it->second->getName()); all_profiles.erase(it); profile_infos_cache.clear(); + need_merge_settings_and_constraints = true; +} + + +void SettingsProfilesCache::mergeSettingsAndConstraintsIfNeeded() +{ + std::lock_guard lock{mutex}; + if (!need_merge_settings_and_constraints) + return; + /// Clear the flag only after a successful rebuild, so a throwing recompute is retried next batch. mergeSettingsAndConstraints(); + need_merge_settings_and_constraints = false; } @@ -103,6 +127,8 @@ void SettingsProfilesCache::setDefaultProfileName(const String & default_profile void SettingsProfilesCache::mergeSettingsAndConstraints() { /// `mutex` is already locked. + ProfileEvents::increment(ProfileEvents::SettingsProfileCacheRecalculations); + Stopwatch watch; for (auto i = enabled_settings.begin(), e = enabled_settings.end(); i != e;) { auto enabled = i->second.lock(); @@ -114,6 +140,14 @@ void SettingsProfilesCache::mergeSettingsAndConstraints() ++i; } } + + const auto elapsed_ms = watch.elapsedMilliseconds(); + ProfileEvents::increment(ProfileEvents::SettingsProfileCacheRecalculationMicroseconds, watch.elapsedMicroseconds()); + /// O(enabled sets * profiles), under `mutex` that the ContextAccess build path also takes. + if (elapsed_ms >= 1000) + LOG_WARNING(getLogger("SettingsProfilesCache"), "Re-merged settings and constraints for {} enabled set(s) over {} profiles in {} ms", enabled_settings.size(), all_profiles.size(), elapsed_ms); + else + LOG_DEBUG(getLogger("SettingsProfilesCache"), "Re-merged settings and constraints for {} enabled set(s) over {} profiles in {} ms", enabled_settings.size(), all_profiles.size(), elapsed_ms); } diff --git a/src/Access/SettingsProfilesCache.h b/src/Access/SettingsProfilesCache.h index afc3c3e13a5c..a238774e040e 100644 --- a/src/Access/SettingsProfilesCache.h +++ b/src/Access/SettingsProfilesCache.h @@ -36,6 +36,7 @@ class SettingsProfilesCache void profileAddedOrChanged(const UUID & profile_id, const SettingsProfilePtr & new_profile); void profileRemoved(const UUID & profile_id); void mergeSettingsAndConstraints(); + void mergeSettingsAndConstraintsIfNeeded(); void mergeSettingsAndConstraintsFor(EnabledSettings & enabled) const; void substituteProfiles(SettingsProfileElements & elements, @@ -47,7 +48,10 @@ class SettingsProfilesCache std::unordered_map all_profiles; std::unordered_map profiles_by_name; bool all_profiles_read = false; + /// Set by the per-entity handler; the rebuild is coalesced to once per notification batch. + bool need_merge_settings_and_constraints = false; scope_guard subscription; + scope_guard batch_subscription; std::map> enabled_settings; std::optional default_profile_id; Poco::LRUCache> profile_infos_cache; diff --git a/src/Access/tests/gtest_access_changes_notifier.cpp b/src/Access/tests/gtest_access_changes_notifier.cpp new file mode 100644 index 000000000000..37e4625df821 --- /dev/null +++ b/src/Access/tests/gtest_access_changes_notifier.cpp @@ -0,0 +1,56 @@ +#include + +#include +#include +#include + +using namespace DB; + +/// The per-entity caches (RowPolicyCache, RoleCache, SettingsProfilesCache, QuotaCache) rely on +/// subscribeForBatchFinished firing exactly once after a whole notification batch is drained, so that +/// they patch their per-entity maps in the cheap per-entity handlers and run the expensive O(enabled +/// sets * entities) rebuild only once per batch (a full refresh delivers one notification per entity). +TEST(AccessChangesNotifier, BatchFinishedCoalescesRecompute) +{ + AccessChangesNotifier notifier; + + size_t per_entity_calls = 0; + size_t batch_calls = 0; + size_t per_entity_calls_seen_by_last_batch = 0; + + auto entity_subscription = notifier.subscribeForChanges( + AccessEntityType::ROW_POLICY, + [&](const UUID &, const AccessEntityPtr &) { ++per_entity_calls; }); + + auto batch_subscription = notifier.subscribeForBatchFinished( + [&] + { + ++batch_calls; + per_entity_calls_seen_by_last_batch = per_entity_calls; + }); + + /// A refresh that touches many entities: one notification per entity, drained in a single batch. + static constexpr size_t num_entities = 16; + for (size_t i = 0; i < num_entities; ++i) + notifier.onEntityRemoved(UUIDHelpers::generateV4(), AccessEntityType::ROW_POLICY); + + notifier.sendNotifications(); + + /// Every entity is dispatched, but the batch-finished hook runs exactly once - this is the + /// coalescing the caches depend on (without it the rebuild would run num_entities times). + EXPECT_EQ(per_entity_calls, num_entities); + EXPECT_EQ(batch_calls, 1u); + /// The batch hook runs after all per-entity notifications within the same sendNotifications() call, + /// so the derived state is current once sendNotifications() returns. + EXPECT_EQ(per_entity_calls_seen_by_last_batch, num_entities); + + /// A single DDL still recomputes exactly once. + notifier.onEntityRemoved(UUIDHelpers::generateV4(), AccessEntityType::ROW_POLICY); + notifier.sendNotifications(); + EXPECT_EQ(per_entity_calls, num_entities + 1); + EXPECT_EQ(batch_calls, 2u); + + /// An empty queue must not invoke the batch hook (nothing changed, nothing to rebuild). + notifier.sendNotifications(); + EXPECT_EQ(batch_calls, 2u); +} diff --git a/src/Common/ProfileEvents.cpp b/src/Common/ProfileEvents.cpp index d4c005d2f01b..be6c5d244177 100644 --- a/src/Common/ProfileEvents.cpp +++ b/src/Common/ProfileEvents.cpp @@ -513,6 +513,14 @@ \ M(ContextLock, "Number of times the lock of Context was acquired or tried to acquire. This is global lock.", ValueType::Number) \ M(ContextLockWaitMicroseconds, "Context lock wait time in microseconds", ValueType::Microseconds) \ + M(RowPolicyCacheRecalculations, "Number of times the row policy cache re-mixed filters for all live enabled sets. Coalesced to once per access entity notification batch.", ValueType::Number) \ + M(RowPolicyCacheRecalculationMicroseconds, "Total time spent re-mixing row policy filters for all live enabled sets (held under the cache mutex that the ContextAccess build path also takes).", ValueType::Microseconds) \ + M(RoleCacheRecalculations, "Number of times the role cache recalculated all live enabled sets. Coalesced to once per access entity notification batch.", ValueType::Number) \ + M(RoleCacheRecalculationMicroseconds, "Total time spent recalculating all live enabled role sets (held under the cache mutex that the ContextAccess build path also takes).", ValueType::Microseconds) \ + M(SettingsProfileCacheRecalculations, "Number of times the settings profile cache re-merged settings and constraints for all live enabled sets. Coalesced to once per access entity notification batch.", ValueType::Number) \ + M(SettingsProfileCacheRecalculationMicroseconds, "Total time spent re-merging settings and constraints for all live enabled sets (held under the cache mutex that the ContextAccess build path also takes).", ValueType::Microseconds) \ + M(QuotaCacheRecalculations, "Number of times the quota cache re-chose quotas for all live enabled sets. Coalesced to once per access entity notification batch.", ValueType::Number) \ + M(QuotaCacheRecalculationMicroseconds, "Total time spent re-choosing quotas for all live enabled sets (held under the cache mutex that the ContextAccess build path also takes).", ValueType::Microseconds) \ \ M(StorageBufferFlush, "Number of times a buffer in a 'Buffer' table was flushed.", ValueType::Number) \ M(StorageBufferErrorOnFlush, "Number of times a buffer in the 'Buffer' table has not been able to flush due to error writing in the destination table.", ValueType::Number) \ diff --git a/tests/integration/test_access_cache_recompute_coalescing/__init__.py b/tests/integration/test_access_cache_recompute_coalescing/__init__.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/tests/integration/test_access_cache_recompute_coalescing/configs/config.xml b/tests/integration/test_access_cache_recompute_coalescing/configs/config.xml new file mode 100644 index 000000000000..6738f8dfa66b --- /dev/null +++ b/tests/integration/test_access_cache_recompute_coalescing/configs/config.xml @@ -0,0 +1,22 @@ + + + + + true + + node1 + 9000 + + + node2 + 9000 + + + + + + + /clickhouse/access + + + diff --git a/tests/integration/test_access_cache_recompute_coalescing/test.py b/tests/integration/test_access_cache_recompute_coalescing/test.py new file mode 100644 index 000000000000..5381b5f347fa --- /dev/null +++ b/tests/integration/test_access_cache_recompute_coalescing/test.py @@ -0,0 +1,102 @@ +import pytest + +from helpers.cluster import ClickHouseCluster +from helpers.network import PartitionManager + +cluster = ClickHouseCluster(__file__) + +node1 = cluster.add_instance( + "node1", + main_configs=["configs/config.xml"], + with_zookeeper=True, + stay_alive=True, +) + +node2 = cluster.add_instance( + "node2", + main_configs=["configs/config.xml"], + with_zookeeper=True, + stay_alive=True, +) + +NUM_POLICIES = 50 + + +@pytest.fixture(scope="module") +def started_cluster(): + try: + cluster.start() + yield cluster + finally: + cluster.shutdown() + + +def get_row_policy_recalculations(node): + value = node.query( + "SELECT value FROM system.events WHERE event = 'RowPolicyCacheRecalculations'" + ).strip() + return int(value) if value else 0 + + +# A refresh that touches many entities at once (here: a ZooKeeper session loss that makes node2 +# reread everything) delivers one notification per entity. The row policy cache must coalesce the +# expensive re-mix to once per batch instead of running it once per changed policy. +def test_recompute_coalesced_on_reread(started_cluster): + node1.query( + "CREATE TABLE IF NOT EXISTS default.t (x Int32) ENGINE = Memory;" + "CREATE USER u IDENTIFIED WITH no_password;" + + "".join( + f"CREATE ROW POLICY p{i} ON default.t USING x = {i} TO u;" + for i in range(NUM_POLICIES) + ) + ) + node2.query("CREATE TABLE IF NOT EXISTS default.t (x Int32) ENGINE = Memory") + + node2.query_with_retry( + "SELECT count() FROM system.row_policies WHERE short_name LIKE 'p%'", + check_callback=lambda r: r.strip() == str(NUM_POLICIES), + ) + + # Build a live EnabledRowPolicies set for u on node2 (this also subscribes RowPolicyCache). + node2.query("SELECT 1", user="u") + + baseline = get_row_policy_recalculations(node2) + + # All three steps are required and none can be dropped: + # - PartitionManager makes node2 *miss* the changes, so they are not consumed one-by-one as + # per-entity watches (each of which would be its own notification batch the coalescing can't + # help with); + # - SYSTEM RECONNECT ZOOKEEPER drops node2's session so the reconnect rereads everything in one + # `all=true` pass (a surviving session would redeliver the missed changes as NUM_POLICIES + # separate watch notifications); + # - SYSTEM RELOAD USERS is the deterministic flush (reload + synchronous sendNotifications). + # SYSTEM RELOAD USERS on its own is not enough: on an up-to-date node `setAll` diffs to zero and + # emits nothing. + with PartitionManager() as pm: + pm.drop_instance_zk_connections(node2) + node1.query( + "".join( + f"ALTER ROW POLICY p{i} ON default.t USING x = {i} + 1000;" + for i in range(NUM_POLICIES) + ) + ) + node2.query("SYSTEM RECONNECT ZOOKEEPER") + + node2.query_with_retry("SYSTEM RELOAD USERS") + + delta = get_row_policy_recalculations(node2) - baseline + + node1.query( + "DROP USER u;" + + "".join(f"DROP ROW POLICY p{i} ON default.t;" for i in range(NUM_POLICIES)) + + "DROP TABLE default.t SYNC;" + ) + node2.query("DROP TABLE default.t SYNC") + + # Lower bound guards against a silently broken setup (cache not subscribed / batch never fired): + # the reread must recompute at least once. Upper bound is the coalescing guarantee: once per + # batch (allow 2 for the reconnect/reload reread overlapping), not once per changed policy (~50). + assert 1 <= delta <= 2, ( + f"row policy cache recomputed {delta} times for a {NUM_POLICIES}-entity reread; " + f"expected 1 (coalesced); without coalescing it would be ~{NUM_POLICIES}" + ) From 584caffa035d393ef7c0a530280152fe0f5d6e61 Mon Sep 17 00:00:00 2001 From: robot-clickhouse Date: Mon, 22 Jun 2026 14:14:22 +0000 Subject: [PATCH 062/146] Backport #102396 to 26.3: Fix SLRU and SplitFileCachePriority dynamic resize bugs --- src/Interpreters/Cache/EvictionCandidates.cpp | 7 + src/Interpreters/Cache/EvictionCandidates.h | 11 + src/Interpreters/Cache/FileCache.cpp | 23 +- src/Interpreters/Cache/IFileCachePriority.h | 15 + .../Cache/SLRUFileCachePriority.cpp | 33 +- .../Cache/SLRUFileCachePriority.h | 8 + .../Cache/SplitFileCachePriority.cpp | 17 +- .../Cache/SplitFileCachePriority.h | 11 + src/Interpreters/tests/gtest_filecache.cpp | 98 +++++ .../config.d/cache_dynamic_resize_slru.xml | 21 ++ .../integration/test_filesystem_cache/test.py | 334 ++++++++++++++++++ 11 files changed, 563 insertions(+), 15 deletions(-) create mode 100644 tests/integration/test_filesystem_cache/config.d/cache_dynamic_resize_slru.xml diff --git a/src/Interpreters/Cache/EvictionCandidates.cpp b/src/Interpreters/Cache/EvictionCandidates.cpp index ad74947dce2b..35500747622d 100644 --- a/src/Interpreters/Cache/EvictionCandidates.cpp +++ b/src/Interpreters/Cache/EvictionCandidates.cpp @@ -215,6 +215,13 @@ void EvictionCandidates::removeQueueEntries(const CachePriorityGuard::WriteLock for (const auto & candidate : key_candidates.candidates) { auto queue_iterator = candidate->getQueueIterator(); + + /// Save the inner queue type before invalidation so we can + /// restore entries to their original queue if eviction fails. + /// Use getNestedOrThis() to see through SplitIterator and get + /// the SLRU_Protected/SLRU_Probationary type, not SplitCache_Data/System. + original_queue_types[candidate.get()] = queue_iterator->getNestedOrThis()->getType(); + queue_iterator->invalidate(); chassert(candidate->releasable()); diff --git a/src/Interpreters/Cache/EvictionCandidates.h b/src/Interpreters/Cache/EvictionCandidates.h index 73e09eb60da0..b4c8da2e47bd 100644 --- a/src/Interpreters/Cache/EvictionCandidates.h +++ b/src/Interpreters/Cache/EvictionCandidates.h @@ -152,6 +152,14 @@ class EvictionCandidates : private boost::noncopyable FailedCandidates getFailedCandidates() const { return failed_candidates; } + /// Get the original queue type of a candidate saved during removeQueueEntries. + /// Returns None if not found (e.g., if removeQueueEntries was not called). + FileCacheQueueEntryType getOriginalQueueType(const FileSegmentMetadata * candidate) const + { + auto it = original_queue_types.find(candidate); + return it != original_queue_types.end() ? it->second : FileCacheQueueEntryType::None; + } + private: std::unordered_map candidates; @@ -159,6 +167,9 @@ class EvictionCandidates : private boost::noncopyable size_t candidates_bytes = 0; FailedCandidates failed_candidates; + /// Saved original queue type per candidate, populated in removeQueueEntries. + std::unordered_map original_queue_types; + AfterEvictWriteFunc after_evict_write_func; AfterEvictStateFunc after_evict_state_func; diff --git a/src/Interpreters/Cache/FileCache.cpp b/src/Interpreters/Cache/FileCache.cpp index 927475fd90d1..c01a2e777309 100644 --- a/src/Interpreters/Cache/FileCache.cpp +++ b/src/Interpreters/Cache/FileCache.cpp @@ -2423,13 +2423,14 @@ bool FileCache::doDynamicResizeImpl( return true; } - result_limits.max_size = std::min( - prev_limits.max_size, - desired_limits.max_size + failed_candidates.total_cache_size); - - result_limits.max_elements = std::min( - prev_limits.max_elements, - desired_limits.max_elements + failed_candidates.total_cache_elements); + /// Restore to previous limits. Using prev_limits is safe because + /// the entries existed under those limits before the resize attempt, + /// so each sub-queue had enough room. Computing a tighter bound + /// (desired + failed) would be incorrect for SLRU: when all failed + /// entries belong to one sub-queue (e.g. protected with ratio 0.6), + /// the total might not translate into enough per-sub-queue space + /// after the ratio split. + result_limits = prev_limits; LOG_INFO( log, "Having {} failed candidates with total size {}. " @@ -2479,13 +2480,15 @@ bool FileCache::doDynamicResizeImpl( log, "Adding back file segment after failed eviction: {}:{}, size: {}", file_segment->key(), file_segment->offset(), file_segment->getDownloadedSize()); - auto main_priority_iterator = main_priority->add( + auto original_queue_type = eviction_candidates.getOriginalQueueType(candidate.get()); + + auto main_priority_iterator = main_priority->addForRestore( key_metadata, file_segment->offset(), file_segment->getDownloadedSize(), + original_queue_type, cache_write_lock, - &state_lock, - false); + &state_lock); candidate->setRemovedFlag(*locked_key, /* value */false); file_segment->setQueueIterator(main_priority_iterator); diff --git a/src/Interpreters/Cache/IFileCachePriority.h b/src/Interpreters/Cache/IFileCachePriority.h index 81fee26c14df..d6ad810d6480 100644 --- a/src/Interpreters/Cache/IFileCachePriority.h +++ b/src/Interpreters/Cache/IFileCachePriority.h @@ -232,6 +232,21 @@ class IFileCachePriority : private boost::noncopyable const CacheStateGuard::Lock *, bool is_initial_load = false) = 0; + /// Restore a previously removed entry back to the queue it came from. + /// `original_queue_type` is the `QueueEntryType` the entry had before removal. + /// Default implementation ignores the hint and delegates to `add`. + /// SLRU overrides this to route protected entries back to the protected queue. + virtual IteratorPtr addForRestore( /// NOLINT + KeyMetadataPtr key_metadata, + size_t offset, + size_t size, + QueueEntryType /* original_queue_type */, + const CachePriorityGuard::WriteLock & lock, + const CacheStateGuard::Lock * state_lock) + { + return add(key_metadata, offset, size, lock, state_lock, false); + } + /// `reservee` is the entry for which are reserving now. /// It does not exist, if it is the first space reservation attempt /// for the corresponding file segment. diff --git a/src/Interpreters/Cache/SLRUFileCachePriority.cpp b/src/Interpreters/Cache/SLRUFileCachePriority.cpp index e894af23bfaf..651931362496 100644 --- a/src/Interpreters/Cache/SLRUFileCachePriority.cpp +++ b/src/Interpreters/Cache/SLRUFileCachePriority.cpp @@ -159,6 +159,27 @@ IFileCachePriority::IteratorPtr SLRUFileCachePriority::add( /// NOLINT is_protected); } +IFileCachePriority::IteratorPtr SLRUFileCachePriority::addForRestore( /// NOLINT + KeyMetadataPtr key_metadata, + size_t offset, + size_t size, + QueueEntryType original_queue_type, + const CachePriorityGuard::WriteLock & lock, + const CacheStateGuard::Lock * state_lock) +{ + /// Restore to the original queue: protected entries go back to protected, + /// everything else goes to probationary. + bool is_protected = (original_queue_type == QueueEntryType::SLRU_Protected); + + auto entry = std::make_shared(key_metadata->key, offset, size, key_metadata); + return std::make_shared( + this, + is_protected + ? protected_queue.add(std::move(entry), lock, state_lock) + : probationary_queue.add(std::move(entry), lock, state_lock), + is_protected); +} + void SLRUFileCachePriority::iterate( IterateFunc func, FileCacheReserveStat & stat, @@ -261,9 +282,13 @@ bool SLRUFileCachePriority::collectCandidatesForEviction( { if (is_total_space_cleanup) { + /// Use per-queue local stat objects so that each sub-queue's + /// stopping condition sees only its own accumulated releasable bytes, + /// not the cumulative total from both passes. + FileCacheReserveStat probationary_stat; bool success_probationary = probationary_queue.collectCandidatesForEviction( eviction_info, - stat, + probationary_stat, res, invalidated_entries, reservee, @@ -280,9 +305,10 @@ bool SLRUFileCachePriority::collectCandidatesForEviction( /// We do not use collectCandidatesForEvictionInProtected method, /// because it will "downgrade" instead of remove, /// but for total space cleanup we need remove. + FileCacheReserveStat protected_stat; bool success_protected = protected_queue.collectCandidatesForEviction( eviction_info, - stat, + protected_stat, res, invalidated_entries, reservee, @@ -293,6 +319,9 @@ bool SLRUFileCachePriority::collectCandidatesForEviction( cache_guard, state_guard); + stat += probationary_stat; + stat += protected_stat; + return success_probationary && success_protected; } diff --git a/src/Interpreters/Cache/SLRUFileCachePriority.h b/src/Interpreters/Cache/SLRUFileCachePriority.h index 02c8a866d7dc..d015449b2d00 100644 --- a/src/Interpreters/Cache/SLRUFileCachePriority.h +++ b/src/Interpreters/Cache/SLRUFileCachePriority.h @@ -61,6 +61,14 @@ class SLRUFileCachePriority : public IFileCachePriority const CacheStateGuard::Lock *, bool is_initial_load = false) override; + IteratorPtr addForRestore( /// NOLINT + KeyMetadataPtr key_metadata, + size_t offset, + size_t size, + QueueEntryType original_queue_type, + const CachePriorityGuard::WriteLock &, + const CacheStateGuard::Lock *) override; + bool collectCandidatesForEviction( const EvictionInfo & eviction_info, FileCacheReserveStat & stat, diff --git a/src/Interpreters/Cache/SplitFileCachePriority.cpp b/src/Interpreters/Cache/SplitFileCachePriority.cpp index 45a01ff41a15..758a03cfd7f6 100644 --- a/src/Interpreters/Cache/SplitFileCachePriority.cpp +++ b/src/Interpreters/Cache/SplitFileCachePriority.cpp @@ -122,9 +122,7 @@ bool SplitFileCachePriority::modifySizeLimits( double size_ratio_, const CacheStateGuard::Lock & lock) { - if (max_size == max_size_ - && max_elements == max_elements_ - && system_segment_size_ratio == size_ratio_) + if (max_size == max_size_ && max_elements == max_elements_) return false; /// Nothing to change. max_data_segment_elements = getRatio(max_elements_, (1 - system_segment_size_ratio)); @@ -177,6 +175,19 @@ IFileCachePriority::IteratorPtr SplitFileCachePriority::add( /// NOLINT key_metadata, offset, size, write_lock, state_lock, is_initial_load); } +IFileCachePriority::IteratorPtr SplitFileCachePriority::addForRestore( /// NOLINT + KeyMetadataPtr key_metadata, + size_t offset, + size_t size, + QueueEntryType original_queue_type, + const CachePriorityGuard::WriteLock & write_lock, + const CacheStateGuard::Lock * state_lock) +{ + const auto type = getPriorityType(key_metadata->origin.segment_type); + return priorities_holder.at(type)->addForRestore( + key_metadata, offset, size, original_queue_type, write_lock, state_lock); +} + bool SplitFileCachePriority::canFit( /// NOLINT size_t size, size_t elements, diff --git a/src/Interpreters/Cache/SplitFileCachePriority.h b/src/Interpreters/Cache/SplitFileCachePriority.h index 4306f4462c9b..31fa0ef0563f 100644 --- a/src/Interpreters/Cache/SplitFileCachePriority.h +++ b/src/Interpreters/Cache/SplitFileCachePriority.h @@ -62,6 +62,14 @@ class SplitFileCachePriority : public IFileCachePriority const CacheStateGuard::Lock *, bool is_initial_load = false) override; + IteratorPtr addForRestore( /// NOLINT + KeyMetadataPtr key_metadata, + size_t offset, + size_t size, + QueueEntryType original_queue_type, + const CachePriorityGuard::WriteLock &, + const CacheStateGuard::Lock *) override; + bool tryIncreasePriority( Iterator & iterator, bool is_space_reservation_complete, @@ -158,6 +166,9 @@ class SplitFileCachePriority::SplitIterator : public IFileCachePriority::Iterato : QueueEntryType::SplitCache_System; } + const Iterator * getNestedOrThis() const override { return iterator->getNestedOrThis(); } + Iterator * getNestedOrThis() override { return iterator->getNestedOrThis(); } + const FileSegmentKeyType type; private: diff --git a/src/Interpreters/tests/gtest_filecache.cpp b/src/Interpreters/tests/gtest_filecache.cpp index 582e391a42f8..fe0a4296ff32 100644 --- a/src/Interpreters/tests/gtest_filecache.cpp +++ b/src/Interpreters/tests/gtest_filecache.cpp @@ -68,6 +68,7 @@ namespace DB::FileCacheSetting extern const FileCacheSettingsUInt64 load_metadata_threads; extern const FileCacheSettingsBool load_metadata_asynchronously; extern const FileCacheSettingsBool write_cache_per_user_id_directory; + extern const FileCacheSettingsBool allow_dynamic_cache_resize; } void printRanges(const auto & segments) @@ -355,14 +356,19 @@ class FileCacheTest : public ::testing::Test fs::remove_all(cache_base_path); if (fs::exists(cache_base_path2)) fs::remove_all(cache_base_path2); + if (fs::exists(cache_base_path3)) + fs::remove_all(cache_base_path3); fs::create_directories(cache_base_path); fs::create_directories(cache_base_path2); + fs::create_directories(cache_base_path3); } void TearDown() override { if (fs::exists(cache_base_path)) fs::remove_all(cache_base_path); + if (fs::exists(cache_base_path3)) + fs::remove_all(cache_base_path3); } pcg64 rng; @@ -1442,6 +1448,98 @@ TEST_F(FileCacheTest, SLRUPolicy) } } +TEST_F(FileCacheTest, SLRUDynamicResizeCorrectEviction) +{ + /// Test that SLRU dynamic resize correctly evicts from both sub-queues + /// after the per-queue stat fix. + ServerUUID::setRandomForUnitTests(); + DB::ThreadStatus thread_status; + + ReadSettings read_settings; + read_settings.enable_filesystem_cache = true; + read_settings.local_fs_method = LocalFSReadMethod::pread; + + auto write_file = [](const std::string & filename, const std::string & s) + { + std::string file_path = fs::current_path() / filename; + auto wb = std::make_unique(file_path, DBMS_DEFAULT_BUFFER_SIZE); + wb->write(s.data(), s.size()); + wb->next(); + wb->finalize(); + return file_path; + }; + + /// Create SLRU cache: max_size=30, max_elements=6, ratio=0.5 + /// So protected = 15 bytes / 3 elements, probationary = 15 bytes / 3 elements. + DB::FileCacheSettings settings; + settings[FileCacheSetting::path] = cache_base_path2; + settings[FileCacheSetting::max_file_segment_size] = 5; + settings[FileCacheSetting::max_size] = 30; + settings[FileCacheSetting::max_elements] = 6; + settings[FileCacheSetting::boundary_alignment] = 1; + settings[FileCacheSetting::slru_size_ratio] = 0.5; + settings[FileCacheSetting::load_metadata_asynchronously] = false; + settings[FileCacheSetting::cache_policy] = FileCachePolicy::SLRU; + settings[FileCacheSetting::allow_dynamic_cache_resize] = true; + + auto cache = std::make_shared("slru_resize", settings); + cache->initialize(); + + const auto & user = FileCache::getCommonOrigin(); + + auto read_and_check = [&](const std::string & file, const FileCacheKey & key, const std::string & expect_result) + { + auto read_buffer_creator = [&]() + { + return createReadBufferFromFileBase(file, read_settings, std::nullopt, std::nullopt); + }; + auto cached_buffer = std::make_shared( + file, key, cache, user, read_buffer_creator, read_settings, + "test", expect_result.size(), false, false, std::nullopt, nullptr); + WriteBufferFromOwnString result; + copyData(*cached_buffer, result); + ASSERT_EQ(result.str(), expect_result); + }; + + /// Read file1 twice -> 15 bytes in protected (3 segs x 5) + std::string data1(15, '*'); + auto file1 = write_file("test_resize1", data1); + auto key1 = DB::FileCacheKey::fromPath(file1); + read_and_check(file1, key1, data1); + read_and_check(file1, key1, data1); + + assertProtected(cache->dumpQueue(), { Range(0, 4), Range(5, 9), Range(10, 14) }); + + /// Read file2 once -> 10 bytes in probationary (2 segs x 5) + std::string data2(10, '+'); + auto file2 = write_file("test_resize2", data2); + auto key2 = DB::FileCacheKey::fromPath(file2); + read_and_check(file2, key2, data2); + + assertProbationary(cache->dumpQueue(), { Range(0, 4), Range(5, 9) }); + ASSERT_EQ(cache->getUsedCacheSize(), 25); + ASSERT_EQ(cache->getFileSegmentsNum(), 5); + + /// Resize to max_size=8, max_elements=6. + /// Protected limit = 4, probationary limit = 4. + /// Both queues need eviction. Without the fix, the protected pass + /// would short-circuit and modifySizeLimits would throw LOGICAL_ERROR. + DB::FileCacheSettings new_settings = settings; + new_settings[FileCacheSetting::max_size] = 8; + DB::FileCacheSettings actual_settings = settings; + + /// Must not throw -- this is the core regression test for the bug. + ASSERT_NO_THROW(cache->applySettingsIfPossible(new_settings, actual_settings)); + + /// Verify limits were applied. + ASSERT_EQ(actual_settings[FileCacheSetting::max_size].value, 8); + ASSERT_EQ(actual_settings[FileCacheSetting::max_elements].value, 6); + + /// Verify cache usage is within new limits. + ASSERT_LE(cache->getUsedCacheSize(), 8); + ASSERT_LE(cache->getFileSegmentsNum(), 6); +} + TEST_F(FileCacheTest, FileCacheGetOrSet) { ServerUUID::setRandomForUnitTests(); diff --git a/tests/integration/test_filesystem_cache/config.d/cache_dynamic_resize_slru.xml b/tests/integration/test_filesystem_cache/config.d/cache_dynamic_resize_slru.xml new file mode 100644 index 000000000000..a4aaeafab1f6 --- /dev/null +++ b/tests/integration/test_filesystem_cache/config.d/cache_dynamic_resize_slru.xml @@ -0,0 +1,21 @@ + + + + + local_blob_storage + /var/lib/clickhouse/local_disk_slru/ + + + cache + hdd_blob + 100 + 10 + 10 + 10 + SLRU + 1 + ./cache_dynamic_resize_slru/ + + + + diff --git a/tests/integration/test_filesystem_cache/test.py b/tests/integration/test_filesystem_cache/test.py index 66eb3cc0d468..3a3161a73783 100644 --- a/tests/integration/test_filesystem_cache/test.py +++ b/tests/integration/test_filesystem_cache/test.py @@ -40,6 +40,13 @@ def cluster(): "config.d/cache_dynamic_resize.xml", ], ) + cluster.add_instance( + "cache_dynamic_resize_slru", + main_configs=[ + "config.d/cache_dynamic_resize_slru.xml", + ], + stay_alive=True, + ) cluster.add_instance( "node_force_read_through_cache_on_merge", main_configs=[ @@ -1033,3 +1040,330 @@ def drop_cache_loop(): assert errors == 0, f"LOGICAL_ERROR occurred on {node.name}" finally: node.query(f"DROP TABLE IF EXISTS {table_name} SYNC") + + +cache_dynamic_resize_slru_config = """ + + + + + local_blob_storage + / + + + cache + hdd_blob + {max_size} + {max_elements} + 10 + 10 + SLRU + 1 + ./cache_dynamic_resize_slru/ + + + + +""" + + +def slru_config(max_size=100, max_elements=10): + return cache_dynamic_resize_slru_config.format( + max_size=max_size, max_elements=max_elements + ) + + +def test_dynamic_resize_slru(cluster): + """Test that SLRU filesystem cache properly evicts from both protected and + probationary queues when max_size and max_elements are shrunk via config reload, + and that growing limits back works correctly.""" + node = cluster.instances["cache_dynamic_resize_slru"] + cache_name = "cache_dynamic_resize_slru" + + node.query( + f""" +DROP TABLE IF EXISTS test_slru1 SYNC; +DROP TABLE IF EXISTS test_slru2 SYNC; +SYSTEM CLEAR FILESYSTEM CACHE; +CREATE TABLE test_slru1 (a String) +ENGINE = MergeTree() ORDER BY tuple() +SETTINGS disk = '{cache_name}', min_bytes_for_wide_part = 10485760, + serialization_info_version = 'basic'; +INSERT INTO test_slru1 SELECT randomString(20); +CREATE TABLE test_slru2 (a String) +ENGINE = MergeTree() ORDER BY tuple() +SETTINGS disk = '{cache_name}', min_bytes_for_wide_part = 10485760, + serialization_info_version = 'basic'; +INSERT INTO test_slru2 SELECT randomString(20); +SYSTEM CLEAR FILESYSTEM CACHE; + """ + ) + + def get_cache_settings(): + row = node.query( + f"SELECT max_size, max_elements FROM system.filesystem_cache_settings " + f"WHERE cache_name = '{cache_name}'" + ).strip() + parts = row.split("\t") + return int(parts[0]), int(parts[1]) + + def get_downloaded_count(): + return int( + node.query( + f"SELECT count() FROM system.filesystem_cache " + f"WHERE state = 'DOWNLOADED' AND cache_name = '{cache_name}'" + ) + ) + + def get_downloaded_size(): + return int( + node.query( + f"SELECT sum(downloaded_size) FROM system.filesystem_cache " + f"WHERE state = 'DOWNLOADED' AND cache_name = '{cache_name}'" + ) + ) + + try: + # Verify initial settings + max_size, max_elements = get_cache_settings() + assert max_size == 100 + assert max_elements == 10 + + assert get_downloaded_count() == 0 + + test_start = node.query("SELECT now()").strip() + + # Read table 1 twice to promote its segments into the protected queue + node.query("SELECT * FROM test_slru1 FORMAT Null") + node.query("SELECT * FROM test_slru1 FORMAT Null") + + # Read table 2 once -- its segments stay in the probationary queue + node.query("SELECT * FROM test_slru2 FORMAT Null") + + assert get_downloaded_count() > 0 + assert get_downloaded_size() > 0 + + # --- Shrink max_size from 100 to 10 --- + node.replace_config( + "/etc/clickhouse-server/config.d/cache_dynamic_resize_slru.xml", + slru_config(max_size=10, max_elements=10), + ) + node.query("SYSTEM RELOAD CONFIG") + + s, e = get_cache_settings() + assert s == 10 + assert e == 10 + # Total cached bytes must not exceed the new limit + assert get_downloaded_size() <= 10 + + # --- Grow max_size back to 100 --- + node.replace_config( + "/etc/clickhouse-server/config.d/cache_dynamic_resize_slru.xml", + slru_config(max_size=100, max_elements=10), + ) + node.query("SYSTEM RELOAD CONFIG") + + s, e = get_cache_settings() + assert s == 100 + assert e == 10 + + # Re-read to populate the cache again + node.query("SELECT * FROM test_slru1 FORMAT Null") + node.query("SELECT * FROM test_slru2 FORMAT Null") + assert get_downloaded_count() > 0 + assert get_downloaded_size() > 0 + + # --- Shrink max_elements from 10 to 2 --- + node.replace_config( + "/etc/clickhouse-server/config.d/cache_dynamic_resize_slru.xml", + slru_config(max_size=100, max_elements=2), + ) + node.query("SYSTEM RELOAD CONFIG") + + s, e = get_cache_settings() + assert s == 100 + assert e == 2 + assert get_downloaded_count() <= 2 + + # --- Grow max_elements back to 10 --- + node.replace_config( + "/etc/clickhouse-server/config.d/cache_dynamic_resize_slru.xml", + slru_config(max_size=100, max_elements=10), + ) + node.query("SYSTEM RELOAD CONFIG") + + s, e = get_cache_settings() + assert s == 100 + assert e == 10 + + # Verify the cache still works after all the resizing + node.query("SELECT * FROM test_slru1 FORMAT Null") + assert get_downloaded_count() > 0 + + # No LOGICAL_ERROR should have occurred during resize operations + errors = int( + node.query( + f"SELECT count() FROM system.errors " + f"WHERE name = 'LOGICAL_ERROR' AND last_error_time >= '{test_start}'" + ).strip() + ) + assert errors == 0, f"LOGICAL_ERROR occurred during SLRU resize test" + + finally: + node.replace_config( + "/etc/clickhouse-server/config.d/cache_dynamic_resize_slru.xml", + slru_config(max_size=100, max_elements=10), + ) + node.query("SYSTEM RELOAD CONFIG") + node.query("DROP TABLE IF EXISTS test_slru1 SYNC") + node.query("DROP TABLE IF EXISTS test_slru2 SYNC") + + +def test_dynamic_resize_slru_failpoint_eviction(cluster): + """Test that SLRU filesystem cache resize gracefully handles eviction failures + via the file_cache_dynamic_resize_fail_to_evict failpoint. When eviction fails, + entries should be restored to their original queues and the cache should remain + in a consistent state.""" + node = cluster.instances["cache_dynamic_resize_slru"] + cache_name = "cache_dynamic_resize_slru" + + # Restore to known-good initial state + node.replace_config( + "/etc/clickhouse-server/config.d/cache_dynamic_resize_slru.xml", + slru_config(max_size=100, max_elements=10), + ) + node.query("SYSTEM RELOAD CONFIG") + + node.query( + f""" +DROP TABLE IF EXISTS test_slru_fp SYNC; +SYSTEM CLEAR FILESYSTEM CACHE; +CREATE TABLE test_slru_fp (a String) +ENGINE = MergeTree() ORDER BY tuple() +SETTINGS disk = '{cache_name}', min_bytes_for_wide_part = 10485760, + serialization_info_version = 'basic'; +INSERT INTO test_slru_fp SELECT randomString(20); +SYSTEM CLEAR FILESYSTEM CACHE; + """ + ) + + get_downloaded_count = lambda: int( + node.query( + f"SELECT count() FROM system.filesystem_cache " + f"WHERE state = 'DOWNLOADED' AND cache_name = '{cache_name}'" + ) + ) + + get_downloaded_size = lambda: int( + node.query( + f"SELECT sum(downloaded_size) FROM system.filesystem_cache " + f"WHERE state = 'DOWNLOADED' AND cache_name = '{cache_name}'" + ) + ) + + get_max_size = lambda: int( + node.query( + f"SELECT max_size FROM system.filesystem_cache_settings " + f"WHERE cache_name = '{cache_name}'" + ).strip() + ) + + try: + test_start = node.query("SELECT now()").strip() + + # Read twice to promote segments into the protected queue + node.query("SELECT * FROM test_slru_fp FORMAT Null") + node.query("SELECT * FROM test_slru_fp FORMAT Null") + + initial_count = get_downloaded_count() + initial_size = get_downloaded_size() + assert initial_count > 0 + assert initial_size > 0 + + # Enable failpoint so eviction will fail + node.query( + "SYSTEM ENABLE FAILPOINT file_cache_dynamic_resize_fail_to_evict" + ) + + # Anchor log position so we only check lines written from now on + log_anchor = node.count_log_lines() + + # Attempt to shrink -- eviction will fail, so limits should stay + # at old values (or somewhere between old and desired) + node.replace_config( + "/etc/clickhouse-server/config.d/cache_dynamic_resize_slru.xml", + slru_config(max_size=10, max_elements=10), + ) + node.query("SYSTEM RELOAD CONFIG") + + # Wait for the background resize thread to attempt (and fail) the resize. + # Confirm the failure path actually ran by checking for the log message + # emitted when eviction candidates fail. + node.wait_for_log_line( + "Having .* failed candidates", + timeout=60, + look_behind_lines=f"+{log_anchor}", + ) + + # Disable failpoint before checking state + node.query( + "SYSTEM DISABLE FAILPOINT file_cache_dynamic_resize_fail_to_evict" + ) + + # After failed resize, limits should have reverted to prev_limits (100) + assert get_max_size() == 100, ( + f"max_size should have reverted to 100 after failed resize, got {get_max_size()}" + ) + + # Entries should have been restored -- count and size should be + # the same as before the failed resize attempt + assert get_downloaded_count() == initial_count, ( + f"Entry count changed after failed resize: {initial_count} -> {get_downloaded_count()}" + ) + assert get_downloaded_size() == initial_size, ( + f"Total size changed after failed resize: {initial_size} -> {get_downloaded_size()}" + ) + + # Cache should still be usable -- reads should work + node.query("SELECT * FROM test_slru_fp FORMAT Null") + + # Now do a real resize (without failpoint) to verify cache is not corrupted + node.replace_config( + "/etc/clickhouse-server/config.d/cache_dynamic_resize_slru.xml", + slru_config(max_size=10, max_elements=10), + ) + node.query("SYSTEM RELOAD CONFIG") + + # Poll for the real resize to complete + for _ in range(30): + if get_max_size() == 10: + break + time.sleep(1) + + assert get_max_size() == 10, ( + f"Dynamic resize to 10 did not complete, max_size is {get_max_size()}" + ) + + assert get_downloaded_size() <= 10, ( + f"Cache size {get_downloaded_size()} exceeds new limit 10 after real resize" + ) + + # No LOGICAL_ERROR should have occurred + errors = int( + node.query( + f"SELECT count() FROM system.errors " + f"WHERE name = 'LOGICAL_ERROR' AND last_error_time >= '{test_start}'" + ).strip() + ) + assert errors == 0, f"LOGICAL_ERROR occurred during SLRU failpoint resize test" + + finally: + node.query( + "SYSTEM DISABLE FAILPOINT file_cache_dynamic_resize_fail_to_evict" + ) + node.replace_config( + "/etc/clickhouse-server/config.d/cache_dynamic_resize_slru.xml", + slru_config(max_size=100, max_elements=10), + ) + node.query("SYSTEM RELOAD CONFIG") + node.query("DROP TABLE IF EXISTS test_slru_fp SYNC") From 7e13b747c931cef831a3d8078708678222f9c331 Mon Sep 17 00:00:00 2001 From: robot-clickhouse Date: Mon, 22 Jun 2026 16:01:04 +0000 Subject: [PATCH 063/146] Backport #107764 to 26.3: Fix regression: accept 0-byte offsets buffer for empty Arrow String/Binary columns --- .../Formats/Impl/ArrowColumnToCHColumn.cpp | 7 +- ...04356_arrow_empty_string_offsets.reference | 5 ++ .../04356_arrow_empty_string_offsets.sh | 73 +++++++++++++++++++ 3 files changed, 84 insertions(+), 1 deletion(-) create mode 100644 tests/queries/0_stateless/04356_arrow_empty_string_offsets.reference create mode 100755 tests/queries/0_stateless/04356_arrow_empty_string_offsets.sh diff --git a/src/Processors/Formats/Impl/ArrowColumnToCHColumn.cpp b/src/Processors/Formats/Impl/ArrowColumnToCHColumn.cpp index 83d1e7aead9d..01b710ac1be8 100644 --- a/src/Processors/Formats/Impl/ArrowColumnToCHColumn.cpp +++ b/src/Processors/Formats/Impl/ArrowColumnToCHColumn.cpp @@ -215,6 +215,9 @@ const ArrowViewArray & checkedCastView(const arrow::Array & array, const String /// because value_length(i) = offset[i+1] - offset[i], so the last element needs /// offset[length]. This must be checked before any call to value_offset(i) or /// GetView(i) to prevent an over-read of the offsets buffer. +/// When length == 0, no offsets are accessed at all (every caller's iteration loop +/// is skipped), so no bytes are required. This accepts the 0-byte offsets buffers +/// that Apache Arrow Java < 19.0.0 emits for empty String/Binary columns. template void checkBinaryOffsetsBuffer(const ArrowBinaryArray & chunk, const String & column_name) { @@ -225,7 +228,9 @@ void checkBinaryOffsetsBuffer(const ArrowBinaryArray & chunk, const String & col column_name, chunk.length(), chunk.offset()); const auto & buffer = chunk.data()->buffers[1]; const size_t buffer_size = buffer ? static_cast(buffer->size()) : 0; - const size_t count_plus_one = static_cast(chunk.offset()) + static_cast(chunk.length()) + 1; + const size_t count_plus_one = chunk.length() > 0 + ? static_cast(chunk.offset()) + static_cast(chunk.length()) + 1 + : 0; size_t required = 0; if (unlikely(__builtin_mul_overflow(sizeof(typename ArrowBinaryArray::offset_type), count_plus_one, &required))) throw Exception( diff --git a/tests/queries/0_stateless/04356_arrow_empty_string_offsets.reference b/tests/queries/0_stateless/04356_arrow_empty_string_offsets.reference new file mode 100644 index 000000000000..4acbfebb8f24 --- /dev/null +++ b/tests/queries/0_stateless/04356_arrow_empty_string_offsets.reference @@ -0,0 +1,5 @@ +0 +1 {} +2 {} +1 [] +2 [] diff --git a/tests/queries/0_stateless/04356_arrow_empty_string_offsets.sh b/tests/queries/0_stateless/04356_arrow_empty_string_offsets.sh new file mode 100755 index 000000000000..fcbfcbb00013 --- /dev/null +++ b/tests/queries/0_stateless/04356_arrow_empty_string_offsets.sh @@ -0,0 +1,73 @@ +#!/usr/bin/env bash +# Tags: no-fasttest +# Regression test for accepting empty String/Binary columns whose offsets buffer +# is 0 bytes. Apache Arrow Java < 19.0.0 (bundled with Apache Spark) emits a +# 0-byte offsets buffer when a String/Binary child column has zero elements +# (e.g. every map is empty, so the key child has length=0). Every other Arrow +# implementation (arrow-cpp, pyarrow, arrow-rs) accepts this; ClickHouse must +# too. The fix: when chunk.length() == 0, no offsets are ever accessed, so +# 0 bytes are required. +# +# Covers three shapes of empty String column: +# (a) standalone String column, length=0 +# (b) Map(String, Int32) where all entries are empty -> key child length=0 +# (c) Array(String) where all arrays are empty -> string child length=0 + +CUR_DIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd) +# shellcheck source=../shell_config.sh +. "$CUR_DIR"/../shell_config.sh + +TMP_DIR="${CLICKHOUSE_TMP}/${CLICKHOUSE_TEST_UNIQUE_NAME}" +mkdir -p "$TMP_DIR" +trap 'rm -rf "$TMP_DIR"' EXIT + +python3 - "$TMP_DIR" <<'PYEOF' +import io, sys +import pyarrow as pa +import pyarrow.ipc as ipc + +out = sys.argv[1] + +def write_arrow(tbl): + buf = io.BytesIO() + with ipc.new_file(buf, tbl.schema) as w: + w.write_table(tbl) + return buf.getvalue() + +def zero_byte_string_array(): + """Build a String array with length=0 and a 0-byte offsets buffer, + as Apache Arrow Java < 19.0.0 produces for empty collections.""" + return pa.Array.from_buffers( + pa.string(), 0, + [None, pa.py_buffer(b""), pa.py_buffer(b"")] + ) + +# (a) Standalone String column, 0 rows, 0-byte offsets buffer. +tbl_a = pa.table({"s": zero_byte_string_array()}) +open(f"{out}/empty_string.arrow", "wb").write(write_arrow(tbl_a)) + +# (b) Map(String, Int32) with 2 rows of empty maps -> key child length=0. +offsets = pa.array([0, 0, 0], type=pa.int32()) # two empty maps +items = pa.array([], type=pa.int32()) +map_col = pa.MapArray.from_arrays(offsets, zero_byte_string_array(), items) +tbl_b = pa.table({"id": pa.array([1, 2], type=pa.int32()), "m": map_col}) +open(f"{out}/empty_map_keys.arrow", "wb").write(write_arrow(tbl_b)) + +# (c) Array(String) with 2 rows of empty arrays -> string child length=0. +list_offsets = pa.array([0, 0, 0], type=pa.int32()) +list_col = pa.ListArray.from_arrays(list_offsets, zero_byte_string_array()) +tbl_c = pa.table({"id": pa.array([1, 2], type=pa.int32()), "a": list_col}) +open(f"{out}/empty_list_strings.arrow", "wb").write(write_arrow(tbl_c)) +PYEOF + +# (a) Should return 0 rows with no exception. +$CLICKHOUSE_LOCAL --query \ + "SELECT count() FROM file('${TMP_DIR}/empty_string.arrow', Arrow)" + +# (b) Should return the two empty-map rows. +$CLICKHOUSE_LOCAL --query \ + "SELECT id, m FROM file('${TMP_DIR}/empty_map_keys.arrow', Arrow) ORDER BY id" + +# (c) Should return the two empty-array rows. +$CLICKHOUSE_LOCAL --query \ + "SELECT id, a FROM file('${TMP_DIR}/empty_list_strings.arrow', Arrow) ORDER BY id" From 4c562cd8e924dac1ab5fe1bb635c0342b9e7a195 Mon Sep 17 00:00:00 2001 From: robot-clickhouse Date: Mon, 22 Jun 2026 16:06:03 +0000 Subject: [PATCH 064/146] Backport #107041 to 26.3: Fix Block structure mismatch in UnionStep on a Sparse-vs-full divergent branch --- src/Core/Block.cpp | 11 +++++- ...on_branch_sparse_header_mismatch.reference | 1 + ...28_union_branch_sparse_header_mismatch.sql | 38 +++++++++++++++++++ 3 files changed, 49 insertions(+), 1 deletion(-) create mode 100644 tests/queries/0_stateless/04328_union_branch_sparse_header_mismatch.reference create mode 100644 tests/queries/0_stateless/04328_union_branch_sparse_header_mismatch.sql diff --git a/src/Core/Block.cpp b/src/Core/Block.cpp index cdef2c555d08..c4322b712339 100644 --- a/src/Core/Block.cpp +++ b/src/Core/Block.cpp @@ -81,7 +81,16 @@ static ReturnType checkColumnStructure(const ColumnWithTypeAndName & actual, con const IColumn * actual_column = actual.column.get(); const IColumn * expected_column = expected.column.get(); - /// If we allow to materialize columns, omit Const, Replicated and Sparse columns. + /// A Sparse column is structurally equal to the full column of the same type it wraps, and every + /// consumer can process sparse, so it must compare equal to a non-sparse column even in the + /// strict path. Unwrap Sparse on both sides here; Const and Replicated stay strict unless + /// allow_materialize. + if (const auto * actual_sparse = typeid_cast(actual_column)) + actual_column = &actual_sparse->getValuesColumn(); + if (const auto * expected_sparse = typeid_cast(expected_column)) + expected_column = &expected_sparse->getValuesColumn(); + + /// If we allow to materialize columns, omit Const and Replicated columns too. if (allow_materialize) { actual_column = getActualColumn(actual_column); diff --git a/tests/queries/0_stateless/04328_union_branch_sparse_header_mismatch.reference b/tests/queries/0_stateless/04328_union_branch_sparse_header_mismatch.reference new file mode 100644 index 000000000000..5b1c6f8c3029 --- /dev/null +++ b/tests/queries/0_stateless/04328_union_branch_sparse_header_mismatch.reference @@ -0,0 +1 @@ +10010 10 diff --git a/tests/queries/0_stateless/04328_union_branch_sparse_header_mismatch.sql b/tests/queries/0_stateless/04328_union_branch_sparse_header_mismatch.sql new file mode 100644 index 000000000000..576e87da0ed2 --- /dev/null +++ b/tests/queries/0_stateless/04328_union_branch_sparse_header_mismatch.sql @@ -0,0 +1,38 @@ +-- https://github.com/ClickHouse/ClickHouse/issues/107273 +-- Tags: no-random-merge-tree-settings + +DROP TABLE IF EXISTS t_sp; +DROP TABLE IF EXISTS src; +DROP TABLE IF EXISTS realt; +DROP TABLE IF EXISTS dst; +DROP VIEW IF EXISTS mv; + +-- Source data whose Bool column serializes Sparse (mostly default values). +CREATE TABLE t_sp (id UInt64, _cdc_is_deleted Bool) +ENGINE = MergeTree ORDER BY id SETTINGS ratio_of_defaults_for_sparse_serialization = 0.95; +INSERT INTO t_sp SELECT number, 0 FROM numbers(10000); + +CREATE TABLE src (id UInt64, _cdc_is_deleted Bool) ENGINE = MergeTree ORDER BY id; +CREATE TABLE realt (id UInt64, _cdc_is_deleted Bool) ENGINE = MergeTree ORDER BY id; +INSERT INTO realt SELECT number, 1 FROM numbers(10); -- full serialization +CREATE TABLE dst (id UInt64, _cdc_is_deleted Bool) ENGINE = MergeTree ORDER BY id; + +-- UNION nested inside a FROM subquery: one branch's header is Sparse, the sibling's is full. +CREATE MATERIALIZED VIEW mv TO dst AS + SELECT id, _cdc_is_deleted FROM ( + SELECT id, _cdc_is_deleted FROM src + UNION ALL + SELECT id, _cdc_is_deleted FROM realt + ); + +-- The inserted block carries a Sparse _cdc_is_deleted; previously threw +-- "Block structure mismatch in UnionStep stream" while pushing to the view. +INSERT INTO src SELECT id, _cdc_is_deleted FROM t_sp; + +SELECT count(), sum(_cdc_is_deleted) FROM dst; + +DROP VIEW mv; +DROP TABLE dst; +DROP TABLE realt; +DROP TABLE src; +DROP TABLE t_sp; From 352c0ee4e4ace68714d78918f309c0dd9e9df090 Mon Sep 17 00:00:00 2001 From: robot-clickhouse Date: Mon, 22 Jun 2026 17:48:16 +0000 Subject: [PATCH 065/146] Backport #108124 to 26.3: Always run access cache batch-finished handlers, even on empty batch --- src/Access/AccessChangesNotifier.cpp | 11 ++-- src/Access/AccessChangesNotifier.h | 10 ++-- src/Access/QuotaCache.h | 2 +- src/Access/SettingsProfilesCache.h | 2 +- .../tests/gtest_access_changes_notifier.cpp | 50 ++++++++++++++++++- 5 files changed, 61 insertions(+), 14 deletions(-) diff --git a/src/Access/AccessChangesNotifier.cpp b/src/Access/AccessChangesNotifier.cpp index ada0b0465225..a43ea74b2f78 100644 --- a/src/Access/AccessChangesNotifier.cpp +++ b/src/Access/AccessChangesNotifier.cpp @@ -102,14 +102,12 @@ void AccessChangesNotifier::sendNotifications() /// Only one thread can send notification at any time. std::lock_guard sending_notifications_lock{sending_notifications}; - bool sent_any = false; std::unique_lock queue_lock{queue_mutex}; while (!queue.empty()) { auto event = std::move(queue.front()); queue.pop(); queue_lock.unlock(); - sent_any = true; std::vector current_handlers; { @@ -136,10 +134,11 @@ void AccessChangesNotifier::sendNotifications() } queue_lock.unlock(); - if (!sent_any) - return; - - /// Queue drained (e.g. a full refresh); run the coalesced per-batch work. Copied out so handlers + /// Run the coalesced per-batch work unconditionally, even when this call drained nothing: a + /// per-batch handler whose previous rebuild threw left its work pending, and the per-entity flag + /// guarding that rebuild is only cleared on success, so the retry must not depend on a fresh event + /// being queued (an up-to-date `SYSTEM RELOAD USERS` diffs to zero and would otherwise never retry). + /// Each handler is cheap when nothing is pending (a mutex + a flag check). Copied out so handlers /// run without `handlers->mutex` held. std::vector batch_finished_handlers; { diff --git a/src/Access/AccessChangesNotifier.h b/src/Access/AccessChangesNotifier.h index 250d58a7ec19..57156a75fe8b 100644 --- a/src/Access/AccessChangesNotifier.h +++ b/src/Access/AccessChangesNotifier.h @@ -38,10 +38,12 @@ class AccessChangesNotifier using OnBatchFinishedHandler = std::function; - /// Subscribes for the end of a notification batch: the handler is called once after sendNotifications() - /// has dispatched all the per-entity notifications queued so far. Lets subscribers coalesce expensive - /// per-entity recomputations into a single one per batch (e.g. a full refresh delivers one notification - /// per entity, but the derived state only needs to be rebuilt once). + /// Subscribes for the end of a notification batch: the handler is called once per sendNotifications() + /// after all the per-entity notifications queued so far have been dispatched. Lets subscribers coalesce + /// expensive per-entity recomputations into a single one per batch (e.g. a full refresh delivers one + /// notification per entity, but the derived state only needs to be rebuilt once). The handler is also + /// called when the batch was empty, so a recomputation that threw (and left its work pending) is retried + /// on the next call without waiting for a fresh access change; it must therefore be cheap when idle. scope_guard subscribeForBatchFinished(const OnBatchFinishedHandler & handler); /// Called by access storages after a new access entity has been added. diff --git a/src/Access/QuotaCache.h b/src/Access/QuotaCache.h index 7a47eb539e7c..4c4cd955bac1 100644 --- a/src/Access/QuotaCache.h +++ b/src/Access/QuotaCache.h @@ -65,7 +65,7 @@ class QuotaCache std::unordered_map all_quotas; bool all_quotas_read = false; /// Set by the per-entity handler; the rebuild is coalesced to once per notification batch. - bool need_choose_quota = false; + bool need_choose_quota TSA_GUARDED_BY(mutex) = false; scope_guard subscription; scope_guard batch_subscription; std::map> enabled_quotas; diff --git a/src/Access/SettingsProfilesCache.h b/src/Access/SettingsProfilesCache.h index a238774e040e..3c27ddebc5fa 100644 --- a/src/Access/SettingsProfilesCache.h +++ b/src/Access/SettingsProfilesCache.h @@ -49,7 +49,7 @@ class SettingsProfilesCache std::unordered_map profiles_by_name; bool all_profiles_read = false; /// Set by the per-entity handler; the rebuild is coalesced to once per notification batch. - bool need_merge_settings_and_constraints = false; + bool need_merge_settings_and_constraints TSA_GUARDED_BY(mutex) = false; scope_guard subscription; scope_guard batch_subscription; std::map> enabled_settings; diff --git a/src/Access/tests/gtest_access_changes_notifier.cpp b/src/Access/tests/gtest_access_changes_notifier.cpp index 37e4625df821..56208513b06d 100644 --- a/src/Access/tests/gtest_access_changes_notifier.cpp +++ b/src/Access/tests/gtest_access_changes_notifier.cpp @@ -50,7 +50,53 @@ TEST(AccessChangesNotifier, BatchFinishedCoalescesRecompute) EXPECT_EQ(per_entity_calls, num_entities + 1); EXPECT_EQ(batch_calls, 2u); - /// An empty queue must not invoke the batch hook (nothing changed, nothing to rebuild). + /// An empty queue still invokes the batch hook: real handlers early-return on their per-entity + /// flag, so this is cheap, and it must keep firing so a rebuild that previously threw is retried + /// without waiting for a fresh access change (see BatchFinishedRetriedAfterThrow). notifier.sendNotifications(); - EXPECT_EQ(batch_calls, 2u); + EXPECT_EQ(batch_calls, 3u); +} + +/// A per-batch rebuild that throws must not lose the pending work: the caches clear their "needs +/// rebuild" flag only on success, so the notifier has to keep invoking batch-finished handlers on +/// later sendNotifications() calls - including ones that drain no events - otherwise the derived +/// state stays stale until some unrelated access change happens to queue a notification (and an +/// up-to-date SYSTEM RELOAD USERS, which diffs to zero, would never trigger the retry). +TEST(AccessChangesNotifier, BatchFinishedRetriedAfterThrow) +{ + AccessChangesNotifier notifier; + + bool need_rebuild = false; /// mirrors RowPolicyCache::need_mix_filters and friends + size_t rebuilds = 0; + bool fail_next_rebuild = true; + + auto entity_subscription = notifier.subscribeForChanges( + AccessEntityType::ROW_POLICY, + [&](const UUID &, const AccessEntityPtr &) { need_rebuild = true; }); + + auto batch_subscription = notifier.subscribeForBatchFinished( + [&] + { + if (!need_rebuild) + return; + if (fail_next_rebuild) + { + fail_next_rebuild = false; + throw std::runtime_error("rebuild failed"); /// caught and logged by sendNotifications() + } + ++rebuilds; + need_rebuild = false; /// cleared only after a successful rebuild + }); + + notifier.onEntityRemoved(UUIDHelpers::generateV4(), AccessEntityType::ROW_POLICY); + notifier.sendNotifications(); /// first attempt throws; the work stays pending + EXPECT_EQ(rebuilds, 0u); + + /// No new event is queued, yet the pending rebuild must still be retried and now succeed. + notifier.sendNotifications(); + EXPECT_EQ(rebuilds, 1u); + + /// Once it has succeeded an empty flush is a no-op, guarded by the handler's own flag. + notifier.sendNotifications(); + EXPECT_EQ(rebuilds, 1u); } From 682426ac22a43db103569e65629af7b72e000a09 Mon Sep 17 00:00:00 2001 From: robot-clickhouse Date: Mon, 22 Jun 2026 17:51:19 +0000 Subject: [PATCH 066/146] Backport #107868 to 26.3: Fix `set` skip index not pruning granules over `LowCardinality` columns --- src/Storages/MergeTree/MergeTreeIndexSet.cpp | 7 ++- ...dex_lowcardinality_hastoken_like.reference | 4 ++ ...kip_index_lowcardinality_hastoken_like.sql | 63 +++++++++++++++++++ 3 files changed, 73 insertions(+), 1 deletion(-) create mode 100644 tests/queries/0_stateless/04357_set_skip_index_lowcardinality_hastoken_like.reference create mode 100644 tests/queries/0_stateless/04357_set_skip_index_lowcardinality_hastoken_like.sql diff --git a/src/Storages/MergeTree/MergeTreeIndexSet.cpp b/src/Storages/MergeTree/MergeTreeIndexSet.cpp index a01e94f21a3e..9a69ff4fe7df 100644 --- a/src/Storages/MergeTree/MergeTreeIndexSet.cpp +++ b/src/Storages/MergeTree/MergeTreeIndexSet.cpp @@ -3,6 +3,7 @@ #include #include +#include #include #include @@ -564,7 +565,11 @@ const ActionsDAG::Node & MergeTreeIndexConditionSet::traverseDAG(const ActionsDA /// "It's a bug!" exception from `__bitWrapperFunc` at execution time. Fall back to /// `UNKNOWN_FIELD` so that the index does not prune granules and the query goes /// through the regular filter path. - if (WhichDataType(removeNullable(atom_node_ptr->result_type)).isInteger()) + const auto & atom_result_type = atom_node_ptr->result_type; + const bool is_integer_atom = WhichDataType(atom_result_type).isLowCardinality() + ? WhichDataType(removeLowCardinality(atom_result_type)).isInteger() + : WhichDataType(removeNullable(atom_result_type)).isInteger(); + if (is_integer_atom) { auto bit_wrapper_function = FunctionFactory::instance().get("__bitWrapperFunc", context); result_node = &result_dag.addFunction(bit_wrapper_function, {atom_node_ptr}, {}); diff --git a/tests/queries/0_stateless/04357_set_skip_index_lowcardinality_hastoken_like.reference b/tests/queries/0_stateless/04357_set_skip_index_lowcardinality_hastoken_like.reference new file mode 100644 index 000000000000..50c95b31e67e --- /dev/null +++ b/tests/queries/0_stateless/04357_set_skip_index_lowcardinality_hastoken_like.reference @@ -0,0 +1,4 @@ +Granules: 1/16 +Granules: 1/16 +1 +1 diff --git a/tests/queries/0_stateless/04357_set_skip_index_lowcardinality_hastoken_like.sql b/tests/queries/0_stateless/04357_set_skip_index_lowcardinality_hastoken_like.sql new file mode 100644 index 000000000000..9049748cbf49 --- /dev/null +++ b/tests/queries/0_stateless/04357_set_skip_index_lowcardinality_hastoken_like.sql @@ -0,0 +1,63 @@ +-- Regression test for a `set` skip index that stops pruning granules for +-- function predicates (`hasToken`, `LIKE`, ...) over a non-nullable +-- `LowCardinality(String)` column. +-- +-- Caused by https://github.com/ClickHouse/ClickHouse/pull/105384 . That PR +-- guards the `__bitWrapperFunc` wrapping in +-- `MergeTreeIndexConditionSet::traverseDAG` with +-- `WhichDataType(removeNullable(atom->result_type)).isInteger()`, falling back +-- to `UNKNOWN_FIELD` (no pruning) otherwise. The check strips `Nullable` but +-- not `LowCardinality`: for a `LowCardinality(String)` column, `hasToken` and +-- `LIKE` produce `LowCardinality(UInt8)`, which is not "integer", so every atom +-- becomes `UNKNOWN_FIELD` and the `set` index prunes nothing -- turning an +-- index lookup into a full scan. A plain `String` column returns `UInt8` and is +-- unaffected, which is why the regression is specific to `LowCardinality`. +-- +-- The index lookup is correct (the wrapped `LowCardinality(UInt8)` atom matches +-- the granule's stored values), so results are unchanged -- only pruning is +-- lost. Hence this test asserts on the number of granules the index keeps, via +-- `EXPLAIN indexes = 1`, rather than on query results. + +DROP TABLE IF EXISTS t_set_lc; + +CREATE TABLE t_set_lc +( + s LowCardinality(String), + INDEX idx_set s TYPE set(100) GRANULARITY 1 +) +ENGINE = MergeTree +ORDER BY tuple() +SETTINGS index_granularity = 1; + +-- 16 rows, one distinct value per granule (GRANULARITY 1, index_granularity 1), +-- so a predicate matching a single value must leave exactly one granule. +INSERT INTO t_set_lc SELECT 'svc' || leftPad(toString(number), 4, '0') FROM numbers(16); + +-- Pin the settings that gate skip-index analysis so the stateless runner's +-- setting randomization cannot mask the regression. + +-- `hasToken`: the set index must prune to a single granule (expect `1/16`). +SELECT trimLeft(explain) +FROM +( + EXPLAIN indexes = 1 + SELECT count() FROM t_set_lc WHERE hasToken(s, 'svc0003') + SETTINGS use_skip_indexes = 1, use_skip_indexes_on_data_read = 0, use_query_condition_cache = 0 +) +WHERE explain LIKE '%Granules:%'; + +-- `LIKE`: same expectation. +SELECT trimLeft(explain) +FROM +( + EXPLAIN indexes = 1 + SELECT count() FROM t_set_lc WHERE s LIKE '%svc0003%' + SETTINGS use_skip_indexes = 1, use_skip_indexes_on_data_read = 0, use_query_condition_cache = 0 +) +WHERE explain LIKE '%Granules:%'; + +-- Results stay correct regardless of pruning (sanity check). +SELECT count() FROM t_set_lc WHERE hasToken(s, 'svc0003'); +SELECT count() FROM t_set_lc WHERE s LIKE '%svc0003%'; + +DROP TABLE t_set_lc; From e6b97054a3e74508df49578aa1a1742d1866402c Mon Sep 17 00:00:00 2001 From: robot-clickhouse Date: Mon, 22 Jun 2026 19:26:42 +0000 Subject: [PATCH 067/146] Backport #107412 to 26.3: validate event type bounds in windowFunnel state deserialization --- .../AggregateFunctionWindowFunnel.cpp | 24 ++++++++++++++++++- ..._funnel_deserialize_event_bounds.reference | 3 +++ ...window_funnel_deserialize_event_bounds.sql | 17 +++++++++++++ 3 files changed, 43 insertions(+), 1 deletion(-) create mode 100644 tests/queries/0_stateless/04338_window_funnel_deserialize_event_bounds.reference create mode 100644 tests/queries/0_stateless/04338_window_funnel_deserialize_event_bounds.sql diff --git a/src/AggregateFunctions/AggregateFunctionWindowFunnel.cpp b/src/AggregateFunctions/AggregateFunctionWindowFunnel.cpp index 78dba6707ac0..30ec8cd1f952 100644 --- a/src/AggregateFunctions/AggregateFunctionWindowFunnel.cpp +++ b/src/AggregateFunctions/AggregateFunctionWindowFunnel.cpp @@ -26,6 +26,7 @@ namespace ErrorCodes extern const int NUMBER_OF_ARGUMENTS_DOESNT_MATCH; extern const int TOO_LARGE_ARRAY_SIZE; extern const int BAD_ARGUMENTS; + extern const int INCORRECT_DATA; } namespace @@ -577,7 +578,28 @@ class AggregateFunctionWindowFunnel final void deserialize(AggregateDataPtr __restrict place, ReadBuffer & buf, std::optional /* version */, Arena *) const override { - this->data(place).deserialize(buf); + auto & data = this->data(place); + data.deserialize(buf); + + /// Event types come from untrusted serialized state. getEventLevel* uses (event - 1) to index + /// events_timestamp / event_sequences, both sized events_size, so an out-of-range event would + /// read and write out of bounds. Valid event types are [1, events_size]; 0 is the no-event + /// sentinel produced only with strict_order, so it is accepted only in that mode. + const UInt8 min_event = strict_order ? 0 : 1; + for (const auto & event : data.events_list) + { + UInt8 event_type = 0; + if constexpr (Data::strict_once_enabled) + event_type = event.event_type; + else + event_type = event.second; + + if (event_type < min_event || event_type > events_size) + throw Exception( + ErrorCodes::INCORRECT_DATA, + "Invalid event type {} in the state of function {}, must be in range [{}, {}]", + static_cast(event_type), getName(), static_cast(min_event), static_cast(events_size)); + } } void insertResultInto(AggregateDataPtr __restrict place, IColumn & to, Arena *) const override diff --git a/tests/queries/0_stateless/04338_window_funnel_deserialize_event_bounds.reference b/tests/queries/0_stateless/04338_window_funnel_deserialize_event_bounds.reference new file mode 100644 index 000000000000..e22493782f08 --- /dev/null +++ b/tests/queries/0_stateless/04338_window_funnel_deserialize_event_bounds.reference @@ -0,0 +1,3 @@ +1 +0 +0 diff --git a/tests/queries/0_stateless/04338_window_funnel_deserialize_event_bounds.sql b/tests/queries/0_stateless/04338_window_funnel_deserialize_event_bounds.sql new file mode 100644 index 000000000000..c75e6d9b7fce --- /dev/null +++ b/tests/queries/0_stateless/04338_window_funnel_deserialize_event_bounds.sql @@ -0,0 +1,17 @@ +-- A valid windowFunnel state (one event of condition 1) finalizes normally. +SELECT finalizeAggregation(CAST(unhex('0101000000000000000000000001') AS AggregateFunction(windowFunnel(1), UInt32, UInt8, UInt8))); + +-- An event type past the number of conditions (events_size = 2) used to index events_timestamp out of bounds. +SELECT finalizeAggregation(CAST(unhex('01010000000000000000000000FF') AS AggregateFunction(windowFunnel(1), UInt32, UInt8, UInt8))); -- { serverError INCORRECT_DATA } + +-- Event type 0 is a no-event sentinel only valid with strict_order; without it the index goes before the buffer. +SELECT finalizeAggregation(CAST(unhex('0101000000000000000000000000') AS AggregateFunction(windowFunnel(1), UInt32, UInt8, UInt8))); -- { serverError INCORRECT_DATA } + +-- With strict_order that same event type 0 is the legitimate no-event sentinel, so the stored state must still finalize. +SELECT finalizeAggregation(CAST(unhex('0101000000000000000000000000') AS AggregateFunction(windowFunnel(1, 'strict_order'), UInt32, UInt8, UInt8))); + +-- Same check for the strict_once variant, whose state carries a per-event unique id. +SELECT finalizeAggregation(CAST(unhex('01010000000000000000000000FF0100000000000000') AS AggregateFunction(windowFunnel(1, 'strict_once'), UInt32, UInt8, UInt8))); -- { serverError INCORRECT_DATA } + +-- The strict_once layout also keeps event type 0 valid together with strict_order. +SELECT finalizeAggregation(CAST(unhex('01010000000000000000000000000100000000000000') AS AggregateFunction(windowFunnel(1, 'strict_order', 'strict_once'), UInt32, UInt8, UInt8))); From 40c661ea7c345a39b8735b30d06051302c795fb1 Mon Sep 17 00:00:00 2001 From: robot-clickhouse Date: Mon, 22 Jun 2026 20:55:56 +0000 Subject: [PATCH 068/146] Update autogenerated version to 26.3.14.49 and contributors --- cmake/autogenerated_versions.txt | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/cmake/autogenerated_versions.txt b/cmake/autogenerated_versions.txt index 87263c2b3641..eb9993a3c9c1 100644 --- a/cmake/autogenerated_versions.txt +++ b/cmake/autogenerated_versions.txt @@ -2,11 +2,11 @@ # NOTE: VERSION_REVISION has nothing common with DBMS_TCP_PROTOCOL_VERSION, # only DBMS_TCP_PROTOCOL_VERSION should be incremented on protocol changes. -SET(VERSION_REVISION 54521) +SET(VERSION_REVISION 54522) SET(VERSION_MAJOR 26) SET(VERSION_MINOR 3) -SET(VERSION_PATCH 14) -SET(VERSION_GITHASH 27ae4e9fe0e3f97f012b3be293caf42a60d08747) -SET(VERSION_DESCRIBE v26.3.14.1-lts) -SET(VERSION_STRING 26.3.14.1) +SET(VERSION_PATCH 15) +SET(VERSION_GITHASH 9d65f3bceb7fd2e3c182df60fa9760ff765058b5) +SET(VERSION_DESCRIBE v26.3.15.1-lts) +SET(VERSION_STRING 26.3.15.1) # end of autochange From 292460662a7e6bf95a806ddf718f43635ace2aa1 Mon Sep 17 00:00:00 2001 From: robot-clickhouse Date: Mon, 22 Jun 2026 22:49:54 +0000 Subject: [PATCH 069/146] Backport #107579 to 26.3: Add missing bounds checks in Elf.cpp and DWARFBlockInputFormat --- src/Common/Elf.cpp | 50 ++++++++++---- src/Common/Elf.h | 1 + .../Formats/Impl/DWARFBlockInputFormat.cpp | 34 +++++++--- ...04340_malformed_elf_dwarf_bounds.reference | 4 ++ .../04340_malformed_elf_dwarf_bounds.sh | 65 +++++++++++++++++++ 5 files changed, 134 insertions(+), 20 deletions(-) create mode 100644 tests/queries/0_stateless/04340_malformed_elf_dwarf_bounds.reference create mode 100755 tests/queries/0_stateless/04340_malformed_elf_dwarf_bounds.sh diff --git a/src/Common/Elf.cpp b/src/Common/Elf.cpp index 769bd5b9c9ec..d5ebc070592a 100644 --- a/src/Common/Elf.cpp +++ b/src/Common/Elf.cpp @@ -14,6 +14,13 @@ namespace ErrorCodes } +/// Whether [offset, offset + length) is within `total` bytes, computed so it can't overflow. +static bool rangeWithin(uint64_t offset, uint64_t length, uint64_t total) +{ + return offset <= total && length <= total - offset; +} + + Elf::Elf(const std::string & path_) { in.emplace(path_, 0); @@ -46,7 +53,7 @@ void Elf::init(const char * data, size_t size, const std::string & path_) if (!section_header_offset || !section_header_num_entries - || section_header_offset + section_header_num_entries * sizeof(ElfSectionHeader) > elf_size) + || !rangeWithin(section_header_offset, section_header_num_entries * sizeof(ElfSectionHeader), elf_size)) throw Exception(ErrorCodes::CANNOT_PARSE_ELF, "The ELF '{}' is truncated (section header points after end of file)", path); section_headers = reinterpret_cast(mapped + section_header_offset); @@ -61,10 +68,16 @@ void Elf::init(const char * data, size_t size, const std::string & path_) throw Exception(ErrorCodes::CANNOT_PARSE_ELF, "The ELF '{}' doesn't have string table with section names", path); uint64_t section_names_offset = section_names_strtab->header.offset; - if (section_names_offset >= elf_size) + uint64_t section_names_table_size = section_names_strtab->header.size; + if (!rangeWithin(section_names_offset, section_names_table_size, elf_size)) throw Exception(ErrorCodes::CANNOT_PARSE_ELF, "The ELF '{}' is truncated (section names string table points after end of file)", path); section_names = reinterpret_cast(mapped + section_names_offset); + section_names_size = section_names_table_size; + + /// Require the trailing NUL (guaranteed by the ELF spec) so name() can return C strings without re-scanning. + if (section_names_size == 0 || section_names[section_names_size - 1] != '\0') + throw Exception(ErrorCodes::CANNOT_PARSE_ELF, "The ELF '{}' has a section names string table that is not zero-terminated", path); /// Get program headers @@ -73,7 +86,7 @@ void Elf::init(const char * data, size_t size, const std::string & path_) if (!program_header_offset || !program_header_num_entries - || program_header_offset + program_header_num_entries * sizeof(ElfProgramHeader) > elf_size) + || !rangeWithin(program_header_offset, program_header_num_entries * sizeof(ElfProgramHeader), elf_size)) throw Exception(ErrorCodes::CANNOT_PARSE_ELF, "The ELF '{}' is truncated (program header points after end of file)", path); program_headers = reinterpret_cast(mapped + program_header_offset); @@ -92,8 +105,8 @@ bool Elf::iterateSections(std::function elf_size) + /// Skip sections that span past the end of file. + if (!rangeWithin(section.header.offset, section.header.size, elf_size)) continue; if (pred(section, idx)) @@ -152,7 +165,11 @@ String Elf::getBuildID() const const ElfProgramHeader & phdr = program_headers[idx]; if (phdr.type == ProgramHeaderType::NOTE) + { + if (!rangeWithin(phdr.offset, phdr.filesz, elf_size)) + continue; return getBuildID(mapped + phdr.offset, phdr.filesz); + } } return {}; @@ -164,14 +181,22 @@ String Elf::getBuildID(const char * nhdr_pos, size_t size) while (nhdr_pos < nhdr_end) { + if (static_cast(nhdr_end - nhdr_pos) < sizeof(ElfNameHeader)) + break; + ElfNameHeader nhdr = unalignedLoad(nhdr_pos); + nhdr_pos += sizeof(ElfNameHeader); + + if (nhdr.namesz > static_cast(nhdr_end - nhdr_pos)) + break; + nhdr_pos += nhdr.namesz; + + if (nhdr.descsz > static_cast(nhdr_end - nhdr_pos)) + break; - nhdr_pos += sizeof(ElfNameHeader) + nhdr.namesz; if (nhdr.type == NameHeaderType::GNU_BUILD_ID) - { - const char * build_id = nhdr_pos; - return {build_id, nhdr.descsz}; - } + return {nhdr_pos, nhdr.descsz}; + nhdr_pos += nhdr.descsz; } @@ -192,7 +217,10 @@ const char * Elf::Section::name() const if (!elf.section_names) throw Exception(ErrorCodes::CANNOT_PARSE_ELF, "Section names are not initialized"); - /// TODO buffer overflow is possible, we may need to check strlen. + /// init() verified the table ends with a NUL, so a forward scan from any in-bounds offset stays in-bounds. + if (header.name >= elf.section_names_size) + throw Exception(ErrorCodes::CANNOT_PARSE_ELF, "Section name offset is out of bounds"); + return elf.section_names + header.name; } diff --git a/src/Common/Elf.h b/src/Common/Elf.h index f9782c375dbc..743f00cb0c7d 100644 --- a/src/Common/Elf.h +++ b/src/Common/Elf.h @@ -163,6 +163,7 @@ class Elf final const ElfSectionHeader * section_headers; const ElfProgramHeader * program_headers; const char * section_names = nullptr; + size_t section_names_size = 0; // bytes available starting at `section_names`, for bounds checking void init(const char * data, size_t size, const std::string & path_); }; diff --git a/src/Processors/Formats/Impl/DWARFBlockInputFormat.cpp b/src/Processors/Formats/Impl/DWARFBlockInputFormat.cpp index f6c65aa0c84a..2de5252c0dc3 100644 --- a/src/Processors/Formats/Impl/DWARFBlockInputFormat.cpp +++ b/src/Processors/Formats/Impl/DWARFBlockInputFormat.cpp @@ -786,8 +786,18 @@ Chunk DWARFBlockInputFormat::parseEntries(UnitState & unit) cols.push_back(std::exchange(col_linkage_name, nullptr)); break; case COL_DECL_FILE: - cols.push_back(ColumnLowCardinality::create(unit.filename_table, col_decl_file.detachIndexes(), /*is_shared*/ true)); + { + /// A unit without DW_AT_stmt_list has no filename table; use a minimal dictionary instead of null. + ColumnPtr filename_table = unit.filename_table; + if (filename_table == nullptr) + { + auto dict = ColumnString::create(); + dict->insertDefault(); + filename_table = ColumnUnique::create(std::move(dict), /*is_nullable*/ false); + } + cols.push_back(ColumnLowCardinality::create(filename_table, col_decl_file.detachIndexes(), /*is_shared*/ true)); break; + } case COL_DECL_LINE: cols.push_back(std::exchange(col_decl_line, nullptr)); break; @@ -859,10 +869,11 @@ uint64_t DWARFBlockInputFormat::fetchFromDebugAddr(uint64_t addr_base, uint64_t throw Exception(ErrorCodes::CANNOT_PARSE_DWARF, "Missing .debug_addr section."); if (addr_base == UINT64_MAX) throw Exception(ErrorCodes::CANNOT_PARSE_DWARF, "Missing DW_AT_addr_base"); + uint64_t section_size = debug_addr_section->size(); + if (addr_base > section_size || idx >= (section_size - addr_base) / 8) + throw Exception(ErrorCodes::CANNOT_PARSE_DWARF, ".debug_addr offset out of bounds: addr_base {}, idx {} vs {}.", addr_base, idx, section_size); uint64_t offset = addr_base + idx * 8; - if (offset + 8 > debug_addr_section->size()) - throw Exception(ErrorCodes::CANNOT_PARSE_DWARF, ".debug_addr offset out of bounds: {} vs {}.", offset, debug_addr_section->size()); - uint64_t res; + uint64_t res = 0; memcpy(&res, debug_addr_section->data() + offset, 8); return res; } @@ -900,14 +911,19 @@ void DWARFBlockInputFormat::parseRanges( if (unit.rnglists_base == UINT64_MAX) throw Exception(ErrorCodes::CANNOT_PARSE_DWARF, "Missing DW_AT_rnglists_base"); uint64_t entry_size = unit.dwarf_unit->getFormParams().getDwarfOffsetByteSize(); + uint64_t section_size = debug_rnglists_extractor->size(); + if (entry_size == 0 || unit.rnglists_base > section_size + || offset >= (section_size - unit.rnglists_base) / entry_size) + throw Exception(ErrorCodes::CANNOT_PARSE_DWARF, "DW_FORM_rnglistx offset out of bounds: rnglists_base {}, idx {} vs {}", unit.rnglists_base, offset, section_size); uint64_t lists_offset = unit.rnglists_base + offset * entry_size; - if (lists_offset + entry_size > debug_rnglists_extractor->size()) - throw Exception(ErrorCodes::CANNOT_PARSE_DWARF, "DW_FORM_rnglistx offset out of bounds: {} vs {}", lists_offset, debug_rnglists_extractor->size()); - offset = 0; - memcpy(&offset, debug_rnglists_extractor->getData().data() + lists_offset, entry_size); + uint64_t list_entry = 0; + memcpy(&list_entry, debug_rnglists_extractor->getData().data() + lists_offset, entry_size); - offset += unit.rnglists_base; + /// The offset read from the table is untrusted; the subtraction also stops rnglists_base + list_entry from wrapping. + if (list_entry > section_size - unit.rnglists_base) + throw Exception(ErrorCodes::CANNOT_PARSE_DWARF, "DW_FORM_rnglistx points out of bounds: rnglists_base {}, entry {} vs {}", unit.rnglists_base, list_entry, section_size); + offset = unit.rnglists_base + list_entry; } llvm::DWARFDebugRnglist list; diff --git a/tests/queries/0_stateless/04340_malformed_elf_dwarf_bounds.reference b/tests/queries/0_stateless/04340_malformed_elf_dwarf_bounds.reference new file mode 100644 index 000000000000..0d72c661a03d --- /dev/null +++ b/tests/queries/0_stateless/04340_malformed_elf_dwarf_bounds.reference @@ -0,0 +1,4 @@ +overflow_shoff: rejected +overflow_phoff: rejected +too_small: rejected +no_sections: rejected diff --git a/tests/queries/0_stateless/04340_malformed_elf_dwarf_bounds.sh b/tests/queries/0_stateless/04340_malformed_elf_dwarf_bounds.sh new file mode 100755 index 000000000000..116a1287b2f2 --- /dev/null +++ b/tests/queries/0_stateless/04340_malformed_elf_dwarf_bounds.sh @@ -0,0 +1,65 @@ +#!/usr/bin/env bash +# Tags: no-fasttest, no-msan +# no-fasttest: the DWARF format requires the DWARF parser, which is not built in fasttest. +# no-msan: the DWARF input format is not built under MSan (LLVM, which the DWARF parser depends on, is excluded from MSan builds), so the format is unknown there. + +CUR_DIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd) +# shellcheck source=../shell_config.sh +. "$CUR_DIR"/../shell_config.sh + +# Regression test for bounds checking in Common/Elf (used by the DWARF input format). +# Feed deliberately malformed ELF files to the DWARF format and check that each is rejected +# with a clean exception instead of reading out of bounds. The crafted offsets are close to +# UINT64_MAX so that the unchecked `offset + size` additions would overflow and pass naive +# bounds checks, leading to wild pointer dereferences. All these inputs are rejected during +# ELF header validation, before any DWARF parsing, so the test is sanitizer-safe. + +WORK_DIR="${CLICKHOUSE_TMP}/malformed_elf_$$" +mkdir -p "$WORK_DIR" + +python3 - "$WORK_DIR" <<'PY' +import struct, sys, os +work = sys.argv[1] + +def elf_header(shoff, shnum, phoff, phnum, shstrndx=0): + # 64-byte ELF64 header. + return (b'\x7fELF' + bytes(12) + + struct.pack('/dev/null 2>"${WORK_DIR}/err" + then + echo "${name}: UNEXPECTED SUCCESS" + elif grep -q "CANNOT_PARSE_ELF" "${WORK_DIR}/err" + then + echo "${name}: rejected" + else + echo "${name}: UNEXPECTED ERROR" + cat "${WORK_DIR}/err" + fi +done + +rm -rf "$WORK_DIR" From 569a137f6c4e01758a956fc38b654180d7155e1e Mon Sep 17 00:00:00 2001 From: robot-clickhouse Date: Tue, 23 Jun 2026 05:39:35 +0000 Subject: [PATCH 070/146] Backport #106867 to 26.3: Fix "Too many marks" for text index on an empty merged part --- src/Storages/MergeTree/MergeTask.cpp | 1 + src/Storages/MergeTree/MutateTask.cpp | 1 + src/Storages/MergeTree/TextIndexUtils.cpp | 15 +++++++-- src/Storages/MergeTree/TextIndexUtils.h | 2 ++ ...t_index_empty_part_marks_prewarm.reference | 1 + ...26_text_index_empty_part_marks_prewarm.sql | 31 +++++++++++++++++++ 6 files changed, 49 insertions(+), 2 deletions(-) create mode 100644 tests/queries/0_stateless/04326_text_index_empty_part_marks_prewarm.reference create mode 100644 tests/queries/0_stateless/04326_text_index_empty_part_marks_prewarm.sql diff --git a/src/Storages/MergeTree/MergeTask.cpp b/src/Storages/MergeTree/MergeTask.cpp index 3a27828ae8c9..5f04153f2a24 100644 --- a/src/Storages/MergeTree/MergeTask.cpp +++ b/src/Storages/MergeTree/MergeTask.cpp @@ -1939,6 +1939,7 @@ bool MergeTask::MergeTextIndexStage::prepare() const auto task = std::make_unique( std::move(segments), global_ctx->new_data_part, + global_ctx->rows_written, index_ptr, global_ctx->merged_part_offsets, reader_settings, diff --git a/src/Storages/MergeTree/MutateTask.cpp b/src/Storages/MergeTree/MutateTask.cpp index d1210d853991..e13c5f4fa4e3 100644 --- a/src/Storages/MergeTree/MutateTask.cpp +++ b/src/Storages/MergeTree/MutateTask.cpp @@ -1672,6 +1672,7 @@ void PartMergerWriter::finalizeTempProjectionsAndIndexes() auto merge_task = std::make_unique( std::move(segments), ctx->new_data_part, + (*ctx->mutate_entry)->rows_written, index, /*merged_part_offsets=*/ nullptr, reader_settings, diff --git a/src/Storages/MergeTree/TextIndexUtils.cpp b/src/Storages/MergeTree/TextIndexUtils.cpp index 039566ceae3c..72d14cd26ea9 100644 --- a/src/Storages/MergeTree/TextIndexUtils.cpp +++ b/src/Storages/MergeTree/TextIndexUtils.cpp @@ -207,12 +207,14 @@ void BuildTextIndexTransform::writeTemporarySegment(size_t i) MergeTextIndexesTask::MergeTextIndexesTask( std::vector segments_, MergeTreeMutableDataPartPtr new_data_part_, + size_t num_rows_, MergeTreeIndexPtr index_ptr_, std::shared_ptr merged_part_offsets_, const MergeTreeReaderSettings & reader_settings_, const MergeTreeWriterSettings & writer_settings_) : segments(std::move(segments_)) , new_data_part(std::move(new_data_part_)) + , num_rows(num_rows_) , index_ptr(std::move(index_ptr_)) , merged_part_offsets(std::move(merged_part_offsets_)) , writer_settings(writer_settings_) @@ -397,9 +399,18 @@ bool MergeTextIndexesTask::executeStep() is_initialized = true; initializeQueue(); /// Write marks for compatibility with other skip indexes. + /// An empty part carries no marks at all, exactly like every other skip index on an + /// empty part. Writing one here would leave the marks file with a single mark while + /// `getMarksCountForSkipIndex` reports zero, so reading the marks back (e.g. when the + /// mark cache is prewarmed on attach) fails with `Too many marks in file`. + /// The part is not finalized yet at this stage, so its `index_granularity` is empty; + /// rely on the merged row count instead. chassert(new_data_part); - bool can_use_adaptive_granularity = new_data_part->index_granularity_info.mark_type.adaptive; - writeMarks(output_streams, can_use_adaptive_granularity); + if (num_rows != 0) + { + bool can_use_adaptive_granularity = new_data_part->index_granularity_info.mark_type.adaptive; + writeMarks(output_streams, can_use_adaptive_granularity); + } } if (!queue.isValid()) diff --git a/src/Storages/MergeTree/TextIndexUtils.h b/src/Storages/MergeTree/TextIndexUtils.h index b7fa0dcbf083..9824d3b692e2 100644 --- a/src/Storages/MergeTree/TextIndexUtils.h +++ b/src/Storages/MergeTree/TextIndexUtils.h @@ -74,6 +74,7 @@ class MergeTextIndexesTask : public MergeProjectionsIndexesTask MergeTextIndexesTask( std::vector segments, MergeTreeMutableDataPartPtr new_data_part_, + size_t num_rows_, MergeTreeIndexPtr index_ptr_, std::shared_ptr merged_part_offsets_, const MergeTreeReaderSettings & reader_settings_, @@ -107,6 +108,7 @@ class MergeTextIndexesTask : public MergeProjectionsIndexesTask std::vector segments; MergeTreeMutableDataPartPtr new_data_part; + size_t num_rows; MergeTreeIndexPtr index_ptr; MergeTreeIndexTextParams params; diff --git a/tests/queries/0_stateless/04326_text_index_empty_part_marks_prewarm.reference b/tests/queries/0_stateless/04326_text_index_empty_part_marks_prewarm.reference new file mode 100644 index 000000000000..573541ac9702 --- /dev/null +++ b/tests/queries/0_stateless/04326_text_index_empty_part_marks_prewarm.reference @@ -0,0 +1 @@ +0 diff --git a/tests/queries/0_stateless/04326_text_index_empty_part_marks_prewarm.sql b/tests/queries/0_stateless/04326_text_index_empty_part_marks_prewarm.sql new file mode 100644 index 000000000000..7f7088ccede0 --- /dev/null +++ b/tests/queries/0_stateless/04326_text_index_empty_part_marks_prewarm.sql @@ -0,0 +1,31 @@ +-- Merging a part down to zero rows (e.g. by lightweight delete) leaves a text index whose marks +-- file holds a single mark, while the part reports zero marks. Prewarming the mark cache on attach +-- then failed to read that index with "Too many marks in file ... skp_idx_idx.cmrk2". + +DROP TABLE IF EXISTS tab; +CREATE TABLE tab ( + s FixedString(37), + INDEX idx s TYPE text(tokenizer = array()) +) ENGINE = MergeTree() ORDER BY tuple() +SETTINGS min_bytes_for_wide_part = 0, prewarm_mark_cache = 1; + +INSERT INTO TABLE tab (s) VALUES ('爸爸'), (97); + +DELETE FROM tab WHERE TRUE; + +INSERT INTO TABLE tab (s) VALUES (''), (''), ('see'), ('was'); +INSERT INTO TABLE tab (s) VALUES ('thought'), ('认识你很高兴'), (''), ('叫'), (x'C328'), ('叫'), ('日本'), ('哪国人'), ('日本'); +INSERT INTO TABLE tab (s) VALUES ('would'), ('\t'), ('need'), ('('), ('名字'); +INSERT INTO TABLE tab (s) SELECT CAST(number AS String) FROM numbers(5); +INSERT INTO TABLE tab (s) SELECT s FROM generateRandom('s FixedString(37)', 12734763443271340066, 25, 2) LIMIT 3; + +DELETE FROM tab WHERE TRUE; +OPTIMIZE TABLE tab FINAL; + +-- Force the startup mark-cache prewarm, which reads the text index of the empty part. +DETACH TABLE tab; +ATTACH TABLE tab; + +SELECT count() FROM tab WHERE hasAllTokens(s, 'was'); + +DROP TABLE tab; From cfb0005307afe25983c36625509db07e57528498 Mon Sep 17 00:00:00 2001 From: robot-clickhouse Date: Tue, 23 Jun 2026 11:20:35 +0000 Subject: [PATCH 071/146] Backport #107341 to 26.3: Reject deeply nested input in formats to prevent stack overflow --- base/poco/JSON/include/Poco/JSON/ParserImpl.h | 4 + base/poco/JSON/src/ParserImpl.cpp | 17 +++- contrib/orc | 2 +- src/Formats/SchemaInferenceUtils.cpp | 20 ++++- .../Impl/BSONEachRowRowInputFormat.cpp | 37 ++++++-- .../Formats/Impl/BSONEachRowRowInputFormat.h | 4 +- .../Formats/Impl/MsgPackRowInputFormat.cpp | 33 +++++-- .../Formats/Impl/MsgPackRowInputFormat.h | 2 +- .../Impl/NativeORCBlockInputFormat.cpp | 48 ++++++++-- .../Formats/Impl/Parquet/Reader.cpp | 6 +- .../Formats/Impl/Parquet/SchemaConverter.cpp | 16 +++- .../Formats/Impl/Parquet/SchemaConverter.h | 3 + ..._schema_inference_deep_recursion.reference | 4 + ...msgpack_schema_inference_deep_recursion.sh | 70 +++++++++++++++ ..._schema_inference_deep_recursion.reference | 3 + ...36_bson_schema_inference_deep_recursion.sh | 84 ++++++++++++++++++ ..._schema_inference_deep_recursion.reference | 3 + ...337_orc_schema_inference_deep_recursion.sh | 75 ++++++++++++++++ ..._schema_inference_deep_recursion.reference | 2 + ...parquet_schema_inference_deep_recursion.sh | 88 +++++++++++++++++++ ..._schema_inference_deep_recursion.reference | 2 + ...39_json_schema_inference_deep_recursion.sh | 56 ++++++++++++ ...0_datalake_schema_deep_recursion.reference | 3 + .../04340_datalake_schema_deep_recursion.sh | 72 +++++++++++++++ ...c_explicit_schema_deep_recursion.reference | 2 + ...4341_orc_explicit_schema_deep_recursion.sh | 54 ++++++++++++ 26 files changed, 678 insertions(+), 32 deletions(-) create mode 100644 tests/queries/0_stateless/04335_msgpack_schema_inference_deep_recursion.reference create mode 100755 tests/queries/0_stateless/04335_msgpack_schema_inference_deep_recursion.sh create mode 100644 tests/queries/0_stateless/04336_bson_schema_inference_deep_recursion.reference create mode 100755 tests/queries/0_stateless/04336_bson_schema_inference_deep_recursion.sh create mode 100644 tests/queries/0_stateless/04337_orc_schema_inference_deep_recursion.reference create mode 100755 tests/queries/0_stateless/04337_orc_schema_inference_deep_recursion.sh create mode 100644 tests/queries/0_stateless/04338_parquet_schema_inference_deep_recursion.reference create mode 100755 tests/queries/0_stateless/04338_parquet_schema_inference_deep_recursion.sh create mode 100644 tests/queries/0_stateless/04339_json_schema_inference_deep_recursion.reference create mode 100755 tests/queries/0_stateless/04339_json_schema_inference_deep_recursion.sh create mode 100644 tests/queries/0_stateless/04340_datalake_schema_deep_recursion.reference create mode 100755 tests/queries/0_stateless/04340_datalake_schema_deep_recursion.sh create mode 100644 tests/queries/0_stateless/04341_orc_explicit_schema_deep_recursion.reference create mode 100755 tests/queries/0_stateless/04341_orc_explicit_schema_deep_recursion.sh diff --git a/base/poco/JSON/include/Poco/JSON/ParserImpl.h b/base/poco/JSON/include/Poco/JSON/ParserImpl.h index 7a8ea647180c..6cf2451f5129 100644 --- a/base/poco/JSON/include/Poco/JSON/ParserImpl.h +++ b/base/poco/JSON/include/Poco/JSON/ParserImpl.h @@ -43,6 +43,9 @@ namespace JSON static const std::size_t JSON_PARSE_BUFFER_SIZE = 4096; static const std::size_t JSON_PARSER_STACK_SIZE = 128; static const int JSON_UNLIMITED_DEPTH = -1; + /// Default nesting limit so that parsing untrusted JSON cannot overflow the native stack via + /// Poco's recursive descent. Far above any legitimate JSON; raise per-parser with setDepth. + static const int JSON_DEFAULT_DEPTH = 1000; ParserImpl(const Handler::Ptr & pHandler = new ParseHandler, std::size_t bufSize = JSON_PARSE_BUFFER_SIZE); /// Creates JSON ParserImpl, using the given Handler and buffer size. @@ -109,6 +112,7 @@ namespace JSON struct json_stream * _pJSON; Handler::Ptr _pHandler; int _depth; + int _currentDepth = 0; char _decimalPoint; bool _allowNullByte; bool _allowComments; diff --git a/base/poco/JSON/src/ParserImpl.cpp b/base/poco/JSON/src/ParserImpl.cpp index 07e9f0a256da..b47f5c2430f0 100644 --- a/base/poco/JSON/src/ParserImpl.cpp +++ b/base/poco/JSON/src/ParserImpl.cpp @@ -37,7 +37,7 @@ namespace JSON { ParserImpl::ParserImpl(const Handler::Ptr& pHandler, std::size_t bufSize): _pJSON(new json_stream), _pHandler(pHandler), - _depth(JSON_UNLIMITED_DEPTH), + _depth(JSON_DEFAULT_DEPTH), _decimalPoint('.'), _allowNullByte(true), _allowComments(false) @@ -53,6 +53,7 @@ ParserImpl::~ParserImpl() void ParserImpl::handle(const std::string& json) { + _currentDepth = 0; if (!_allowNullByte && json.find("\\u0000") != json.npos) throw JSONException("Null bytes in strings not allowed."); @@ -139,6 +140,12 @@ void ParserImpl::stripComments(std::string& json) void ParserImpl::handleArray() { + // Enforce the configured depth limit (set via setDepth) to avoid a native-stack + // overflow on deeply nested input. _depth == JSON_UNLIMITED_DEPTH (-1) means no limit. + if (_depth != JSON_UNLIMITED_DEPTH && _currentDepth >= _depth) + throw JSONException("Maximum allowed JSON depth exceeded"); + ++_currentDepth; + json_type tok = json_peek(_pJSON); while (tok != JSON_ARRAY_END && checkError()) { @@ -148,11 +155,17 @@ void ParserImpl::handleArray() if (tok == JSON_ARRAY_END) handle(); else throw JSONException("JSON array end not found"); + + --_currentDepth; } void ParserImpl::handleObject() { + if (_depth != JSON_UNLIMITED_DEPTH && _currentDepth >= _depth) + throw JSONException("Maximum allowed JSON depth exceeded"); + ++_currentDepth; + json_type tok = json_peek(_pJSON); while (tok != JSON_OBJECT_END && checkError()) { @@ -166,6 +179,8 @@ void ParserImpl::handleObject() if (tok == JSON_OBJECT_END) handle(); else throw JSONException("JSON object end not found"); + + --_currentDepth; } diff --git a/contrib/orc b/contrib/orc index 1892a6539e65..49e965750a94 160000 --- a/contrib/orc +++ b/contrib/orc @@ -1 +1 @@ -Subproject commit 1892a6539e655280d38a8d0669214dbb9b1d0919 +Subproject commit 49e965750a94627d0ce0a3f3c781423b982cbc1d diff --git a/src/Formats/SchemaInferenceUtils.cpp b/src/Formats/SchemaInferenceUtils.cpp index 3cb226c69093..276af29a8e8e 100644 --- a/src/Formats/SchemaInferenceUtils.cpp +++ b/src/Formats/SchemaInferenceUtils.cpp @@ -22,6 +22,7 @@ #include #include +#include #include #include @@ -1169,7 +1170,10 @@ namespace bool tryReadJSONObject(ReadBuffer & buf, const FormatSettings & settings, DataTypeJSONPaths::Paths & paths, const std::vector & path, JSONInferenceInfo * json_info, size_t depth) { - if (depth > settings.max_parser_depth) + /// max_parser_depth is the primary bound but user-tunable; keep a checkStackSize backstop. + /// max_parser_depth == 0 means unlimited (matching the SQL parser), leaving only checkStackSize. + checkStackSize(); + if (settings.max_parser_depth != 0 && depth > settings.max_parser_depth) throw Exception(ErrorCodes::TOO_DEEP_RECURSION, "Maximum parse depth ({}) exceeded. Consider raising max_parser_depth setting.", settings.max_parser_depth); @@ -1339,7 +1343,11 @@ namespace template DataTypePtr tryInferDataTypeForSingleFieldImpl(ReadBuffer & buf, const FormatSettings & settings, JSONInferenceInfo * json_info, size_t depth) { - if (depth > settings.max_parser_depth) + /// The max_parser_depth limit below is the primary bound, but it is user-tunable; keep a + /// checkStackSize backstop so a raised limit cannot turn deep nesting into a stack overflow. + /// max_parser_depth == 0 means unlimited (matching the SQL parser), leaving only checkStackSize. + checkStackSize(); + if (settings.max_parser_depth != 0 && depth > settings.max_parser_depth) throw Exception(ErrorCodes::TOO_DEEP_RECURSION, "Maximum parse depth ({}) exceeded. Consider raising max_parser_depth setting.", settings.max_parser_depth); @@ -1450,6 +1458,8 @@ void transformInferredJSONTypesFromDifferentFilesIfNeeded(DataTypePtr & first, D void transformFinalInferredJSONTypeIfNeededImpl(DataTypePtr & data_type, const FormatSettings & settings, JSONInferenceInfo * json_info, bool remain_nothing_types = false) { + checkStackSize(); + if (!data_type) return; @@ -1666,6 +1676,10 @@ DataTypePtr tryInferDataTypeForSingleJSONField(std::string_view field, const For static DataTypePtr adjustNullableRecursively(DataTypePtr type, bool make_nullable, const FormatSettings & settings) { + /// The inferred type tree can be arbitrarily deep (e.g. a deeply nested Array/Map/Tuple from a + /// crafted input). This walk runs after the per-format schema reader, so guard the native stack. + checkStackSize(); + if (!type) return nullptr; @@ -1762,6 +1776,8 @@ NamesAndTypesList getNamesAndRecursivelyNullableTypes(const Block & header, cons bool checkIfTypeIsComplete(const DataTypePtr & type) { + checkStackSize(); + if (!type) return false; diff --git a/src/Processors/Formats/Impl/BSONEachRowRowInputFormat.cpp b/src/Processors/Formats/Impl/BSONEachRowRowInputFormat.cpp index e53238b97729..6afa7cb497aa 100644 --- a/src/Processors/Formats/Impl/BSONEachRowRowInputFormat.cpp +++ b/src/Processors/Formats/Impl/BSONEachRowRowInputFormat.cpp @@ -1,5 +1,7 @@ #include +#include + #include #include #include @@ -42,6 +44,7 @@ namespace ErrorCodes extern const int TOO_LARGE_STRING_SIZE; extern const int UNKNOWN_TYPE; extern const int TYPE_MISMATCH; + extern const int TOO_DEEP_RECURSION; } namespace @@ -360,6 +363,11 @@ static void readAndInsertUUID(ReadBuffer & in, IColumn & column, BSONType bson_t void BSONEachRowRowInputFormat::readArray(IColumn & column, const DataTypePtr & data_type, BSONType bson_type) { + /// Nested Array/Tuple/Map recurse through readField; the depth is bounded by the declared column + /// type, but guard the native stack here (on container entry, not on every primitive field) + /// against a pathologically deep declared type. + checkStackSize(); + if (bson_type != BSONType::ARRAY) throw Exception(ErrorCodes::ILLEGAL_COLUMN, "Cannot insert BSON {} into Array column", getBSONTypeName(bson_type)); @@ -392,6 +400,8 @@ void BSONEachRowRowInputFormat::readArray(IColumn & column, const DataTypePtr & void BSONEachRowRowInputFormat::readTuple(IColumn & column, const DataTypePtr & data_type, BSONType bson_type) { + checkStackSize(); + if (bson_type != BSONType::ARRAY && bson_type != BSONType::DOCUMENT) throw Exception(ErrorCodes::ILLEGAL_COLUMN, "Cannot insert BSON {} into Tuple column", getBSONTypeName(bson_type)); @@ -459,6 +469,8 @@ void BSONEachRowRowInputFormat::readTuple(IColumn & column, const DataTypePtr & void BSONEachRowRowInputFormat::readMap(IColumn & column, const DataTypePtr & data_type, BSONType bson_type) { + checkStackSize(); + if (bson_type != BSONType::DOCUMENT) throw Exception(ErrorCodes::ILLEGAL_COLUMN, "Cannot insert BSON {} into Map column", getBSONTypeName(bson_type)); @@ -879,7 +891,7 @@ BSONEachRowSchemaReader::BSONEachRowSchemaReader(ReadBuffer & in_, const FormatS { } -DataTypePtr BSONEachRowSchemaReader::getDataTypeFromBSONField(BSONType type, bool allow_to_skip_unsupported_types, bool & skip) +DataTypePtr BSONEachRowSchemaReader::getDataTypeFromBSONField(BSONType type, bool allow_to_skip_unsupported_types, bool & skip, size_t depth) { switch (type) { @@ -924,7 +936,7 @@ DataTypePtr BSONEachRowSchemaReader::getDataTypeFromBSONField(BSONType type, boo } case BSONType::DOCUMENT: { - auto nested_names_and_types = getDataTypesFromBSONDocument(false); + auto nested_names_and_types = getDataTypesFromBSONDocument(false, depth + 1); auto nested_types = nested_names_and_types.getTypes(); bool types_are_equal = true; if (nested_types.empty() || !nested_types[0]) @@ -946,7 +958,7 @@ DataTypePtr BSONEachRowSchemaReader::getDataTypeFromBSONField(BSONType type, boo } case BSONType::ARRAY: { - auto nested_types = getDataTypesFromBSONDocument(false).getTypes(); + auto nested_types = getDataTypesFromBSONDocument(false, depth + 1).getTypes(); bool types_are_equal = true; if (nested_types.empty() || !nested_types[0]) return nullptr; @@ -998,8 +1010,21 @@ DataTypePtr BSONEachRowSchemaReader::getDataTypeFromBSONField(BSONType type, boo } } -NamesAndTypesList BSONEachRowSchemaReader::getDataTypesFromBSONDocument(bool allow_to_skip_unsupported_types) +NamesAndTypesList BSONEachRowSchemaReader::getDataTypesFromBSONDocument(bool allow_to_skip_unsupported_types, size_t depth) { + /// BSON documents and arrays can be nested arbitrarily deep. Reject deep nesting early (before + /// building the type) with an explicit limit, so inference stays cheap and interruptible instead + /// of overflowing the native stack in this recursive descent. checkStackSize is a last-resort + /// backstop for when max_parser_depth is raised above the default. + /// max_parser_depth == 0 means unlimited (matching the SQL parser), leaving only checkStackSize. + if (format_settings.max_parser_depth != 0 && depth > format_settings.max_parser_depth) + throw Exception( + ErrorCodes::TOO_DEEP_RECURSION, + "Too deep recursion while inferring the BSON schema: the nesting depth exceeds the limit ({}). " + "It can be raised with the setting 'max_parser_depth', but a very deep schema is rarely intentional", + format_settings.max_parser_depth); + checkStackSize(); + size_t document_start = in.count(); BSONSizeT document_size; readBinaryLittleEndian(document_size, in); @@ -1010,7 +1035,7 @@ NamesAndTypesList BSONEachRowSchemaReader::getDataTypesFromBSONDocument(bool all String name; readNullTerminated(name, in); bool skip = false; - auto type = getDataTypeFromBSONField(bson_type, allow_to_skip_unsupported_types, skip); + auto type = getDataTypeFromBSONField(bson_type, allow_to_skip_unsupported_types, skip, depth); if (!skip) names_and_types.emplace_back(name, type); } @@ -1028,7 +1053,7 @@ NamesAndTypesList BSONEachRowSchemaReader::readRowAndGetNamesAndDataTypes(bool & return {}; } - return getDataTypesFromBSONDocument(format_settings.bson.skip_fields_with_unsupported_types_in_schema_inference); + return getDataTypesFromBSONDocument(format_settings.bson.skip_fields_with_unsupported_types_in_schema_inference, 1); } void BSONEachRowSchemaReader::transformTypesIfNeeded(DataTypePtr & type, DataTypePtr & new_type) diff --git a/src/Processors/Formats/Impl/BSONEachRowRowInputFormat.h b/src/Processors/Formats/Impl/BSONEachRowRowInputFormat.h index a08c1e5dbf9e..d3c672516fb1 100644 --- a/src/Processors/Formats/Impl/BSONEachRowRowInputFormat.h +++ b/src/Processors/Formats/Impl/BSONEachRowRowInputFormat.h @@ -108,8 +108,8 @@ class BSONEachRowSchemaReader : public IRowWithNamesSchemaReader NamesAndTypesList readRowAndGetNamesAndDataTypes(bool & eof) override; void transformTypesIfNeeded(DataTypePtr & type, DataTypePtr & new_type) override; - NamesAndTypesList getDataTypesFromBSONDocument(bool skip_unsupported_types); - DataTypePtr getDataTypeFromBSONField(BSONType type, bool skip_unsupported_types, bool & skip); + NamesAndTypesList getDataTypesFromBSONDocument(bool skip_unsupported_types, size_t depth); + DataTypePtr getDataTypeFromBSONField(BSONType type, bool skip_unsupported_types, bool & skip, size_t depth); }; } diff --git a/src/Processors/Formats/Impl/MsgPackRowInputFormat.cpp b/src/Processors/Formats/Impl/MsgPackRowInputFormat.cpp index 0823edfcf1d7..58e797b4d591 100644 --- a/src/Processors/Formats/Impl/MsgPackRowInputFormat.cpp +++ b/src/Processors/Formats/Impl/MsgPackRowInputFormat.cpp @@ -14,6 +14,7 @@ #include #include +#include #include #include @@ -46,6 +47,7 @@ namespace ErrorCodes extern const int INCORRECT_DATA; extern const int BAD_ARGUMENTS; extern const int UNEXPECTED_END_OF_FILE; + extern const int TOO_DEEP_RECURSION; } MsgPackRowInputFormat::MsgPackRowInputFormat(SharedHeader header_, ReadBuffer & in_, Params params_, const FormatSettings & settings) @@ -601,8 +603,23 @@ msgpack::object_handle MsgPackSchemaReader::readObject() return object_handle; } -DataTypePtr MsgPackSchemaReader::getDataType(const msgpack::object & object) +DataTypePtr MsgPackSchemaReader::getDataType(const msgpack::object & object, size_t depth) { + /// MsgPack arrays and maps can be nested arbitrarily deep, and msgpack::unpack builds the + /// whole object tree iteratively (heap), so a deeply nested object would overflow the native + /// stack during this recursive descent. Reject deep nesting early (before building the type) + /// with an explicit limit: this keeps inference cheap and interruptible instead of walking and + /// allocating a pathologically deep type that later code (e.g. makeNullableRecursively) would + /// also recurse over. checkStackSize is a last-resort backstop if max_parser_depth is raised. + /// max_parser_depth == 0 means unlimited (matching the SQL parser), leaving only checkStackSize. + if (format_settings.max_parser_depth != 0 && depth > format_settings.max_parser_depth) + throw Exception( + ErrorCodes::TOO_DEEP_RECURSION, + "Too deep recursion while inferring the MsgPack schema: the nesting depth exceeds the limit ({}). " + "It can be raised with the setting 'max_parser_depth', but a very deep schema is rarely intentional", + format_settings.max_parser_depth); + checkStackSize(); + switch (object.type) { case msgpack::type::object_type::POSITIVE_INTEGER: [[fallthrough]]; @@ -628,12 +645,16 @@ DataTypePtr MsgPackSchemaReader::getDataType(const msgpack::object & object) bool nested_types_are_equal = true; for (size_t i = 0; i != object_array.size; ++i) { - auto nested_type = getDataType(object_array.ptr[i]); + auto nested_type = getDataType(object_array.ptr[i], depth + 1); if (!nested_type) return nullptr; + /// Compare only against the first element. Comparing the first element to itself is + /// pointless and would recurse through the whole (possibly deeply nested) type via + /// DataTypeArray::equals, which is itself unguarded recursion. + if (!nested_types.empty()) + nested_types_are_equal &= nested_type->equals(*nested_types[0]); nested_types.push_back(nested_type); - nested_types_are_equal &= nested_type->equals(*nested_types[0]); } if (nested_types_are_equal) @@ -646,8 +667,8 @@ DataTypePtr MsgPackSchemaReader::getDataType(const msgpack::object & object) msgpack::object_map object_map = object.via.map; if (object_map.size) { - auto key_type = removeNullable(getDataType(object_map.ptr[0].key)); - auto value_type = getDataType(object_map.ptr[0].val); + auto key_type = removeNullable(getDataType(object_map.ptr[0].key, depth + 1)); + auto value_type = getDataType(object_map.ptr[0].val, depth + 1); if (key_type && value_type) return std::make_shared(key_type, value_type); } @@ -675,7 +696,7 @@ std::optional MsgPackSchemaReader::readRowAndGetDataTypes() for (size_t i = 0; i != number_of_columns; ++i) { auto object_handle = readObject(); - data_types.push_back(getDataType(object_handle.get())); + data_types.push_back(getDataType(object_handle.get(), 1)); } return data_types; diff --git a/src/Processors/Formats/Impl/MsgPackRowInputFormat.h b/src/Processors/Formats/Impl/MsgPackRowInputFormat.h index f96bf336c523..be9dd30d6e8f 100644 --- a/src/Processors/Formats/Impl/MsgPackRowInputFormat.h +++ b/src/Processors/Formats/Impl/MsgPackRowInputFormat.h @@ -94,7 +94,7 @@ class MsgPackSchemaReader : public IRowSchemaReader private: msgpack::object_handle readObject(); - DataTypePtr getDataType(const msgpack::object & object); + DataTypePtr getDataType(const msgpack::object & object, size_t depth); std::optional readRowAndGetDataTypes() override; PeekableReadBuffer buf; diff --git a/src/Processors/Formats/Impl/NativeORCBlockInputFormat.cpp b/src/Processors/Formats/Impl/NativeORCBlockInputFormat.cpp index 9460888d9b0d..21fc462005e1 100644 --- a/src/Processors/Formats/Impl/NativeORCBlockInputFormat.cpp +++ b/src/Processors/Formats/Impl/NativeORCBlockInputFormat.cpp @@ -1,6 +1,9 @@ #include #if USE_ORC + +#include +#include #include #include #include @@ -58,6 +61,7 @@ extern const int VALUE_IS_OUT_OF_RANGE_OF_DATA_TYPE; extern const int THERE_IS_NO_COLUMN; extern const int INCORRECT_DATA; extern const int ARGUMENT_OUT_OF_BOUND; +extern const int TOO_DEEP_RECURSION; } @@ -179,10 +183,25 @@ static DataTypePtr parseORCType( bool skip_columns_with_unsupported_types, bool dictionary_as_low_cardinality, const orc::StripeInformation * stripe_info, - bool & skipped) + bool & skipped, + size_t max_depth = DBMS_DEFAULT_MAX_PARSER_DEPTH, + size_t depth = 0) { assert(orc_type != nullptr); + /// ORC LIST/MAP/STRUCT types can be nested arbitrarily deep and the ORC library does not bound + /// the nesting, so reject deep nesting early (before building the type) with an explicit limit. + /// This keeps schema inference cheap and interruptible instead of recursing over (and later + /// walking) a pathologically deep type. checkStackSize is a last-resort backstop. + /// max_depth == 0 means unlimited (matching the SQL parser), leaving only checkStackSize. + if (max_depth != 0 && depth > max_depth) + throw Exception( + ErrorCodes::TOO_DEEP_RECURSION, + "Too deep recursion while parsing the ORC schema: the nesting depth exceeds the limit ({}). " + "It can be raised with the setting 'max_parser_depth', but a very deep schema is rarely intentional", + max_depth); + checkStackSize(); + const int subtype_count = static_cast(orc_type->getSubtypeCount()); switch (orc_type->getKind()) { @@ -237,7 +256,7 @@ static DataTypePtr parseORCType( throw Exception(ErrorCodes::LOGICAL_ERROR, "Invalid Orc List type {}", orc_type->toString()); DataTypePtr nested_type = parseORCType( - orc_type->getSubtype(0), skip_columns_with_unsupported_types, dictionary_as_low_cardinality, stripe_info, skipped); + orc_type->getSubtype(0), skip_columns_with_unsupported_types, dictionary_as_low_cardinality, stripe_info, skipped, max_depth, depth + 1); if (skipped) return {}; @@ -248,12 +267,12 @@ static DataTypePtr parseORCType( throw Exception(ErrorCodes::LOGICAL_ERROR, "Invalid Orc Map type {}", orc_type->toString()); DataTypePtr key_type = parseORCType( - orc_type->getSubtype(0), skip_columns_with_unsupported_types, dictionary_as_low_cardinality, stripe_info, skipped); + orc_type->getSubtype(0), skip_columns_with_unsupported_types, dictionary_as_low_cardinality, stripe_info, skipped, max_depth, depth + 1); if (skipped) return {}; DataTypePtr value_type = parseORCType( - orc_type->getSubtype(1), skip_columns_with_unsupported_types, dictionary_as_low_cardinality, stripe_info, skipped); + orc_type->getSubtype(1), skip_columns_with_unsupported_types, dictionary_as_low_cardinality, stripe_info, skipped, max_depth, depth + 1); if (skipped) return {}; @@ -268,7 +287,7 @@ static DataTypePtr parseORCType( for (size_t i = 0; i < orc_type->getSubtypeCount(); ++i) { auto parsed_type = parseORCType( - orc_type->getSubtype(i), skip_columns_with_unsupported_types, dictionary_as_low_cardinality, stripe_info, skipped); + orc_type->getSubtype(i), skip_columns_with_unsupported_types, dictionary_as_low_cardinality, stripe_info, skipped, max_depth, depth + 1); if (skipped) return {}; @@ -570,7 +589,7 @@ static void buildORCSearchArgumentImpl( /// For queries with where condition like "a > 10", if a column contains negative values such as "-1", pushing or not pushing /// down filters would result in different outputs. bool skipped = false; - auto expect_type = makeNullableRecursively(parseORCType(orc_type, true, false, nullptr, skipped), format_settings); + auto expect_type = makeNullableRecursively(parseORCType(orc_type, true, false, nullptr, skipped, format_settings.max_parser_depth), format_settings); const ColumnWithTypeAndName * column = header.findByName(column_name, format_settings.orc.case_insensitive_column_matching); if (!expect_type || !column) { @@ -797,6 +816,11 @@ static void getFileReader( static const orc::Type * traverseDownORCTypeByName(const std::string & target, const orc::Type * orc_type, DataTypePtr & type, bool ignore_case) { + /// Recurses the file-controlled ORC type tree. The matching CH type (and the requested column + /// name) bound the depth in practice, but keep a stack backstop here too, since this runs in + /// prepareFileReader before the readColumnFromORCColumn guard is reached. + checkStackSize(); + if (target.empty()) return orc_type; @@ -851,6 +875,11 @@ traverseDownORCTypeByName(const std::string & target, const orc::Type * orc_type static void updateIncludeTypeIds(DataTypePtr type, const orc::Type * orc_type, bool ignore_case, std::unordered_set & include_typeids) { + /// Recurses the file-controlled ORC type tree in lockstep with the (parser-bounded) CH type. + /// Keep a stack backstop here too: this runs in prepareFileReader, before the + /// readColumnFromORCColumn guard is reached, also for explicit-schema reads. + checkStackSize(); + /// For primitive types, directly append column id into result if (orc_type->getSubtypeCount() == 0) { @@ -1164,7 +1193,8 @@ NamesAndTypesList NativeORCSchemaReader::readSchema() format_settings.orc.skip_columns_with_unsupported_types_in_schema_inference, format_settings.orc.dictionary_as_low_cardinality, stripe_info.get(), - skipped); + skipped, + format_settings.max_parser_depth); if (!skipped) header.insert(ColumnWithTypeAndName{type, name}); } @@ -1739,6 +1769,10 @@ ColumnWithTypeAndName ORCColumnToCHColumn::readColumnFromORCColumn( bool inside_nullable, DataTypePtr type_hint) const { + /// Reading a nested LIST/MAP/STRUCT recurses through this function over the ORC type tree, whose + /// depth is attacker-controlled, so guard the native stack on the data-read path as well. + checkStackSize(); + bool skipped = false; if (!inside_nullable && (orc_column->hasNulls || (type_hint && type_hint->isNullable())) && !orc_column->isEncoded diff --git a/src/Processors/Formats/Impl/Parquet/Reader.cpp b/src/Processors/Formats/Impl/Parquet/Reader.cpp index 4930d3df2dec..6d8ac2065486 100644 --- a/src/Processors/Formats/Impl/Parquet/Reader.cpp +++ b/src/Processors/Formats/Impl/Parquet/Reader.cpp @@ -5,8 +5,8 @@ #include #include #include -#include #include +#include #include #include #include @@ -2103,8 +2103,8 @@ void Reader::decompressPageIfCompressed(PageState & page) MutableColumnPtr Reader::formOutputColumn(RowSubgroup & row_subgroup, size_t output_column_idx, size_t num_rows) { - /// Recurses over nested output types; bound the depth so a deeply nested schema cannot - /// overflow the stack. + /// Recurses over the nested output column tree, whose depth is bounded by SchemaConverter's + /// recursion limit; guard the native stack here too as defense in depth. checkStackSize(); const OutputColumnInfo & output_info = output_columns.at(output_column_idx); diff --git a/src/Processors/Formats/Impl/Parquet/SchemaConverter.cpp b/src/Processors/Formats/Impl/Parquet/SchemaConverter.cpp index 2be4a0073730..2f58ffeecdce 100644 --- a/src/Processors/Formats/Impl/Parquet/SchemaConverter.cpp +++ b/src/Processors/Formats/Impl/Parquet/SchemaConverter.cpp @@ -169,10 +169,20 @@ std::string_view SchemaConverter::useColumnMapperIfNeeded(const parq::SchemaElem void SchemaConverter::processSubtree(TraversalNode & node) { - /// A deeply nested schema (e.g. a long chain of REQUIRED groups) recurses here per level. - /// The definition-level cap below only counts OPTIONAL/REPEATED nesting, so without this an - /// untrusted file could overflow the stack (uncatchable crash) instead of throwing. + /// Reject deeply nested schemas before recursing. The def-level guard below (def == UINT8_MAX) + /// only counts OPTIONAL/REPEATED nodes, so a chain of REQUIRED groups would bypass it and + /// overflow the native stack. Track the real recursion depth unconditionally and reject early; + /// checkStackSize is a last-resort backstop if max_parser_depth is raised. + /// max_parser_depth == 0 means unlimited (matching the SQL parser), leaving only checkStackSize. checkStackSize(); + ++recursion_depth; + SCOPE_EXIT({ --recursion_depth; }); + if (options.format.max_parser_depth != 0 && recursion_depth > options.format.max_parser_depth) + throw Exception( + ErrorCodes::TOO_DEEP_RECURSION, + "Parquet schema is nested deeper than the limit ({}). It can be raised with the setting " + "'max_parser_depth', but a very deeply nested schema is rarely intentional", + options.format.max_parser_depth); if (node.type_hint) chassert(node.requested); diff --git a/src/Processors/Formats/Impl/Parquet/SchemaConverter.h b/src/Processors/Formats/Impl/Parquet/SchemaConverter.h index 86d965d30b1b..7f6c825ff9e3 100644 --- a/src/Processors/Formats/Impl/Parquet/SchemaConverter.h +++ b/src/Processors/Formats/Impl/Parquet/SchemaConverter.h @@ -32,6 +32,9 @@ struct SchemaConverter size_t schema_idx = 1; size_t primitive_column_idx = 0; std::vector levels; + /// Actual recursion depth of processSubtree. Tracked unconditionally because the def-level + /// counter only advances for OPTIONAL/REPEATED nodes, so REQUIRED-group nesting would bypass it. + size_t recursion_depth = 0; /// The key is the parquet column name, without ColumnMapper. std::unordered_map geo_columns; diff --git a/tests/queries/0_stateless/04335_msgpack_schema_inference_deep_recursion.reference b/tests/queries/0_stateless/04335_msgpack_schema_inference_deep_recursion.reference new file mode 100644 index 000000000000..292b253eee30 --- /dev/null +++ b/tests/queries/0_stateless/04335_msgpack_schema_inference_deep_recursion.reference @@ -0,0 +1,4 @@ +explicit_limit: TOO_DEEP_RECURSION +stack_backstop: TOO_DEEP_RECURSION +multirow_merge: TOO_DEEP_RECURSION +unlimited_depth: accepted diff --git a/tests/queries/0_stateless/04335_msgpack_schema_inference_deep_recursion.sh b/tests/queries/0_stateless/04335_msgpack_schema_inference_deep_recursion.sh new file mode 100755 index 000000000000..baf0909870d4 --- /dev/null +++ b/tests/queries/0_stateless/04335_msgpack_schema_inference_deep_recursion.sh @@ -0,0 +1,70 @@ +#!/usr/bin/env bash +# Tags: no-fasttest +# Regression test: MsgPack schema inference recursed over nested arrays/maps without a depth limit. +# msgpack::unpack builds the whole object tree iteratively (on the heap), so a tiny, deeply nested +# object overflowed the native stack and crashed the process - both in MsgPackSchemaReader::getDataType +# and, for moderate depths that slipped past it, in the shared makeNullableRecursively walk over the +# inferred type. Nesting must now be rejected as TOO_DEEP_RECURSION, never crash. + +CUR_DIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd) +# shellcheck source=../shell_config.sh +. "$CUR_DIR"/../shell_config.sh + +TMP_DIR="${CLICKHOUSE_TMP}/${CLICKHOUSE_TEST_UNIQUE_NAME}" +mkdir -p "$TMP_DIR" +trap 'rm -rf "$TMP_DIR"' EXIT + +# Reads a query's combined output from stdin. If it contains $2, print a stable "