diff options
| author | YDBot <[email protected]> | 2026-07-15 01:00:25 +0000 |
|---|---|---|
| committer | YDBot <[email protected]> | 2026-07-15 01:00:25 +0000 |
| commit | 2b4e2c93bd17c28daddde4b7df74729bfd252c72 (patch) | |
| tree | ea997453f43cfd8b37a8846b6a8128549b2281a8 | |
| parent | 7fb571f5679cbb020a9ce860149c0efea4f5178e (diff) | |
| parent | 38a06261511257d249ac3d300311d55f5af6c731 (diff) | |
Sync branches 260715-0058
64 files changed, 2143 insertions, 697 deletions
diff --git a/build/conf/compilers/gnu_compiler.conf b/build/conf/compilers/gnu_compiler.conf index 2b269831069..3382f99a79d 100644 --- a/build/conf/compilers/gnu_compiler.conf +++ b/build/conf/compilers/gnu_compiler.conf @@ -178,18 +178,6 @@ elsewhen ($CONSISTENT_DEBUG == "yes") { } YASM_DEBUG_INFO_DISABLE_CACHE__NO_UID__=--replace=${ARCADIA_BUILD_ROOT}=/-B --replace=${ARCADIA_ROOT}=/-S --replace=$(TOOL_ROOT)=/-T ${env:"YASM_TEST_SUITE=1"} } -elsewhen ($CONSISTENT_DEBUG_LIGHT == "yes") { - when ($CLANG == "yes") { - CL_DEBUG_INFO_DISABLE_CACHE__NO_UID__=-fdebug-prefix-map=${ARCADIA_BUILD_ROOT}=/-B -Xclang -fdebug-compilation-dir -Xclang /tmp - } - otherwise { - CL_DEBUG_INFO_DISABLE_CACHE__NO_UID__=-fdebug-prefix-map=${ARCADIA_BUILD_ROOT}=/-B - when ($ARCH_XTENSA == "yes") { - CL_DEBUG_INFO_DISABLE_CACHE__NO_UID__= - } - } - YASM_DEBUG_INFO_DISABLE_CACHE__NO_UID__=--replace=${ARCADIA_BUILD_ROOT}=/-B ${env:"YASM_TEST_SUITE=1"} -} when ($OS_LINUX == "yes" || $OS_DARWIN == "yes" || $OS_WINDOWS == "yes") { # Some android toolchains using old compilers which didn't support some flags (f.e. -fmacro-prefix-map) diff --git a/contrib/libs/openssl/.yandex_meta/__init__.py b/contrib/libs/openssl/.yandex_meta/__init__.py index 912cdd7c01b..03d0dc5a5da 100644 --- a/contrib/libs/openssl/.yandex_meta/__init__.py +++ b/contrib/libs/openssl/.yandex_meta/__init__.py @@ -1,16 +1,27 @@ -import os.path as P +import os -from devtools.yamaker.fileutil import subcopy +from devtools.yamaker import fileutil +from devtools.yamaker import pathutil from devtools.yamaker.modules import Linkable, Switch from devtools.yamaker.project import NixProject +def post_build(self): + os.remove(f"{self.dstdir}/libssl.map") + for hdr in fileutil.files(f"{self.dstdir}/include", rel=False, test=pathutil.is_header): + with open(hdr, "rt") as hdr_file: + data = hdr_file.read() + with open(hdr, "wt") as hdr_file: + hdr_file.write("#include <contrib/libs/openssl/redef.h>\n") + hdr_file.write(data) + + def post_install(self): def d(s): return self.dstdir + "/" + s # Move asm sources to asm/linux. - subcopy(self.dstdir, d("asm/linux"), ["**/*.s"], move=True) + fileutil.subcopy(self.dstdir, d("asm/linux"), ["**/*.s"], move=True) with self.yamakes["crypto"] as m: asm = {s for s in m.SRCS if s.endswith(".s")} m.SRCS -= asm @@ -28,7 +39,7 @@ def post_install(self): ) # Shorten paths. m.SRCDIR = [] - m.SRCS = {P.relpath(s, "crypto") for s in m.SRCS} + m.SRCS = {os.path.relpath(s, "crypto") for s in m.SRCS} m.SRCS -= {"dso/dso_dlfcn.c", "rand/rand_vms.c"} # Add suppression for ubsan, see also https://github.com/openssl/openssl/issues/22896 @@ -137,6 +148,7 @@ openssl = NixProject( "include/openssl/opensslconf.h", ], keep_paths=[ + # This asm files were generated manually "asm/aarch64/", "asm/android/", "asm/darwin/", @@ -144,9 +156,13 @@ openssl = NixProject( "asm/ios/", "asm/ppc64le/", "asm/windows/", - "openssl.package.json", + # This code is written by us + "ar.pyplugin", "sanitizers.h", "crypto/ubsan.supp", + "redef.h", + "system_openssl.ya.inc", ], + post_build=post_build, post_install=post_install, ) diff --git a/contrib/libs/openssl/apps/progs-linux.h b/contrib/libs/openssl/apps/progs-linux.h index 2fce5cbc797..b7c2e31b284 100644 --- a/contrib/libs/openssl/apps/progs-linux.h +++ b/contrib/libs/openssl/apps/progs-linux.h @@ -2,7 +2,7 @@ * WARNING: do not edit! * Generated by apps/progs.pl * - * Copyright 1995-2023 The OpenSSL Project Authors. All Rights Reserved. + * Copyright 1995-2026 The OpenSSL Project Authors. All Rights Reserved. * * Licensed under the OpenSSL license (the "License"). You may not use * this file except in compliance with the License. You can obtain a copy diff --git a/contrib/libs/openssl/include/crypto/bn_conf.h b/contrib/libs/openssl/include/crypto/bn_conf.h index eb25b5c6636..7b4d0a3b106 100644 --- a/contrib/libs/openssl/include/crypto/bn_conf.h +++ b/contrib/libs/openssl/include/crypto/bn_conf.h @@ -1,7 +1,8 @@ -#include <contrib/libs/openssl/redef.h> #pragma once -#if defined(__ANDROID__) && defined(__arm__) +#if defined(__wasm__) && !defined(__wasm64__) +# include "bn_conf-wasm32.h" +#elif defined(__ANDROID__) && defined(__arm__) # include "bn_conf-android-arm.h" #elif defined(__ANDROID__) && defined(__aarch64__) # include "bn_conf-android-arm64.h" @@ -21,8 +22,6 @@ # include "bn_conf-linux-arm.h" #elif defined(__linux__) && (defined(__aarch64__) || defined(_M_ARM64)) # include "bn_conf-linux-aarch64.h" -#elif defined(__wasm__) && !defined(__wasm64__) -# include "bn_conf-wasm32.h" #else # include "bn_conf-linux.h" #endif diff --git a/contrib/libs/openssl/include/crypto/dso_conf.h b/contrib/libs/openssl/include/crypto/dso_conf.h index fd60cdfe832..39695b0aaa8 100644 --- a/contrib/libs/openssl/include/crypto/dso_conf.h +++ b/contrib/libs/openssl/include/crypto/dso_conf.h @@ -1,4 +1,3 @@ -#include <contrib/libs/openssl/redef.h> #pragma once #if defined(__ANDROID__) diff --git a/contrib/libs/openssl/include/openssl/opensslconf-freebsd.h b/contrib/libs/openssl/include/openssl/opensslconf-freebsd-x86_64.h index bdeb5b9a471..bdeb5b9a471 100644 --- a/contrib/libs/openssl/include/openssl/opensslconf-freebsd.h +++ b/contrib/libs/openssl/include/openssl/opensslconf-freebsd-x86_64.h diff --git a/contrib/libs/openssl/include/openssl/opensslconf.h b/contrib/libs/openssl/include/openssl/opensslconf.h index 38164e3cb65..d7f65c6ab1e 100644 --- a/contrib/libs/openssl/include/openssl/opensslconf.h +++ b/contrib/libs/openssl/include/openssl/opensslconf.h @@ -1,7 +1,10 @@ -#include <contrib/libs/openssl/redef.h> #pragma once -#if defined(__ANDROID__) && defined(__arm__) +#if defined(__wasm__) && !defined(__wasm64__) +# include "opensslconf-wasm32.h" +#elif defined(__wasm64__) +# include "opensslconf-wasm64.h" +#elif defined(__ANDROID__) && defined(__arm__) # include "opensslconf-android-arm.h" #elif defined(__ANDROID__) && defined(__aarch64__) # include "opensslconf-android-arm64.h" @@ -21,16 +24,12 @@ # include "opensslconf-win-x86_64.h" #elif defined(_MSC_VER) && (defined(__i686__) || defined(_M_IX86)) # include "opensslconf-win-i686.h" +#elif defined(__FreeBSD__) && (defined(__x86_64__) || defined(_M_X64)) +# include "opensslconf-freebsd-x86_64.h" #elif defined(__linux__) && defined(__arm__) # include "opensslconf-linux-arm.h" #elif defined(__linux__) && (defined(__aarch64__) || defined(_M_ARM64)) # include "opensslconf-linux-aarch64.h" -#elif defined(__wasm__) && !defined(__wasm64__) -# include "opensslconf-wasm32.h" -#elif defined(__wasm64__) -# include "opensslconf-wasm64.h" -#elif defined(__FreeBSD__) -# include "opensslconf-freebsd.h" #else # include "opensslconf-linux.h" #endif diff --git a/contrib/libs/openssl/libssl.map b/contrib/libs/openssl/libssl.map deleted file mode 100644 index 153358459d1..00000000000 --- a/contrib/libs/openssl/libssl.map +++ /dev/null @@ -1,543 +0,0 @@ -OPENSSL_1_1_0 { - global: - BIO_f_ssl; - BIO_new_buffer_ssl_connect; - BIO_new_ssl; - BIO_new_ssl_connect; - BIO_ssl_copy_session_id; - BIO_ssl_shutdown; - DTLS_client_method; - DTLS_method; - DTLS_server_method; - DTLSv1_2_client_method; - DTLSv1_2_method; - DTLSv1_2_server_method; - DTLSv1_client_method; - DTLSv1_listen; - DTLSv1_method; - DTLSv1_server_method; - ERR_load_SSL_strings; - OPENSSL_init_ssl; - PEM_read_SSL_SESSION; - PEM_read_bio_SSL_SESSION; - PEM_write_SSL_SESSION; - PEM_write_bio_SSL_SESSION; - SRP_Calc_A_param; - SSL_CIPHER_description; - SSL_CIPHER_find; - SSL_CIPHER_get_auth_nid; - SSL_CIPHER_get_bits; - SSL_CIPHER_get_cipher_nid; - SSL_CIPHER_get_digest_nid; - SSL_CIPHER_get_id; - SSL_CIPHER_get_kx_nid; - SSL_CIPHER_get_name; - SSL_CIPHER_get_version; - SSL_CIPHER_is_aead; - SSL_CIPHER_standard_name; - SSL_COMP_add_compression_method; - SSL_COMP_get_compression_methods; - SSL_COMP_get_name; - SSL_COMP_set0_compression_methods; - SSL_CONF_CTX_clear_flags; - SSL_CONF_CTX_finish; - SSL_CONF_CTX_free; - SSL_CONF_CTX_new; - SSL_CONF_CTX_set1_prefix; - SSL_CONF_CTX_set_flags; - SSL_CONF_CTX_set_ssl; - SSL_CONF_CTX_set_ssl_ctx; - SSL_CONF_cmd; - SSL_CONF_cmd_argv; - SSL_CONF_cmd_value_type; - SSL_CTX_SRP_CTX_free; - SSL_CTX_SRP_CTX_init; - SSL_CTX_add_client_CA; - SSL_CTX_add_client_custom_ext; - SSL_CTX_add_server_custom_ext; - SSL_CTX_add_session; - SSL_CTX_callback_ctrl; - SSL_CTX_check_private_key; - SSL_CTX_clear_options; - SSL_CTX_config; - SSL_CTX_ct_is_enabled; - SSL_CTX_ctrl; - SSL_CTX_dane_clear_flags; - SSL_CTX_dane_enable; - SSL_CTX_dane_mtype_set; - SSL_CTX_dane_set_flags; - SSL_CTX_enable_ct; - SSL_CTX_flush_sessions; - SSL_CTX_free; - SSL_CTX_get0_certificate; - SSL_CTX_get0_ctlog_store; - SSL_CTX_get0_param; - SSL_CTX_get0_privatekey; - SSL_CTX_get0_security_ex_data; - SSL_CTX_get_cert_store; - SSL_CTX_get_ciphers; - SSL_CTX_get_client_CA_list; - SSL_CTX_get_client_cert_cb; - SSL_CTX_get_default_passwd_cb; - SSL_CTX_get_default_passwd_cb_userdata; - SSL_CTX_get_ex_data; - SSL_CTX_get_info_callback; - SSL_CTX_get_options; - SSL_CTX_get_quiet_shutdown; - SSL_CTX_get_security_callback; - SSL_CTX_get_security_level; - SSL_CTX_get_ssl_method; - SSL_CTX_get_timeout; - SSL_CTX_get_verify_callback; - SSL_CTX_get_verify_depth; - SSL_CTX_get_verify_mode; - SSL_CTX_has_client_custom_ext; - SSL_CTX_load_verify_locations; - SSL_CTX_new; - SSL_CTX_remove_session; - SSL_CTX_sess_get_get_cb; - SSL_CTX_sess_get_new_cb; - SSL_CTX_sess_get_remove_cb; - SSL_CTX_sess_set_get_cb; - SSL_CTX_sess_set_new_cb; - SSL_CTX_sess_set_remove_cb; - SSL_CTX_sessions; - SSL_CTX_set0_ctlog_store; - SSL_CTX_set0_security_ex_data; - SSL_CTX_set1_param; - SSL_CTX_set_alpn_protos; - SSL_CTX_set_alpn_select_cb; - SSL_CTX_set_cert_cb; - SSL_CTX_set_cert_store; - SSL_CTX_set_cert_verify_callback; - SSL_CTX_set_cipher_list; - SSL_CTX_set_client_CA_list; - SSL_CTX_set_client_cert_cb; - SSL_CTX_set_client_cert_engine; - SSL_CTX_set_cookie_generate_cb; - SSL_CTX_set_cookie_verify_cb; - SSL_CTX_set_ct_validation_callback; - SSL_CTX_set_ctlog_list_file; - SSL_CTX_set_default_ctlog_list_file; - SSL_CTX_set_default_passwd_cb; - SSL_CTX_set_default_passwd_cb_userdata; - SSL_CTX_set_default_read_buffer_len; - SSL_CTX_set_default_verify_dir; - SSL_CTX_set_default_verify_file; - SSL_CTX_set_default_verify_paths; - SSL_CTX_set_ex_data; - SSL_CTX_set_generate_session_id; - SSL_CTX_set_info_callback; - SSL_CTX_set_msg_callback; - SSL_CTX_set_next_proto_select_cb; - SSL_CTX_set_next_protos_advertised_cb; - SSL_CTX_set_not_resumable_session_callback; - SSL_CTX_set_options; - SSL_CTX_set_psk_client_callback; - SSL_CTX_set_psk_server_callback; - SSL_CTX_set_purpose; - SSL_CTX_set_quiet_shutdown; - SSL_CTX_set_security_callback; - SSL_CTX_set_security_level; - SSL_CTX_set_session_id_context; - SSL_CTX_set_srp_cb_arg; - SSL_CTX_set_srp_client_pwd_callback; - SSL_CTX_set_srp_password; - SSL_CTX_set_srp_strength; - SSL_CTX_set_srp_username; - SSL_CTX_set_srp_username_callback; - SSL_CTX_set_srp_verify_param_callback; - SSL_CTX_set_ssl_version; - SSL_CTX_set_timeout; - SSL_CTX_set_tlsext_use_srtp; - SSL_CTX_set_tmp_dh_callback; - SSL_CTX_set_trust; - SSL_CTX_set_verify; - SSL_CTX_set_verify_depth; - SSL_CTX_up_ref; - SSL_CTX_use_PrivateKey; - SSL_CTX_use_PrivateKey_ASN1; - SSL_CTX_use_PrivateKey_file; - SSL_CTX_use_RSAPrivateKey; - SSL_CTX_use_RSAPrivateKey_ASN1; - SSL_CTX_use_RSAPrivateKey_file; - SSL_CTX_use_certificate; - SSL_CTX_use_certificate_ASN1; - SSL_CTX_use_certificate_chain_file; - SSL_CTX_use_certificate_file; - SSL_CTX_use_psk_identity_hint; - SSL_CTX_use_serverinfo; - SSL_CTX_use_serverinfo_file; - SSL_SESSION_free; - SSL_SESSION_get0_cipher; - SSL_SESSION_get0_hostname; - SSL_SESSION_get0_id_context; - SSL_SESSION_get0_peer; - SSL_SESSION_get0_ticket; - SSL_SESSION_get_compress_id; - SSL_SESSION_get_ex_data; - SSL_SESSION_get_id; - SSL_SESSION_get_master_key; - SSL_SESSION_get_protocol_version; - SSL_SESSION_get_ticket_lifetime_hint; - SSL_SESSION_get_time; - SSL_SESSION_get_timeout; - SSL_SESSION_has_ticket; - SSL_SESSION_new; - SSL_SESSION_print; - SSL_SESSION_print_fp; - SSL_SESSION_print_keylog; - SSL_SESSION_set1_id; - SSL_SESSION_set1_id_context; - SSL_SESSION_set_ex_data; - SSL_SESSION_set_time; - SSL_SESSION_set_timeout; - SSL_SESSION_up_ref; - SSL_SRP_CTX_free; - SSL_SRP_CTX_init; - SSL_accept; - SSL_add1_host; - SSL_add_client_CA; - SSL_add_dir_cert_subjects_to_stack; - SSL_add_file_cert_subjects_to_stack; - SSL_add_ssl_module; - SSL_alert_desc_string; - SSL_alert_desc_string_long; - SSL_alert_type_string; - SSL_alert_type_string_long; - SSL_callback_ctrl; - SSL_certs_clear; - SSL_check_chain; - SSL_check_private_key; - SSL_clear; - SSL_clear_options; - SSL_client_version; - SSL_config; - SSL_connect; - SSL_copy_session_id; - SSL_ct_is_enabled; - SSL_ctrl; - SSL_dane_clear_flags; - SSL_dane_enable; - SSL_dane_set_flags; - SSL_dane_tlsa_add; - SSL_do_handshake; - SSL_dup; - SSL_dup_CA_list; - SSL_enable_ct; - SSL_export_keying_material; - SSL_extension_supported; - SSL_free; - SSL_get0_alpn_selected; - SSL_get0_dane; - SSL_get0_dane_authority; - SSL_get0_dane_tlsa; - SSL_get0_next_proto_negotiated; - SSL_get0_param; - SSL_get0_peer_scts; - SSL_get0_peername; - SSL_get0_security_ex_data; - SSL_get0_verified_chain; - SSL_get1_session; - SSL_get1_supported_ciphers; - SSL_get_SSL_CTX; - SSL_get_all_async_fds; - SSL_get_certificate; - SSL_get_changed_async_fds; - SSL_get_cipher_list; - SSL_get_ciphers; - SSL_get_client_CA_list; - SSL_get_client_ciphers; - SSL_get_client_random; - SSL_get_current_cipher; - SSL_get_current_compression; - SSL_get_current_expansion; - SSL_get_default_passwd_cb; - SSL_get_default_passwd_cb_userdata; - SSL_get_default_timeout; - SSL_get_error; - SSL_get_ex_data; - SSL_get_ex_data_X509_STORE_CTX_idx; - SSL_get_fd; - SSL_get_finished; - SSL_get_info_callback; - SSL_get_options; - SSL_get_peer_cert_chain; - SSL_get_peer_certificate; - SSL_get_peer_finished; - SSL_get_privatekey; - SSL_get_psk_identity; - SSL_get_psk_identity_hint; - SSL_get_quiet_shutdown; - SSL_get_rbio; - SSL_get_read_ahead; - SSL_get_rfd; - SSL_get_security_callback; - SSL_get_security_level; - SSL_get_selected_srtp_profile; - SSL_get_server_random; - SSL_get_servername; - SSL_get_servername_type; - SSL_get_session; - SSL_get_shared_ciphers; - SSL_get_shared_sigalgs; - SSL_get_shutdown; - SSL_get_sigalgs; - SSL_get_srp_N; - SSL_get_srp_g; - SSL_get_srp_userinfo; - SSL_get_srp_username; - SSL_get_srtp_profiles; - SSL_get_ssl_method; - SSL_get_state; - SSL_get_verify_callback; - SSL_get_verify_depth; - SSL_get_verify_mode; - SSL_get_verify_result; - SSL_get_version; - SSL_get_wbio; - SSL_get_wfd; - SSL_has_matching_session_id; - SSL_has_pending; - SSL_in_before; - SSL_in_init; - SSL_is_dtls; - SSL_is_init_finished; - SSL_is_server; - SSL_load_client_CA_file; - SSL_new; - SSL_peek; - SSL_pending; - SSL_read; - SSL_renegotiate; - SSL_renegotiate_abbreviated; - SSL_renegotiate_pending; - SSL_rstate_string; - SSL_rstate_string_long; - SSL_select_next_proto; - SSL_session_reused; - SSL_set0_rbio; - SSL_set0_security_ex_data; - SSL_set0_wbio; - SSL_set1_host; - SSL_set1_param; - SSL_set_SSL_CTX; - SSL_set_accept_state; - SSL_set_alpn_protos; - SSL_set_bio; - SSL_set_cert_cb; - SSL_set_cipher_list; - SSL_set_client_CA_list; - SSL_set_connect_state; - SSL_set_ct_validation_callback; - SSL_set_debug; - SSL_set_default_passwd_cb; - SSL_set_default_passwd_cb_userdata; - SSL_set_default_read_buffer_len; - SSL_set_ex_data; - SSL_set_fd; - SSL_set_generate_session_id; - SSL_set_hostflags; - SSL_set_info_callback; - SSL_set_msg_callback; - SSL_set_not_resumable_session_callback; - SSL_set_options; - SSL_set_psk_client_callback; - SSL_set_psk_server_callback; - SSL_set_purpose; - SSL_set_quiet_shutdown; - SSL_set_read_ahead; - SSL_set_rfd; - SSL_set_security_callback; - SSL_set_security_level; - SSL_set_session; - SSL_set_session_id_context; - SSL_set_session_secret_cb; - SSL_set_session_ticket_ext; - SSL_set_session_ticket_ext_cb; - SSL_set_shutdown; - SSL_set_srp_server_param; - SSL_set_srp_server_param_pw; - SSL_set_ssl_method; - SSL_set_tlsext_use_srtp; - SSL_set_tmp_dh_callback; - SSL_set_trust; - SSL_set_verify; - SSL_set_verify_depth; - SSL_set_verify_result; - SSL_set_wfd; - SSL_shutdown; - SSL_srp_server_param_with_username; - SSL_state_string; - SSL_state_string_long; - SSL_up_ref; - SSL_use_PrivateKey; - SSL_use_PrivateKey_ASN1; - SSL_use_PrivateKey_file; - SSL_use_RSAPrivateKey; - SSL_use_RSAPrivateKey_ASN1; - SSL_use_RSAPrivateKey_file; - SSL_use_certificate; - SSL_use_certificate_ASN1; - SSL_use_certificate_chain_file; - SSL_use_certificate_file; - SSL_use_psk_identity_hint; - SSL_version; - SSL_waiting_for_async; - SSL_want; - SSL_write; - TLS_client_method; - TLS_method; - TLS_server_method; - TLSv1_1_client_method; - TLSv1_1_method; - TLSv1_1_server_method; - TLSv1_2_client_method; - TLSv1_2_method; - TLSv1_2_server_method; - TLSv1_client_method; - TLSv1_method; - TLSv1_server_method; - d2i_SSL_SESSION; - i2d_SSL_SESSION; -}; - -OPENSSL_1_1_0d { - global: - SSL_COMP_get0_name; - SSL_COMP_get_id; -} OPENSSL_1_1_0; - -OPENSSL_1_1_1 { - global: - DTLS_get_data_mtu; - DTLS_set_timer_cb; - OPENSSL_cipher_name; - SSL_CIPHER_get_handshake_digest; - SSL_CIPHER_get_protocol_id; - SSL_CTX_add1_to_CA_list; - SSL_CTX_add_custom_ext; - SSL_CTX_get0_CA_list; - SSL_CTX_get_keylog_callback; - SSL_CTX_get_max_early_data; - SSL_CTX_get_num_tickets; - SSL_CTX_get_record_padding_callback_arg; - SSL_CTX_get_recv_max_early_data; - SSL_CTX_set0_CA_list; - SSL_CTX_set1_cert_store; - SSL_CTX_set_allow_early_data_cb; - SSL_CTX_set_block_padding; - SSL_CTX_set_ciphersuites; - SSL_CTX_set_client_hello_cb; - SSL_CTX_set_keylog_callback; - SSL_CTX_set_max_early_data; - SSL_CTX_set_num_tickets; - SSL_CTX_set_post_handshake_auth; - SSL_CTX_set_psk_find_session_callback; - SSL_CTX_set_psk_use_session_callback; - SSL_CTX_set_record_padding_callback; - SSL_CTX_set_record_padding_callback_arg; - SSL_CTX_set_recv_max_early_data; - SSL_CTX_set_session_ticket_cb; - SSL_CTX_set_stateless_cookie_generate_cb; - SSL_CTX_set_stateless_cookie_verify_cb; - SSL_CTX_set_tlsext_max_fragment_length; - SSL_CTX_use_cert_and_key; - SSL_CTX_use_serverinfo_ex; - SSL_SESSION_dup; - SSL_SESSION_get0_alpn_selected; - SSL_SESSION_get0_ticket_appdata; - SSL_SESSION_get_max_early_data; - SSL_SESSION_get_max_fragment_length; - SSL_SESSION_is_resumable; - SSL_SESSION_set1_alpn_selected; - SSL_SESSION_set1_hostname; - SSL_SESSION_set1_master_key; - SSL_SESSION_set1_ticket_appdata; - SSL_SESSION_set_cipher; - SSL_SESSION_set_max_early_data; - SSL_SESSION_set_protocol_version; - SSL_add1_to_CA_list; - SSL_alloc_buffers; - SSL_bytes_to_cipher_list; - SSL_client_hello_get0_ciphers; - SSL_client_hello_get0_compression_methods; - SSL_client_hello_get0_ext; - SSL_client_hello_get0_legacy_version; - SSL_client_hello_get0_random; - SSL_client_hello_get0_session_id; - SSL_client_hello_get1_extensions_present; - SSL_client_hello_isv2; - SSL_export_keying_material_early; - SSL_free_buffers; - SSL_get0_CA_list; - SSL_get0_peer_CA_list; - SSL_get_early_data_status; - SSL_get_key_update_type; - SSL_get_max_early_data; - SSL_get_num_tickets; - SSL_get_peer_signature_type_nid; - SSL_get_pending_cipher; - SSL_get_record_padding_callback_arg; - SSL_get_recv_max_early_data; - SSL_key_update; - SSL_peek_ex; - SSL_read_early_data; - SSL_read_ex; - SSL_set0_CA_list; - SSL_set_allow_early_data_cb; - SSL_set_block_padding; - SSL_set_ciphersuites; - SSL_set_max_early_data; - SSL_set_num_tickets; - SSL_set_post_handshake_auth; - SSL_set_psk_find_session_callback; - SSL_set_psk_use_session_callback; - SSL_set_record_padding_callback; - SSL_set_record_padding_callback_arg; - SSL_set_recv_max_early_data; - SSL_set_tlsext_max_fragment_length; - SSL_stateless; - SSL_use_cert_and_key; - SSL_verify_client_post_handshake; - SSL_write_early_data; - SSL_write_ex; -} OPENSSL_1_1_0d; - -OPENSSL_1_1_1a { - global: - SSL_get_signature_type_nid; - local: *; -} OPENSSL_1_1_1; - -OPENSSL_1_1_1i { - global: - SSL_CIPHER_get_prf_nid; - SSL_CTX_set_quic_method; - SSL_get_peer_quic_transport_params; - SSL_get_peer_quic_transport_version; - SSL_get_quic_transport_version; - SSL_is_quic; - SSL_process_quic_post_handshake; - SSL_provide_quic_data; - SSL_quic_max_handshake_flight_len; - SSL_quic_read_level; - SSL_quic_write_level; - SSL_set_quic_method; - SSL_set_quic_transport_params; - SSL_set_quic_transport_version; - SSL_set_quic_use_legacy_codepoint; -} OPENSSL_1_1_1a; - -OPENSSL_1_1_1j { - global: - SSL_set_quic_early_data_enabled; -} OPENSSL_1_1_1i; - -OPENSSL_1_1_1k { - global: - SSL_new_session_ticket; - local: *; -} OPENSSL_1_1_1j; - - diff --git a/contrib/libs/openssl/openssl.package.json b/contrib/libs/openssl/openssl.package.json deleted file mode 100644 index e98964e5067..00000000000 --- a/contrib/libs/openssl/openssl.package.json +++ /dev/null @@ -1,37 +0,0 @@ -{ - "meta": { - "name": "libyandex-openssl-dev", - "version": "1.0.2j-r{revision}", - "maintainer": "Sergey Mironov <[email protected]>", - "description": "Statically linked OpenSSL from arcadia, headers\nWith Yandex' patches\nApproved by security" - }, - "build": { - "targets": [ - "contrib/libs/openssl" - ] - }, - "data": [ - { - "source": { - "type": "BUILD_OUTPUT", - "path": "contrib/libs/openssl", - "files": [ - "libcontrib-libs-openssl.a" - ] - }, - "destination": { - "path": "/usr/lib/" - } - }, - { - "source": { - "path": "contrib/libs/openssl/include/openssl", - "scheme": "svn", - "type": "ARCADIA" - }, - "destination": { - "path": "/usr/include/yandex-newssl/" - } - } - ] -} diff --git a/util/ysafeptr.cpp b/util/ysafeptr.cpp index 96f25140673..db2ff21ec0c 100644 --- a/util/ysafeptr.cpp +++ b/util/ysafeptr.cpp @@ -1,5 +1,7 @@ #include "ysafeptr.h" +#include <util/system/sanitizers.h> + #ifdef CHECK_YPTR2 Y_POD_THREAD(bool) IObjectBase::DisableThreadCheck; @@ -24,6 +26,11 @@ void IObjectBase::ReleaseObjComplete(int nMask) { } } //////////////////////////////////////////////////////////////////////////////////////////////////// +void IObjectBase::UnpoisonCounters() noexcept { + NSan::Unpoison(&ObjData, sizeof(ObjData)); + NSan::Unpoison(&RefData, sizeof(RefData)); +} +//////////////////////////////////////////////////////////////////////////////////////////////////// void IObjectBase::ReleaseRefComplete() { assert(RefData == 0); if ((ObjData & 0x3fffffff) == 0) { diff --git a/util/ysafeptr.h b/util/ysafeptr.h index ab5f26fb1ba..807ed63b67e 100644 --- a/util/ysafeptr.h +++ b/util/ysafeptr.h @@ -91,6 +91,8 @@ protected: // function should clear contents of object, easy to implement via consequent calls to // destructor and constructor, this function should not be called directly, use Clear() virtual void DestroyContents() = 0; + // Unpoison ObjData/RefData to mute deliberate use-after-dtor + void UnpoisonCounters() noexcept; virtual ~IObjectBase() = default; inline void CopyValidFlag(const IObjectBase& a) { ObjData &= 0x7fffffff; @@ -207,6 +209,7 @@ public: \ protected: \ virtual void DestroyContents() override { \ this->~classname(); \ + this->UnpoisonCounters(); \ int nHoldRefs = this->RefData, nHoldObjs = this->ObjData; \ new (this) classname(); \ this->RefData += nHoldRefs; \ diff --git a/yql/essentials/core/peephole_opt/yql_opt_peephole_physical.cpp b/yql/essentials/core/peephole_opt/yql_opt_peephole_physical.cpp index 2c82652f573..1b74ca1e817 100644 --- a/yql/essentials/core/peephole_opt/yql_opt_peephole_physical.cpp +++ b/yql/essentials/core/peephole_opt/yql_opt_peephole_physical.cpp @@ -3143,6 +3143,30 @@ TExprNode::TPtr ExpandPartitionsByKeys(const TExprNode::TPtr& node, TExprContext return ctx.ReplaceNode(node->Tail().TailPtr(), node->Tail().Head().Head(), std::move(sort)); } +TExprNode::TPtr ExpandLPartitionsByKeys(const TExprNode::TPtr& node, TExprContext& ctx) { + YQL_CLOG(DEBUG, CorePeepHole) << "Expand " << node->Content(); + return ctx.Builder(node->Pos()) + .Callable("Block") + .Lambda(0) + .Param("parent") + .Callable("Collect") + .Callable(0, "PartitionsByKeys") + .Callable(0, "Iterator") + .Add(0, node->HeadPtr()) + .Callable(1, "DependsOn") + .Arg(0, "parent") + .Seal() + .Seal() + .Add(1, node->ChildPtr(1)) + .Add(2, node->ChildPtr(2)) + .Add(3, node->ChildPtr(3)) + .Add(4, node->ChildPtr(4)) + .Seal() + .Seal() + .Seal() + .Seal().Build(); +} + TExprNode::TPtr ExpandIsKeySwitch(const TExprNode::TPtr& node, TExprContext& ctx) { YQL_CLOG(DEBUG, CorePeepHole) << "Expand " << node->Content(); return ctx.Builder(node->Pos()) @@ -9349,6 +9373,7 @@ struct TPeepHoleRules { {"Fold1Map", &CleckClosureOnUpperLambdaOverList<1U, 2U>}, {"Chain1Map", &CleckClosureOnUpperLambdaOverList<1U, 2U>}, {"PartitionsByKeys", &ExpandPartitionsByKeys}, + {"LPartitionsByKeys", &ExpandLPartitionsByKeys}, {"DictItems", &MapForOptionalContainer}, {"DictKeys", &MapForOptionalContainer}, {"DictPayloads", &MapForOptionalContainer}, diff --git a/yql/essentials/core/type_ann/type_ann_blocks.cpp b/yql/essentials/core/type_ann/type_ann_blocks.cpp index eee3e6a8db3..dd5e3dae0a4 100644 --- a/yql/essentials/core/type_ann/type_ann_blocks.cpp +++ b/yql/essentials/core/type_ann/type_ann_blocks.cpp @@ -1377,10 +1377,24 @@ IGraphTransformer::TStatus BlockPgCallWrapper(const TExprNode::TPtr& input, TExp } auto content = setting->Head().Content(); - ctx.Expr.AddError(TIssue(ctx.Expr.GetPosition(input->Pos()), - TStringBuilder() << "Unexpected setting " << content << " in function " << name)); + if (content == "collation_oid") { + // BlockPgResolvedCall is only ever built by rewriting an already-resolved + // PgResolvedCall (see the peephole rewrite in yql_opt_peephole_physical.cpp), + // so its settings never carry a raw "collation" name - only the already-resolved + // "collation_oid", trusted as-is here, same as the analogous case in PgCallWrapper. + if (!EnsureTupleSize(*setting, 2, ctx.Expr)) { + return IGraphTransformer::TStatus::Error; + } - return IGraphTransformer::TStatus::Error; + if (!EnsureAtom(setting->Tail(), ctx.Expr)) { + return IGraphTransformer::TStatus::Error; + } + } else { + ctx.Expr.AddError(TIssue(ctx.Expr.GetPosition(input->Pos()), + TStringBuilder() << "Unexpected setting " << content << " in function " << name)); + + return IGraphTransformer::TStatus::Error; + } } TVector<ui32> argTypes; diff --git a/yql/essentials/core/type_ann/type_ann_core.cpp b/yql/essentials/core/type_ann/type_ann_core.cpp index 1fd05710857..74dca3df57a 100644 --- a/yql/essentials/core/type_ann/type_ann_core.cpp +++ b/yql/essentials/core/type_ann/type_ann_core.cpp @@ -16087,7 +16087,8 @@ template <NKikimr::NUdf::EDataSlot DataSlot> Functions["ToBytes"] = &ToBytesWrapper; Functions["GroupByKey"] = &GroupByKeyWrapper; Functions["PartitionByKey"] = &PartitionByKeyWrapper; - Functions["PartitionsByKeys"] = &PartitionsByKeysWrapper; + Functions["PartitionsByKeys"] = &PartitionsByKeysWrapper<false>; + Functions["LPartitionsByKeys"] = &PartitionsByKeysWrapper<true>; Functions["Reverse"] = &ReverseWrapper; ExtFunctions["Skip"] = &TakeWrapperEx; ExtFunctions["Take"] = &TakeWrapperEx; diff --git a/yql/essentials/core/type_ann/type_ann_list.cpp b/yql/essentials/core/type_ann/type_ann_list.cpp index bfdcb22552b..d2cd11fad47 100644 --- a/yql/essentials/core/type_ann/type_ann_list.cpp +++ b/yql/essentials/core/type_ann/type_ann_list.cpp @@ -4369,6 +4369,7 @@ namespace { return IGraphTransformer::TStatus::Ok; } + template<bool LPartitionsByKeys> IGraphTransformer::TStatus PartitionsByKeysWrapper(const TExprNode::TPtr& input, TExprNode::TPtr& output, TContext& ctx) { Y_UNUSED(output); if (!EnsureArgsCount(*input, 5, ctx.Expr)) { @@ -4381,8 +4382,15 @@ namespace { } const TTypeAnnotationNode* itemType = nullptr; - if (!EnsureNewSeqType<false>(input->Head(), ctx.Expr, &itemType)) { - return IGraphTransformer::TStatus::Error; + if constexpr (LPartitionsByKeys) { + if (!EnsureListType(input->Head(), ctx.Expr)) { + return IGraphTransformer::TStatus::Error; + } + itemType = input->Head().GetTypeAnn()->Cast<TListExprType>()->GetItemType(); + } else { + if (!EnsureNewSeqType<false>(input->Head(), ctx.Expr, &itemType)) { + return IGraphTransformer::TStatus::Error; + } } auto& lambdaKeySelector = input->ChildRef(1); @@ -4450,7 +4458,11 @@ namespace { return IGraphTransformer::TStatus::Error; } - if (!UpdateLambdaAllArgumentsTypes(lambdaFinalHandler, { input->Head().GetTypeAnn() }, ctx.Expr)) { + const auto lambdaFinalHandlerArgType = LPartitionsByKeys + ? ctx.Expr.MakeType<TStreamExprType>(itemType) + : input->Head().GetTypeAnn(); + + if (!UpdateLambdaAllArgumentsTypes(lambdaFinalHandler, { lambdaFinalHandlerArgType }, ctx.Expr)) { return IGraphTransformer::TStatus::Error; } @@ -4467,10 +4479,17 @@ namespace { return IGraphTransformer::TStatus::Error; } - input->SetTypeAnn(lambdaFinalHandler->GetTypeAnn()); + if constexpr (LPartitionsByKeys) { + input->SetTypeAnn(ctx.Expr.MakeType<TListExprType>(&GetSeqItemType(*lambdaFinalHandler->GetTypeAnn()))); + } else { + input->SetTypeAnn(lambdaFinalHandler->GetTypeAnn()); + } return IGraphTransformer::TStatus::Ok; } + template IGraphTransformer::TStatus PartitionsByKeysWrapper<true>(const TExprNode::TPtr& input, TExprNode::TPtr& output, TContext& ctx); + template IGraphTransformer::TStatus PartitionsByKeysWrapper<false>(const TExprNode::TPtr& input, TExprNode::TPtr& output, TContext& ctx); + IGraphTransformer::TStatus ReverseWrapper(const TExprNode::TPtr& input, TExprNode::TPtr& output, TContext& ctx) { if (!EnsureArgsCount(*input, 1, ctx.Expr)) { return IGraphTransformer::TStatus::Error; diff --git a/yql/essentials/core/type_ann/type_ann_list.h b/yql/essentials/core/type_ann/type_ann_list.h index 026a0e38a19..5a4d8038e74 100644 --- a/yql/essentials/core/type_ann/type_ann_list.h +++ b/yql/essentials/core/type_ann/type_ann_list.h @@ -89,6 +89,7 @@ namespace NYql::NTypeAnnImpl { IGraphTransformer::TStatus SessionWindowTraitsWrapper(const TExprNode::TPtr& input, TExprNode::TPtr& output, TContext& ctx); IGraphTransformer::TStatus GroupByKeyWrapper(const TExprNode::TPtr& input, TExprNode::TPtr& output, TContext& ctx); IGraphTransformer::TStatus PartitionByKeyWrapper(const TExprNode::TPtr& input, TExprNode::TPtr& output, TContext& ctx); + template<bool LPartitionsByKeys> IGraphTransformer::TStatus PartitionsByKeysWrapper(const TExprNode::TPtr& input, TExprNode::TPtr& output, TContext& ctx); IGraphTransformer::TStatus ReverseWrapper(const TExprNode::TPtr& input, TExprNode::TPtr& output, TContext& ctx); IGraphTransformer::TStatus TakeWrapperEx(const TExprNode::TPtr& input, TExprNode::TPtr& output, TExtContext& ctx); diff --git a/yql/essentials/core/type_ann/type_ann_pg.cpp b/yql/essentials/core/type_ann/type_ann_pg.cpp index accf9548635..18255f683b4 100644 --- a/yql/essentials/core/type_ann/type_ann_pg.cpp +++ b/yql/essentials/core/type_ann/type_ann_pg.cpp @@ -254,6 +254,34 @@ IGraphTransformer::TStatus PgCallWrapper(const TExprNode::TPtr& input, TExprNode } refinedType = NPg::LookupType(TString(setting->Tail().Content())).TypeId; + } else if (content == "collation") { + if (!EnsureTupleSize(*setting, 2, ctx.Expr)) { + return IGraphTransformer::TStatus::Error; + } + + if (!EnsureAtom(setting->Tail(), ctx.Expr)) { + return IGraphTransformer::TStatus::Error; + } + + TString collationName(setting->Tail().Content()); + try { + NPg::LookupCollation(collationName); + } catch (const yexception& e) { + ctx.Expr.AddError(TIssue(ctx.Expr.GetPosition(input->Pos()), + TStringBuilder() << "Failed to resolve collation \"" << collationName << "\" for function " << name << ": " << e.what())); + return IGraphTransformer::TStatus::Error; + } + } else if (content == "collation_oid") { + // Resolved collation oid, computed once from the "collation" setting above + // when PgResolvedCall was built (see below) - trusted as-is, same as the + // resolved proc oid in input->Child(1) is trusted without re-resolving the name. + if (!EnsureTupleSize(*setting, 2, ctx.Expr)) { + return IGraphTransformer::TStatus::Error; + } + + if (!EnsureAtom(setting->Tail(), ctx.Expr)) { + return IGraphTransformer::TStatus::Error; + } } else { ctx.Expr.AddError(TIssue(ctx.Expr.GetPosition(input->Pos()), TStringBuilder() << "Unexpected setting " << content << " in function " << name)); @@ -373,6 +401,32 @@ IGraphTransformer::TStatus PgCallWrapper(const TExprNode::TPtr& input, TExprNode children.push_back(std::move(node)); } + // If the call had an explicit COLLATE, resolve it to a stable oid (see + // NPg::LookupCollation) and replace the "collation" (name) setting with the + // resolved "collation_oid" one - the name has done its job (validation) and + // the oid is now the single source of truth, same as how the function name + // atom stays but the unresolved settings entry itself doesn't need to persist. + auto& settingsNode = children[argStart - 1]; + for (const auto& setting : settingsNode->Children()) { + if (setting->Head().Content() != "collation") { + continue; + } + + auto collationOid = NPg::LookupCollation(TString(setting->Tail().Content())).Oid; + TExprNode::TListType newSettings; + for (const auto& other : settingsNode->Children()) { + if (other->Head().Content() != "collation") { + newSettings.push_back(other); + } + } + newSettings.push_back(ctx.Expr.NewList(input->Pos(), { + ctx.Expr.NewAtom(input->Pos(), "collation_oid"), + ctx.Expr.NewAtom(input->Pos(), ToString(collationOid)), + })); + settingsNode = ctx.Expr.NewList(input->Pos(), std::move(newSettings)); + break; + } + if (proc.Lang == NPg::LangSQL) { if (!proc.ExprNode) { throw yexception() << "Function " << proc.Name << " has no implementation"; diff --git a/yql/essentials/minikql/mkql_program_builder.cpp b/yql/essentials/minikql/mkql_program_builder.cpp index 7a902067c85..87ed9bbbeb7 100644 --- a/yql/essentials/minikql/mkql_program_builder.cpp +++ b/yql/essentials/minikql/mkql_program_builder.cpp @@ -6185,23 +6185,59 @@ TRuntimeNode TProgramBuilder::PgConst(TPgType* pgType, const std::string_view& v TRuntimeNode TProgramBuilder::PgResolvedCall(bool useContext, const std::string_view& name, ui32 id, const TArrayRef<const TRuntimeNode>& args, - TType* returnType, bool rangeFunction) { - TCallableBuilder callableBuilder(Env_, __func__, returnType); - callableBuilder.Add(NewDataLiteral(useContext)); - callableBuilder.Add(NewDataLiteral(rangeFunction)); - callableBuilder.Add(NewDataLiteral<NUdf::EDataSlot::String>(name)); - callableBuilder.Add(NewDataLiteral(id)); - for (const auto& arg : args) { - callableBuilder.Add(arg); + TType* returnType, bool rangeFunction, ui32 collationOid) { + // On old runtimes, always use the "PgResolvedCall" callable exactly as before (and + // explicit COLLATE, which needs the collation oid slot, isn't representable at all). + // Once the runtime is new enough, ALWAYS use the distinct "PgResolvedCall2" callable - + // with an extra collation oid slot right after `id` - even when collationOid is 0, so + // that "PgResolvedCall" stops being emitted entirely once the whole fleet is upgraded + // past this version, and its read-side support can eventually be deleted. The two + // shapes are told apart by callable name rather than input count/type, since an arg's + // own static type can legitimately be non-Pg (e.g. a NULL literal). + if constexpr (RuntimeVersion < 81U) { + Y_ENSURE(collationOid == 0, "Explicit COLLATE requires minikql runtime version >= 81, current: " << RuntimeVersion); + TCallableBuilder callableBuilder(Env_, "PgResolvedCall", returnType); + callableBuilder.Add(NewDataLiteral(useContext)); + callableBuilder.Add(NewDataLiteral(rangeFunction)); + callableBuilder.Add(NewDataLiteral<NUdf::EDataSlot::String>(name)); + callableBuilder.Add(NewDataLiteral(id)); + for (const auto& arg : args) { + callableBuilder.Add(arg); + } + return TRuntimeNode(callableBuilder.Build(), /*isImmediate=*/false); + } else { + TCallableBuilder callableBuilder(Env_, "PgResolvedCall2", returnType); + callableBuilder.Add(NewDataLiteral(useContext)); + callableBuilder.Add(NewDataLiteral(rangeFunction)); + callableBuilder.Add(NewDataLiteral<NUdf::EDataSlot::String>(name)); + callableBuilder.Add(NewDataLiteral(id)); + callableBuilder.Add(NewDataLiteral(collationOid)); + for (const auto& arg : args) { + callableBuilder.Add(arg); + } + return TRuntimeNode(callableBuilder.Build(), /*isImmediate=*/false); } - return TRuntimeNode(callableBuilder.Build(), /*isImmediate=*/false); } TRuntimeNode TProgramBuilder::BlockPgResolvedCall(const std::string_view& name, ui32 id, - const TArrayRef<const TRuntimeNode>& args, TType* returnType) { - TCallableBuilder callableBuilder(Env_, __func__, returnType); + const TArrayRef<const TRuntimeNode>& args, TType* returnType, + ui32 collationOid) { + // Mirrors TProgramBuilder::PgResolvedCall - see the comment there. + if constexpr (RuntimeVersion < 81U) { + Y_ENSURE(collationOid == 0, "Explicit COLLATE requires minikql runtime version >= 81, current: " << RuntimeVersion); + TCallableBuilder callableBuilder(Env_, "BlockPgResolvedCall", returnType); + callableBuilder.Add(NewDataLiteral<NUdf::EDataSlot::String>(name)); + callableBuilder.Add(NewDataLiteral(id)); + for (const auto& arg : args) { + callableBuilder.Add(arg); + } + return TRuntimeNode(callableBuilder.Build(), /*isImmediate=*/false); + } + + TCallableBuilder callableBuilder(Env_, "BlockPgResolvedCall2", returnType); callableBuilder.Add(NewDataLiteral<NUdf::EDataSlot::String>(name)); callableBuilder.Add(NewDataLiteral(id)); + callableBuilder.Add(NewDataLiteral(collationOid)); for (const auto& arg : args) { callableBuilder.Add(arg); } diff --git a/yql/essentials/minikql/mkql_program_builder.h b/yql/essentials/minikql/mkql_program_builder.h index 7499782e162..32fccf7c7c8 100644 --- a/yql/essentials/minikql/mkql_program_builder.h +++ b/yql/essentials/minikql/mkql_program_builder.h @@ -288,7 +288,8 @@ public: TRuntimeNode BlockToPg(TRuntimeNode input, TType* returnType); TRuntimeNode BlockFromPg(TRuntimeNode input, TType* returnType); TRuntimeNode BlockPgResolvedCall(const std::string_view& name, ui32 id, - const TArrayRef<const TRuntimeNode>& args, TType* returnType); + const TArrayRef<const TRuntimeNode>& args, TType* returnType, + ui32 collationOid); TRuntimeNode BlockStorage(TRuntimeNode list, TType* returnType); TRuntimeNode BlockMapJoinIndex(TRuntimeNode blockStorage, TType* listItemType, const TArrayRef<const ui32>& keyColumns, bool any, TType* returnType); TRuntimeNode BlockMapJoinCore(TRuntimeNode leftStream, TRuntimeNode rightBlockStorage, TType* rightListItemType, EJoinKind joinKind, @@ -750,7 +751,8 @@ public: TRuntimeNode PgConst(TPgType* pgType, const std::string_view& value, TRuntimeNode typeMod = {}); TRuntimeNode PgResolvedCall(bool useContext, const std::string_view& name, ui32 id, - const TArrayRef<const TRuntimeNode>& args, TType* returnType, bool rangeFunction); + const TArrayRef<const TRuntimeNode>& args, TType* returnType, bool rangeFunction, + ui32 collationOid); TRuntimeNode PgCast(TRuntimeNode input, TType* returnType, TRuntimeNode typeMod = {}); TRuntimeNode FromPg(TRuntimeNode input, TType* returnType); TRuntimeNode ToPg(TRuntimeNode input, TType* returnType); diff --git a/yql/essentials/parser/pg_catalog/catalog.cpp b/yql/essentials/parser/pg_catalog/catalog.cpp index 88a0a8ac1fc..847bdb6f236 100644 --- a/yql/essentials/parser/pg_catalog/catalog.cpp +++ b/yql/essentials/parser/pg_catalog/catalog.cpp @@ -59,6 +59,8 @@ using TAggregations = THashMap<ui32, TAggregateDesc>; using TAms = THashMap<ui32, TAmDesc>; +using TCollations = THashMap<ui32, TCollationDesc>; + using TNamespaces = THashMap<decltype(TNamespaceDesc::Oid), TNamespaceDesc>; using TOpFamilies = THashMap<TString, TOpFamilyDesc>; @@ -1299,6 +1301,53 @@ private: TAms& Ams_; }; +class TCollationsParser: public TParser { +public: + explicit TCollationsParser(TCollations& collations) + : Collations_(collations) + { + } + + void OnKey(const TString& key, const TString& value) override { + if (key == "oid") { + CurrDesc_.Oid = FromString<ui32>(value); + } else if (key == "descr") { + CurrDesc_.Descr = value; + } else if (key == "collname") { + CurrDesc_.Name = value; + } else if (key == "collprovider") { + Y_ENSURE(value.size() == 1); + switch (value[0]) { + case (char)ECollationProvider::Default: + case (char)ECollationProvider::Icu: + case (char)ECollationProvider::Libc: + CurrDesc_.Provider = (ECollationProvider)value[0]; + break; + default: + throw yexception() << "Unknown collprovider value: " << value; + } + } else if (key == "collencoding") { + CurrDesc_.Encoding = FromString<i32>(value); + } else if (key == "collcollate") { + CurrDesc_.Collate = value; + } else if (key == "collctype") { + CurrDesc_.Ctype = value; + } else if (key == "colliculocale") { + CurrDesc_.IcuLocale = value; + } + } + + void OnFinish() override { + Y_ENSURE(!CurrDesc_.Name.empty()); + Collations_[CurrDesc_.Oid] = std::move(CurrDesc_); + CurrDesc_ = TCollationDesc(); + } + +private: + TCollationDesc CurrDesc_; + TCollations& Collations_; +}; + class TAmProcsParser: public TParser { public: TAmProcsParser(TAmProcs& amProcs, const THashMap<TString, ui32>& typeByName, @@ -1582,6 +1631,13 @@ TAms ParseAms(const TString& dat) { return ret; } +TCollations ParseCollations(const TString& dat) { + TCollations ret; + TCollationsParser parser(ret); + parser.Do(dat); + return ret; +} + TLanguages ParseLanguages(const TString& dat) { TLanguages ret; TLanguagesParser parser(ret); @@ -1634,6 +1690,14 @@ const char* AllowedProcsRaw[] = { #include "postgis_procs.h" }; +// Locale names known to the ICU library pg_collation_icu.generated.h was generated from +// (see gen_icu_collations). Append-only: a locale's position here becomes part of its +// collation oid (see IcuCollationOidBase below), so it must never change once assigned. +// NOLINTNEXTLINE(modernize-avoid-c-arrays) +const char* AllIcuCollationLocalesRaw[] = { +#include "pg_collation_icu.generated.h" +}; + struct TCatalog: public IExtensionSqlBuilder { TCatalog() { Init(); @@ -1733,6 +1797,8 @@ struct TCatalog: public IExtensionSqlBuilder { Y_ENSURE(NResource::FindExact("pg_conversion.dat", &conversionData)); TString amData; Y_ENSURE(NResource::FindExact("pg_am.dat", &amData)); + TString collationData; + Y_ENSURE(NResource::FindExact("pg_collation.dat", &collationData)); TString languagesData; Y_ENSURE(NResource::FindExact("pg_language.dat", &languagesData)); THashMap<ui32, TLazyTypeInfo> lazyTypeInfos; @@ -1860,6 +1926,22 @@ struct TCatalog: public IExtensionSqlBuilder { State->AmOps = ParseAmOps(amOpData, State->TypeByName, State->Types, State->OperatorsByName, State->Operators, State->OpFamilies); State->AmProcs = ParseAmProcs(amProcData, State->TypeByName, State->ProcByName, State->Procs, State->OpFamilies); State->Ams = ParseAms(amData); + State->Collations = ParseCollations(collationData); + for (const auto& [k, v] : State->Collations) { + Y_ENSURE(State->CollationByName.insert(std::make_pair(v.Name, k)).second); + } + + for (size_t i = 0; i < Y_ARRAY_SIZE(AllIcuCollationLocalesRaw); ++i) { + TCollationDesc desc; + desc.Oid = IcuCollationOidBase + i; + desc.IcuLocale = AllIcuCollationLocalesRaw[i]; + desc.Name = desc.IcuLocale + "-x-icu"; + desc.Provider = ECollationProvider::Icu; + desc.Descr = "ICU locale collation"; + Y_ENSURE(State->Collations.insert(std::make_pair(desc.Oid, desc)).second); + Y_ENSURE(State->CollationByName.insert(std::make_pair(desc.Name, desc.Oid)).second); + } + State->Namespaces = FillNamespaces(); for (auto& [k, v] : State->Types) { if (v.TypeId != v.ArrayTypeId) { @@ -2263,6 +2345,7 @@ struct TCatalog: public IExtensionSqlBuilder { TCasts Casts; TAggregations Aggregations; TAms Ams; + TCollations Collations; TNamespaces Namespaces; TOpFamilies OpFamilies; TOpClasses OpClasses; @@ -2272,6 +2355,7 @@ struct TCatalog: public IExtensionSqlBuilder { TLanguages Languages; THashMap<TString, TVector<ui32>> ProcByName; THashMap<TString, ui32> TypeByName; + THashMap<TString, ui32> CollationByName; THashMap<std::pair<ui32, ui32>, ui32> CastsByDir; THashMap<TString, TVector<ui32>> OperatorsByName; THashMap<TString, TVector<ui32>> AggregationsByName; @@ -2463,6 +2547,38 @@ void EnumAm(std::function<void(ui32, const TAmDesc&)> f) { } } +bool HasCollation(const TString& name) { + const auto& catalog = TCatalog::Instance(); + return catalog.State->CollationByName.contains(name); +} + +const TCollationDesc& LookupCollation(const TString& name) { + const auto& catalog = TCatalog::Instance(); + const auto oidPtr = catalog.State->CollationByName.FindPtr(name); + if (!oidPtr) { + throw yexception() << "No such collation: " << name; + } + + return LookupCollation(*oidPtr); +} + +const TCollationDesc& LookupCollation(ui32 oid) { + const auto& catalog = TCatalog::Instance(); + const auto descPtr = catalog.State->Collations.FindPtr(oid); + if (!descPtr) { + throw yexception() << "No such collation: " << oid; + } + + return *descPtr; +} + +void EnumCollation(std::function<void(ui32, const TCollationDesc&)> f) { + const auto& catalog = TCatalog::Instance(); + for (const auto& [oid, desc] : catalog.State->Collations) { + f(oid, desc); + } +} + void EnumConversions(std::function<void(const TConversionDesc&)> f) { const auto& catalog = TCatalog::Instance(); for (const auto& [_, desc] : catalog.State->Conversions) { diff --git a/yql/essentials/parser/pg_catalog/catalog.h b/yql/essentials/parser/pg_catalog/catalog.h index 1f30f692706..e06b7616d44 100644 --- a/yql/essentials/parser/pg_catalog/catalog.h +++ b/yql/essentials/parser/pg_catalog/catalog.h @@ -91,6 +91,32 @@ constexpr ui32 DefaultCollationOid = 100; constexpr ui32 C_CollationOid = 950; constexpr ui32 PosixCollationOid = 951; +// Oid space reserved for ICU locale collations (name "<locale>-x-icu"), kept clear of the +// oids used by pg_collation.dat (which top out in the low thousands). +// A locale's oid is IcuCollationOidBase plus its position in the checked-in, append-only +// pg_collation_icu.generated.h (see gen_icu_collations) - stable across processes/rebuilds +// regardless of which ICU library version is actually linked, since it is our own versioned +// data rather than something derived live from whatever ICU happens to be available. +constexpr ui32 IcuCollationOidBase = 1'000'000; + +// Copied from pg_collation.h, COLLPROVIDER_* constants +enum class ECollationProvider: char { + Default = 'd', + Icu = 'i', + Libc = 'c', +}; + +struct TCollationDesc { + ui32 Oid = 0; + TString Name; + TString Descr; + ECollationProvider Provider = ECollationProvider::Libc; + i32 Encoding = -1; + TString Collate; + TString Ctype; + TString IcuLocale; +}; + // Copied from pg_type_d.h, TYPTYPE_* constants enum class ETypType: char { Base = 'b', @@ -295,6 +321,11 @@ void EnumTypes(std::function<void(ui32, const TTypeDesc&)> f); const TAmDesc& LookupAm(ui32 oid); void EnumAm(std::function<void(ui32, const TAmDesc&)> f); +bool HasCollation(const TString& name); +const TCollationDesc& LookupCollation(const TString& name); +const TCollationDesc& LookupCollation(ui32 oid); +void EnumCollation(std::function<void(ui32, const TCollationDesc&)> f); + void EnumConversions(std::function<void(const TConversionDesc&)> f); const TNamespaceDesc& LookupNamespace(ui32 oid); @@ -497,6 +528,11 @@ inline void Out<NYql::NPg::ECoercionCode>(IOutputStream& out, NYql::NPg::ECoerci } template <> +inline void Out<NYql::NPg::ECollationProvider>(IOutputStream& out, NYql::NPg::ECollationProvider value) { + out.Write(static_cast<std::underlying_type<NYql::NPg::ECollationProvider>::type>(value)); +} + +template <> struct THash<NYql::NPg::TTableInfoKey> { size_t operator()(const NYql::NPg::TTableInfoKey& val) const { return val.Hash(); diff --git a/yql/essentials/parser/pg_catalog/gen_icu_collations/main.cpp b/yql/essentials/parser/pg_catalog/gen_icu_collations/main.cpp new file mode 100644 index 00000000000..9d9e4577806 --- /dev/null +++ b/yql/essentials/parser/pg_catalog/gen_icu_collations/main.cpp @@ -0,0 +1,107 @@ +// Regenerates pg_collation_icu.generated.h - the checked-in list of ICU collation locale +// names known to pg_catalog. Run manually and commit the result whenever the vendored ICU +// (contrib/libs/icu) is updated: +// +// ya make yql/essentials/parser/pg_catalog/gen_icu_collations +// yql/essentials/parser/pg_catalog/gen_icu_collations/gen_icu_collations \ +// yql/essentials/parser/pg_catalog/pg_collation_icu.generated.h +// +// The output is append-only: existing entries are read back first and kept in place, and +// only locales not already present are appended at the end. This keeps each locale's +// position in the file - and hence the collation oid pg_catalog derives from that position +// - stable across regenerations, even if the set of locales ICU knows about changes, +// exactly like AllStaticTablesRaw/pg_class.generated.h and library/cpp/type_info/tz do for +// their own generated tables. + +#include <unicode/uloc.h> + +#include <util/generic/algorithm.h> +#include <util/generic/hash_set.h> +#include <util/generic/string.h> +#include <util/generic/vector.h> +#include <util/generic/yexception.h> +#include <util/stream/file.h> +#include <util/system/fs.h> + +#include <cstdio> + +namespace { + +// Canonicalizes an ICU locale id (e.g. "de_DE") to its BCP47 language tag (e.g. "de-DE"). +// Returns an empty string if ICU can't parse it at all. +TString CanonicalizeIcuLocale(const TString& locale) { + UErrorCode status = U_ZERO_ERROR; + std::array<char, ULOC_FULLNAME_CAPACITY> tag; + auto len = uloc_toLanguageTag(locale.c_str(), tag.data(), tag.size(), /* strict */ true, &status); + if (U_FAILURE(status)) { + return {}; + } + + return TString(tag.data(), len); +} + +TVector<TString> ReadExistingLocales(const TString& path) { + TVector<TString> result; + if (!NFs::Exists(path)) { + return result; + } + + TFileInput input(path); + TString line; + while (input.ReadLine(line)) { + auto endQuote = line.find('"', 1); + Y_ENSURE(line.size() > 2 && line[0] == '"' && endQuote != TString::npos, "Malformed line in " << path << ": " << line); + result.push_back(line.substr(1, endQuote - 1)); + } + + return result; +} + +TVector<TString> DiscoverIcuLocales() { + TVector<TString> result; + if (auto root = CanonicalizeIcuLocale(""); !root.empty()) { + result.push_back(root); + } + + for (i32 i = 0; i < uloc_countAvailable(); ++i) { + if (auto tag = CanonicalizeIcuLocale(uloc_getAvailable(i)); !tag.empty()) { + result.push_back(tag); + } + } + + Sort(result.begin(), result.end()); + result.erase(Unique(result.begin(), result.end()), result.end()); + return result; +} + +} // namespace + +int main(int argc, char** argv) { + Y_ENSURE(argc == 2, "usage: gen_icu_collations <path-to-pg_collation_icu.generated.h>"); + TString outputPath = argv[1]; + + auto existing = ReadExistingLocales(outputPath); + THashSet<TString> seen(existing.begin(), existing.end()); + + TVector<TString> toAppend; + for (auto& tag : DiscoverIcuLocales()) { + if (seen.insert(tag).second) { + toAppend.push_back(tag); + } + } + + { + TFileOutput output(outputPath); + for (auto& tag : existing) { + output << "\"" << tag << "\",\n"; + } + + for (auto& tag : toAppend) { + output << "\"" << tag << "\",\n"; + } + } + + Cerr << "Wrote " << (existing.size() + toAppend.size()) << " ICU collation locales (" + << toAppend.size() << " new) to " << outputPath << Endl; + return 0; +} diff --git a/yql/essentials/parser/pg_catalog/gen_icu_collations/ya.make b/yql/essentials/parser/pg_catalog/gen_icu_collations/ya.make new file mode 100644 index 00000000000..b90c170c01a --- /dev/null +++ b/yql/essentials/parser/pg_catalog/gen_icu_collations/ya.make @@ -0,0 +1,11 @@ +PROGRAM(gen_icu_collations) + +SRCS( + main.cpp +) + +PEERDIR( + contrib/libs/icu +) + +END() diff --git a/yql/essentials/parser/pg_catalog/pg_collation_icu.generated.h b/yql/essentials/parser/pg_catalog/pg_collation_icu.generated.h new file mode 100644 index 00000000000..c1796fc4a7f --- /dev/null +++ b/yql/essentials/parser/pg_catalog/pg_collation_icu.generated.h @@ -0,0 +1,907 @@ +"af", +"af-NA", +"af-ZA", +"agq", +"agq-CM", +"ak", +"ak-GH", +"am", +"am-ET", +"ar", +"ar-001", +"ar-AE", +"ar-BH", +"ar-DJ", +"ar-DZ", +"ar-EG", +"ar-EH", +"ar-ER", +"ar-IL", +"ar-IQ", +"ar-JO", +"ar-KM", +"ar-KW", +"ar-LB", +"ar-LY", +"ar-MA", +"ar-MR", +"ar-OM", +"ar-PS", +"ar-QA", +"ar-SA", +"ar-SD", +"ar-SO", +"ar-SS", +"ar-SY", +"ar-TD", +"ar-TN", +"ar-YE", +"as", +"as-IN", +"asa", +"asa-TZ", +"ast", +"ast-ES", +"az", +"az-Cyrl", +"az-Cyrl-AZ", +"az-Latn", +"az-Latn-AZ", +"ba", +"ba-RU", +"bas", +"bas-CM", +"be", +"be-BY", +"bem", +"bem-ZM", +"bez", +"bez-TZ", +"bg", +"bg-BG", +"bgc", +"bgc-IN", +"bho", +"bho-IN", +"blo", +"blo-BJ", +"bm", +"bm-ML", +"bn", +"bn-BD", +"bn-IN", +"bo", +"bo-CN", +"bo-IN", +"br", +"br-FR", +"brx", +"brx-IN", +"bs", +"bs-Cyrl", +"bs-Cyrl-BA", +"bs-Latn", +"bs-Latn-BA", +"bua", +"bua-RU", +"ca", +"ca-AD", +"ca-ES", +"ca-FR", +"ca-IT", +"ccp", +"ccp-BD", +"ccp-IN", +"ce", +"ce-RU", +"ceb", +"ceb-PH", +"cgg", +"cgg-UG", +"chr", +"chr-US", +"ckb", +"ckb-IQ", +"ckb-IR", +"cs", +"cs-CZ", +"csw", +"csw-CA", +"cv", +"cv-RU", +"cy", +"cy-GB", +"da", +"da-DK", +"da-GL", +"dav", +"dav-KE", +"de", +"de-AT", +"de-BE", +"de-CH", +"de-DE", +"de-IT", +"de-LI", +"de-LU", +"dje", +"dje-NE", +"doi", +"doi-IN", +"dsb", +"dsb-DE", +"dua", +"dua-CM", +"dyo", +"dyo-SN", +"dz", +"dz-BT", +"ebu", +"ebu-KE", +"ee", +"ee-GH", +"ee-TG", +"el", +"el-CY", +"el-GR", +"en", +"en-001", +"en-150", +"en-AE", +"en-AG", +"en-AI", +"en-AS", +"en-AT", +"en-AU", +"en-BB", +"en-BE", +"en-BI", +"en-BM", +"en-BS", +"en-BW", +"en-BZ", +"en-CA", +"en-CC", +"en-CH", +"en-CK", +"en-CM", +"en-CX", +"en-CY", +"en-CZ", +"en-DE", +"en-DG", +"en-DK", +"en-DM", +"en-EE", +"en-ER", +"en-ES", +"en-FI", +"en-FJ", +"en-FK", +"en-FM", +"en-FR", +"en-GB", +"en-GD", +"en-GE", +"en-GG", +"en-GH", +"en-GI", +"en-GM", +"en-GS", +"en-GU", +"en-GY", +"en-HK", +"en-HU", +"en-ID", +"en-IE", +"en-IL", +"en-IM", +"en-IN", +"en-IO", +"en-IT", +"en-JE", +"en-JM", +"en-JP", +"en-KE", +"en-KI", +"en-KN", +"en-KY", +"en-LC", +"en-LR", +"en-LS", +"en-LT", +"en-LV", +"en-MG", +"en-MH", +"en-MO", +"en-MP", +"en-MS", +"en-MT", +"en-MU", +"en-MV", +"en-MW", +"en-MY", +"en-NA", +"en-NF", +"en-NG", +"en-NL", +"en-NO", +"en-NR", +"en-NU", +"en-NZ", +"en-PG", +"en-PH", +"en-PK", +"en-PL", +"en-PN", +"en-PR", +"en-PT", +"en-PW", +"en-RO", +"en-RW", +"en-SB", +"en-SC", +"en-SD", +"en-SE", +"en-SG", +"en-SH", +"en-SI", +"en-SK", +"en-SL", +"en-SS", +"en-SX", +"en-SZ", +"en-TC", +"en-TK", +"en-TO", +"en-TT", +"en-TV", +"en-TZ", +"en-UA", +"en-UG", +"en-UM", +"en-US", +"en-US-u-va-posix", +"en-VC", +"en-VG", +"en-VI", +"en-VU", +"en-WS", +"en-ZA", +"en-ZM", +"en-ZW", +"eo", +"eo-001", +"es", +"es-419", +"es-AR", +"es-BO", +"es-BR", +"es-BZ", +"es-CL", +"es-CO", +"es-CR", +"es-CU", +"es-DO", +"es-EA", +"es-EC", +"es-ES", +"es-GQ", +"es-GT", +"es-HN", +"es-IC", +"es-MX", +"es-NI", +"es-PA", +"es-PE", +"es-PH", +"es-PR", +"es-PY", +"es-SV", +"es-US", +"es-UY", +"es-VE", +"et", +"et-EE", +"eu", +"eu-ES", +"ewo", +"ewo-CM", +"fa", +"fa-AF", +"fa-IR", +"ff", +"ff-Adlm", +"ff-Adlm-BF", +"ff-Adlm-CM", +"ff-Adlm-GH", +"ff-Adlm-GM", +"ff-Adlm-GN", +"ff-Adlm-GW", +"ff-Adlm-LR", +"ff-Adlm-MR", +"ff-Adlm-NE", +"ff-Adlm-NG", +"ff-Adlm-SL", +"ff-Adlm-SN", +"ff-Latn", +"ff-Latn-BF", +"ff-Latn-CM", +"ff-Latn-GH", +"ff-Latn-GM", +"ff-Latn-GN", +"ff-Latn-GW", +"ff-Latn-LR", +"ff-Latn-MR", +"ff-Latn-NE", +"ff-Latn-NG", +"ff-Latn-SL", +"ff-Latn-SN", +"fi", +"fi-FI", +"fil", +"fil-PH", +"fo", +"fo-DK", +"fo-FO", +"fr", +"fr-BE", +"fr-BF", +"fr-BI", +"fr-BJ", +"fr-BL", +"fr-CA", +"fr-CD", +"fr-CF", +"fr-CG", +"fr-CH", +"fr-CI", +"fr-CM", +"fr-DJ", +"fr-DZ", +"fr-FR", +"fr-GA", +"fr-GF", +"fr-GN", +"fr-GP", +"fr-GQ", +"fr-HT", +"fr-KM", +"fr-LU", +"fr-MA", +"fr-MC", +"fr-MF", +"fr-MG", +"fr-ML", +"fr-MQ", +"fr-MR", +"fr-MU", +"fr-NC", +"fr-NE", +"fr-PF", +"fr-PM", +"fr-RE", +"fr-RW", +"fr-SC", +"fr-SN", +"fr-SY", +"fr-TD", +"fr-TG", +"fr-TN", +"fr-VU", +"fr-WF", +"fr-YT", +"fur", +"fur-IT", +"fy", +"fy-NL", +"ga", +"ga-GB", +"ga-IE", +"gaa", +"gaa-GH", +"gd", +"gd-GB", +"gl", +"gl-ES", +"gsw", +"gsw-CH", +"gsw-FR", +"gsw-LI", +"gu", +"gu-IN", +"guz", +"guz-KE", +"gv", +"gv-IM", +"ha", +"ha-GH", +"ha-NE", +"ha-NG", +"haw", +"haw-US", +"he", +"he-IL", +"hi", +"hi-IN", +"hi-Latn", +"hi-Latn-IN", +"hr", +"hr-BA", +"hr-HR", +"hsb", +"hsb-DE", +"hu", +"hu-HU", +"hy", +"hy-AM", +"ia", +"ia-001", +"id", +"id-ID", +"ie", +"ie-EE", +"ig", +"ig-NG", +"ii", +"ii-CN", +"is", +"is-IS", +"it", +"it-CH", +"it-IT", +"it-SM", +"it-VA", +"ja", +"ja-JP", +"jgo", +"jgo-CM", +"jmc", +"jmc-TZ", +"jv", +"jv-ID", +"ka", +"ka-GE", +"kab", +"kab-DZ", +"kam", +"kam-KE", +"kde", +"kde-TZ", +"kea", +"kea-CV", +"kgp", +"kgp-BR", +"khq", +"khq-ML", +"ki", +"ki-KE", +"kk", +"kk-Arab", +"kk-Arab-CN", +"kk-Cyrl", +"kk-Cyrl-KZ", +"kk-KZ", +"kkj", +"kkj-CM", +"kl", +"kl-GL", +"kln", +"kln-KE", +"km", +"km-KH", +"kn", +"kn-IN", +"ko", +"ko-CN", +"ko-KP", +"ko-KR", +"kok", +"kok-Deva", +"kok-Deva-IN", +"kok-Latn", +"kok-Latn-IN", +"ks", +"ks-Arab", +"ks-Arab-IN", +"ks-Deva", +"ks-Deva-IN", +"ksb", +"ksb-TZ", +"ksf", +"ksf-CM", +"ksh", +"ksh-DE", +"ku", +"ku-Latn", +"ku-Latn-IQ", +"ku-Latn-SY", +"ku-Latn-TR", +"ku-TR", +"kw", +"kw-GB", +"kxv", +"kxv-Deva", +"kxv-Deva-IN", +"kxv-Latn", +"kxv-Latn-IN", +"kxv-Orya", +"kxv-Orya-IN", +"kxv-Telu", +"kxv-Telu-IN", +"ky", +"ky-KG", +"lag", +"lag-TZ", +"lb", +"lb-LU", +"lg", +"lg-UG", +"lij", +"lij-IT", +"lkt", +"lkt-US", +"lmo", +"lmo-IT", +"ln", +"ln-AO", +"ln-CD", +"ln-CF", +"ln-CG", +"lo", +"lo-LA", +"lrc", +"lrc-IQ", +"lrc-IR", +"lt", +"lt-LT", +"lu", +"lu-CD", +"luo", +"luo-KE", +"luy", +"luy-KE", +"lv", +"lv-LV", +"mai", +"mai-IN", +"mas", +"mas-KE", +"mas-TZ", +"mer", +"mer-KE", +"mfe", +"mfe-MU", +"mg", +"mg-MG", +"mgh", +"mgh-MZ", +"mgo", +"mgo-CM", +"mi", +"mi-NZ", +"mk", +"mk-MK", +"ml", +"ml-IN", +"mn", +"mn-MN", +"mni", +"mni-Beng", +"mni-Beng-IN", +"mr", +"mr-IN", +"ms", +"ms-BN", +"ms-ID", +"ms-MY", +"ms-SG", +"mt", +"mt-MT", +"mua", +"mua-CM", +"my", +"my-MM", +"mzn", +"mzn-IR", +"naq", +"naq-NA", +"nb", +"nb-NO", +"nb-SJ", +"nd", +"nd-ZW", +"nds", +"nds-DE", +"nds-NL", +"ne", +"ne-IN", +"ne-NP", +"nl", +"nl-AW", +"nl-BE", +"nl-BQ", +"nl-CW", +"nl-NL", +"nl-SR", +"nl-SX", +"nmg", +"nmg-CM", +"nn", +"nn-NO", +"nnh", +"nnh-CM", +"no", +"nqo", +"nqo-GN", +"nso", +"nso-ZA", +"nus", +"nus-SS", +"nyn", +"nyn-UG", +"oc", +"oc-ES", +"oc-FR", +"om", +"om-ET", +"om-KE", +"or", +"or-IN", +"os", +"os-GE", +"os-RU", +"pa", +"pa-Arab", +"pa-Arab-PK", +"pa-Guru", +"pa-Guru-IN", +"pcm", +"pcm-NG", +"pl", +"pl-PL", +"pms", +"pms-IT", +"prg", +"prg-PL", +"ps", +"ps-AF", +"ps-PK", +"pt", +"pt-AO", +"pt-BR", +"pt-CH", +"pt-CV", +"pt-GQ", +"pt-GW", +"pt-LU", +"pt-MO", +"pt-MZ", +"pt-PT", +"pt-ST", +"pt-TL", +"qu", +"qu-BO", +"qu-EC", +"qu-PE", +"raj", +"raj-IN", +"rm", +"rm-CH", +"rn", +"rn-BI", +"ro", +"ro-MD", +"ro-RO", +"rof", +"rof-TZ", +"ru", +"ru-BY", +"ru-KG", +"ru-KZ", +"ru-MD", +"ru-RU", +"ru-UA", +"rw", +"rw-RW", +"rwk", +"rwk-TZ", +"sa", +"sa-IN", +"sah", +"sah-RU", +"saq", +"saq-KE", +"sat", +"sat-Olck", +"sat-Olck-IN", +"sbp", +"sbp-TZ", +"sc", +"sc-IT", +"scn", +"scn-IT", +"sd", +"sd-Arab", +"sd-Arab-PK", +"sd-Deva", +"sd-Deva-IN", +"se", +"se-FI", +"se-NO", +"se-SE", +"seh", +"seh-MZ", +"ses", +"ses-ML", +"sg", +"sg-CF", +"shi", +"shi-Latn", +"shi-Latn-MA", +"shi-Tfng", +"shi-Tfng-MA", +"shn", +"shn-MM", +"shn-TH", +"si", +"si-LK", +"sk", +"sk-SK", +"sl", +"sl-SI", +"smn", +"smn-FI", +"sn", +"sn-ZW", +"so", +"so-DJ", +"so-ET", +"so-KE", +"so-SO", +"sq", +"sq-AL", +"sq-MK", +"sq-XK", +"sr", +"sr-Cyrl", +"sr-Cyrl-BA", +"sr-Cyrl-ME", +"sr-Cyrl-RS", +"sr-Cyrl-XK", +"sr-Latn", +"sr-Latn-BA", +"sr-Latn-ME", +"sr-Latn-RS", +"sr-Latn-XK", +"st", +"st-LS", +"st-ZA", +"su", +"su-Latn", +"su-Latn-ID", +"sv", +"sv-AX", +"sv-FI", +"sv-SE", +"sw", +"sw-CD", +"sw-KE", +"sw-TZ", +"sw-UG", +"syr", +"syr-IQ", +"syr-SY", +"szl", +"szl-PL", +"ta", +"ta-IN", +"ta-LK", +"ta-MY", +"ta-SG", +"te", +"te-IN", +"teo", +"teo-KE", +"teo-UG", +"tg", +"tg-TJ", +"th", +"th-TH", +"ti", +"ti-ER", +"ti-ET", +"tk", +"tk-TM", +"tn", +"tn-BW", +"tn-ZA", +"to", +"to-TO", +"tok", +"tok-001", +"tr", +"tr-CY", +"tr-TR", +"tt", +"tt-RU", +"twq", +"twq-NE", +"tyv", +"tyv-RU", +"tzm", +"tzm-MA", +"ug", +"ug-CN", +"uk", +"uk-UA", +"und", +"ur", +"ur-IN", +"ur-PK", +"uz", +"uz-Arab", +"uz-Arab-AF", +"uz-Cyrl", +"uz-Cyrl-UZ", +"uz-Latn", +"uz-Latn-UZ", +"vai", +"vai-Latn", +"vai-Latn-LR", +"vai-Vaii", +"vai-Vaii-LR", +"vec", +"vec-IT", +"vi", +"vi-VN", +"vmw", +"vmw-MZ", +"vun", +"vun-TZ", +"wae", +"wae-CH", +"wo", +"wo-SN", +"xh", +"xh-ZA", +"xnr", +"xnr-IN", +"xog", +"xog-UG", +"yav", +"yav-CM", +"yi", +"yi-UA", +"yo", +"yo-BJ", +"yo-NG", +"yrl", +"yrl-BR", +"yrl-CO", +"yrl-VE", +"yue", +"yue-Hans", +"yue-Hans-CN", +"yue-Hant", +"yue-Hant-CN", +"yue-Hant-HK", +"yue-Hant-MO", +"za", +"za-CN", +"zgh", +"zgh-MA", +"zh", +"zh-Hans", +"zh-Hans-CN", +"zh-Hans-HK", +"zh-Hans-MO", +"zh-Hans-MY", +"zh-Hans-SG", +"zh-Hant", +"zh-Hant-HK", +"zh-Hant-MO", +"zh-Hant-MY", +"zh-Hant-TW", +"zu", +"zu-ZA", diff --git a/yql/essentials/parser/pg_catalog/ut/catalog_ut.cpp b/yql/essentials/parser/pg_catalog/ut/catalog_ut.cpp index 0dd8e623c14..1bc9f4bf3cd 100644 --- a/yql/essentials/parser/pg_catalog/ut/catalog_ut.cpp +++ b/yql/essentials/parser/pg_catalog/ut/catalog_ut.cpp @@ -207,3 +207,69 @@ Y_UNIT_TEST(TestOk) { UNIT_ASSERT_VALUES_EQUAL(LookupProc(procId).Name, "iso8859_1_to_utf8"); } } // Y_UNIT_TEST_SUITE(TConversionTests) + +Y_UNIT_TEST_SUITE(TCollationTests) { +Y_UNIT_TEST(TestMissing) { + UNIT_ASSERT(!HasCollation("_foo_bar_")); + UNIT_ASSERT_EXCEPTION(LookupCollation("_foo_bar_"), yexception); + UNIT_ASSERT_EXCEPTION(LookupCollation((ui32)123456), yexception); +} + +Y_UNIT_TEST(TestDefault) { + UNIT_ASSERT(HasCollation("default")); + const auto& ret = LookupCollation("default"); + UNIT_ASSERT_VALUES_EQUAL(ret.Oid, DefaultCollationOid); + UNIT_ASSERT_VALUES_EQUAL(ret.Provider, ECollationProvider::Default); + UNIT_ASSERT_VALUES_EQUAL(LookupCollation(ret.Oid).Name, "default"); +} + +Y_UNIT_TEST(TestC) { + const auto& ret = LookupCollation("C"); + UNIT_ASSERT_VALUES_EQUAL(ret.Oid, C_CollationOid); + UNIT_ASSERT_VALUES_EQUAL(ret.Provider, ECollationProvider::Libc); + UNIT_ASSERT_VALUES_EQUAL(ret.Collate, "C"); + UNIT_ASSERT_VALUES_EQUAL(ret.Ctype, "C"); +} + +Y_UNIT_TEST(TestPosix) { + const auto& ret = LookupCollation("POSIX"); + UNIT_ASSERT_VALUES_EQUAL(ret.Oid, PosixCollationOid); + UNIT_ASSERT_VALUES_EQUAL(ret.Provider, ECollationProvider::Libc); +} + +Y_UNIT_TEST(TestUnicodeIcu) { + const auto& ret = LookupCollation("unicode"); + UNIT_ASSERT_VALUES_EQUAL(ret.Provider, ECollationProvider::Icu); + UNIT_ASSERT_VALUES_EQUAL(ret.IcuLocale, "und"); +} + +Y_UNIT_TEST(TestEnum) { + size_t count = 0; + EnumCollation([&](ui32 oid, const TCollationDesc& desc) { + UNIT_ASSERT_VALUES_EQUAL(LookupCollation(desc.Name).Oid, oid); + ++count; + }); + + // 5 statically catalogued (pg_collation.dat) + generated ICU locale collations. + UNIT_ASSERT(count > 5); +} + +Y_UNIT_TEST(TestIcuLocale) { + const auto& ret = LookupCollation("de-DE-x-icu"); + UNIT_ASSERT_VALUES_EQUAL(ret.Provider, ECollationProvider::Icu); + UNIT_ASSERT_VALUES_EQUAL(ret.IcuLocale, "de-DE"); + UNIT_ASSERT(ret.Oid >= IcuCollationOidBase); + UNIT_ASSERT_VALUES_EQUAL(LookupCollation(ret.Oid).Name, "de-DE-x-icu"); + + // The same locale always resolves to the same oid. + UNIT_ASSERT_VALUES_EQUAL(ret.Oid, LookupCollation("de-DE-x-icu").Oid); + + // Different locales get different oids. + UNIT_ASSERT_UNEQUAL(ret.Oid, LookupCollation("en-US-x-icu").Oid); +} + +Y_UNIT_TEST(TestIcuLocaleMissing) { + UNIT_ASSERT(!HasCollation("xx-XX-x-icu")); + UNIT_ASSERT_EXCEPTION(LookupCollation("xx-XX-x-icu"), yexception); +} +} // Y_UNIT_TEST_SUITE(TCollationTests) diff --git a/yql/essentials/parser/pg_catalog/ya.make b/yql/essentials/parser/pg_catalog/ya.make index f7eecb3e7a8..ab657f751dc 100644 --- a/yql/essentials/parser/pg_catalog/ya.make +++ b/yql/essentials/parser/pg_catalog/ya.make @@ -13,6 +13,7 @@ RESOURCE(../pg_wrapper/postgresql/src/include/catalog/pg_opclass.dat pg_opclass. RESOURCE(../pg_wrapper/postgresql/src/include/catalog/pg_amproc.dat pg_amproc.dat) RESOURCE(../pg_wrapper/postgresql/src/include/catalog/pg_amop.dat pg_amop.dat) RESOURCE(../pg_wrapper/postgresql/src/include/catalog/pg_am.dat pg_am.dat) +RESOURCE(../pg_wrapper/postgresql/src/include/catalog/pg_collation.dat pg_collation.dat) RESOURCE(../pg_wrapper/postgresql/src/include/catalog/pg_conversion.dat pg_conversion.dat) RESOURCE(../pg_wrapper/postgresql/src/include/catalog/pg_language.dat pg_language.dat) RESOURCE(../pg_wrapper/postgresql/src/backend/catalog/system_functions.sql system_functions.sql) @@ -31,6 +32,10 @@ PEERDIR( END() +RECURSE( + gen_icu_collations +) + RECURSE_FOR_TESTS( ut ) diff --git a/yql/essentials/parser/pg_wrapper/comp_factory.cpp b/yql/essentials/parser/pg_wrapper/comp_factory.cpp index 78b7e4c59fa..27601a078b3 100644 --- a/yql/essentials/parser/pg_wrapper/comp_factory.cpp +++ b/yql/essentials/parser/pg_wrapper/comp_factory.cpp @@ -463,6 +463,21 @@ public: }; ApplyFillers(AllPgExtensionFillers, Y_ARRAY_SIZE(AllPgExtensionFillers), PgExtensionFillers_); + } else if (Table_ == "pg_collation") { + static const std::pair<const char*, TPgCollationFiller> AllPgCollationFillers[] = { + {"oid", [](const NPg::TCollationDesc& desc) { return ScalarDatumToPod(ObjectIdGetDatum(desc.Oid)); }}, + {"collname", [](const NPg::TCollationDesc& desc) { return PointerDatumToPod((Datum)MakeFixedString(desc.Name, NAMEDATALEN)); }}, + {"collnamespace", [](const NPg::TCollationDesc&) { return ScalarDatumToPod(ObjectIdGetDatum(PG_CATALOG_NAMESPACE)); }}, + {"collowner", [](const NPg::TCollationDesc&) { return ScalarDatumToPod(ObjectIdGetDatum(1)); }}, + {"collprovider", [](const NPg::TCollationDesc& desc) { return ScalarDatumToPod(CharGetDatum((char)desc.Provider)); }}, + {"collisdeterministic", [](const NPg::TCollationDesc&) { return ScalarDatumToPod(BoolGetDatum(true)); }}, + {"collencoding", [](const NPg::TCollationDesc& desc) { return ScalarDatumToPod(Int32GetDatum(desc.Encoding)); }}, + {"collcollate", [](const NPg::TCollationDesc& desc) { return desc.Collate.empty() ? NUdf::TUnboxedValuePod() : PointerDatumToPod((Datum)MakeVar(desc.Collate)); }}, + {"collctype", [](const NPg::TCollationDesc& desc) { return desc.Ctype.empty() ? NUdf::TUnboxedValuePod() : PointerDatumToPod((Datum)MakeVar(desc.Ctype)); }}, + {"colliculocale", [](const NPg::TCollationDesc& desc) { return desc.IcuLocale.empty() ? NUdf::TUnboxedValuePod() : PointerDatumToPod((Datum)MakeVar(desc.IcuLocale)); }}, + }; + + ApplyFillers(AllPgCollationFillers, Y_ARRAY_SIZE(AllPgCollationFillers), PgCollationFillers_); } } else { if (Table_ == "tables") { @@ -864,6 +879,19 @@ public: sysFiller.Fill(items); rows.emplace_back(row); }); + } else if (Table_ == "pg_collation") { + NPg::EnumCollation([&](ui32, const NPg::TCollationDesc& desc) { + NUdf::TUnboxedValue* items; + auto row = compCtx.HolderFactory.CreateDirectArrayHolder(PgCollationFillers_.size(), items); + for (ui32 i = 0; i < PgCollationFillers_.size(); ++i) { + if (PgCollationFillers_[i]) { + items[i] = PgCollationFillers_[i](desc); + } + } + + sysFiller.Fill(items); + rows.emplace_back(row); + }); } } else { if (Table_ == "tables") { @@ -952,6 +980,9 @@ private: using TPgProcFiller = NUdf::TUnboxedValuePod(*)(const NPg::TProcDesc&); TVector<TPgProcFiller> PgProcFillers_; + using TPgCollationFiller = NUdf::TUnboxedValuePod(*)(const NPg::TCollationDesc&); + TVector<TPgCollationFiller> PgCollationFillers_; + using TPgAggregateFiller = NUdf::TUnboxedValuePod(*)(const NPg::TAggregateDesc&); TVector<TPgAggregateFiller> PgAggregateFillers_; @@ -1033,8 +1064,8 @@ private: }; struct TPgResolvedCallNodeState : public TPgResolvedCallState, public TComputationValue<TPgResolvedCallNodeState> { - TPgResolvedCallNodeState(TMemoryUsageInfo* memInfo, ui32 numArgs, const FmgrInfo* finfo) - : TPgResolvedCallState(numArgs, finfo) + TPgResolvedCallNodeState(TMemoryUsageInfo* memInfo, ui32 numArgs, const FmgrInfo* finfo, ui32 collationOid) + : TPgResolvedCallState(numArgs, finfo, collationOid) , TComputationValue(memInfo) {} }; @@ -1043,10 +1074,11 @@ class TPgResolvedCallNode : public TPgResolvedCall<UseContext>, public TMutableC typedef TMutableComputationNode<TPgResolvedCallNode<UseContext>> TBaseComputation; public: TPgResolvedCallNode(TComputationMutables& mutables, const std::string_view& name, ui32 id, - TComputationNodePtrVector&& argNodes, TVector<TType*>&& argTypes, TType* returnType) + TComputationNodePtrVector&& argNodes, TVector<TType*>&& argTypes, TType* returnType, ui32 collationOid) : TPgResolvedCall<UseContext>(name, id, std::move(argTypes), returnType) , TBaseComputation(mutables) , ArgNodes(std::move(argNodes)) + , CollationOid(collationOid) , StateIndex(mutables.CurValueIndex++) { Y_ENSURE(ArgNodes.size() == this->ArgDesc.size()); @@ -1068,13 +1100,14 @@ private: TPgResolvedCallNodeState& GetState(TComputationContext& compCtx) const { auto& result = compCtx.MutableValues[this->StateIndex]; if (!result.HasValue()) { - result = compCtx.HolderFactory.Create<TPgResolvedCallNodeState>(this->ArgNodes.size(), &this->FInfo); + result = compCtx.HolderFactory.Create<TPgResolvedCallNodeState>(this->ArgNodes.size(), &this->FInfo, CollationOid); } return *static_cast<TPgResolvedCallNodeState*>(result.AsBoxed().Get()); } const TComputationNodePtrVector ArgNodes; + const ui32 CollationOid; const ui32 StateIndex; }; @@ -1087,14 +1120,14 @@ private: public: TIterator(TMemoryUsageInfo* memInfo, const std::string_view& name, const TUnboxedValueVector& args, const TVector<NPg::TTypeDesc>& argDesc, const NPg::TTypeDesc& retTypeDesc, const NPg::TProcDesc& procDesc, - const FmgrInfo* fInfo, const TStructType* structType, const TVector<NPg::TTypeDesc>& structTypeDesc, const THolderFactory& holderFactory) + const FmgrInfo* fInfo, ui32 collationOid, const TStructType* structType, const TVector<NPg::TTypeDesc>& structTypeDesc, const THolderFactory& holderFactory) : TComputationValue<TIterator>(memInfo) , Name(name) , Args(args) , ArgDesc(argDesc) , RetTypeDesc(retTypeDesc) , ProcDesc(procDesc) - , CallInfo(argDesc.size(), fInfo) + , CallInfo(argDesc.size(), fInfo, collationOid) , StructType(structType) , StructTypeDesc(structTypeDesc) , HolderFactory(holderFactory) @@ -1302,7 +1335,7 @@ private: TListValue(TMemoryUsageInfo* memInfo, TComputationContext& compCtx, const std::string_view& name, TUnboxedValueVector&& args, const TVector<NPg::TTypeDesc>& argDesc, - const NPg::TTypeDesc& retTypeDesc, const NPg::TProcDesc& procDesc, const FmgrInfo* fInfo, + const NPg::TTypeDesc& retTypeDesc, const NPg::TProcDesc& procDesc, const FmgrInfo* fInfo, ui32 collationOid, const TStructType* structType, const TVector<NPg::TTypeDesc>& structTypeDesc, const THolderFactory& /*holderFactory*/) : TCustomListValue(memInfo) , CompCtx(compCtx) @@ -1312,6 +1345,7 @@ private: , RetTypeDesc(retTypeDesc) , ProcDesc(procDesc) , FInfo(fInfo) + , CollationOid(collationOid) , StructType(structType) , StructTypeDesc(structTypeDesc) { @@ -1319,7 +1353,7 @@ private: private: NUdf::TUnboxedValue GetListIterator() const final { - return CompCtx.HolderFactory.Create<TIterator>(Name, Args, ArgDesc, RetTypeDesc, ProcDesc, FInfo, StructType, StructTypeDesc, CompCtx.HolderFactory); + return CompCtx.HolderFactory.Create<TIterator>(Name, Args, ArgDesc, RetTypeDesc, ProcDesc, FInfo, CollationOid, StructType, StructTypeDesc, CompCtx.HolderFactory); } TComputationContext& CompCtx; @@ -1329,16 +1363,18 @@ private: const NPg::TTypeDesc& RetTypeDesc; const NPg::TProcDesc& ProcDesc; const FmgrInfo* FInfo; + const ui32 CollationOid; const TStructType* StructType; const TVector<NPg::TTypeDesc>& StructTypeDesc; }; public: TPgResolvedMultiCall(TComputationMutables& mutables, const std::string_view& name, ui32 id, - TComputationNodePtrVector&& argNodes, TVector<TType*>&& argTypes, TType* returnType, const TStructType* structType) + TComputationNodePtrVector&& argNodes, TVector<TType*>&& argTypes, TType* returnType, const TStructType* structType, ui32 collationOid) : TPgResolvedCallBase(name, id, std::move(argTypes), returnType, true, structType) , TBaseComputation(mutables) , ArgNodes(std::move(argNodes)) + , CollationOid(collationOid) , StructType(structType) { Y_ENSURE(ArgNodes.size() == ArgDesc.size()); @@ -1352,7 +1388,7 @@ public: args.push_back(value); } - return compCtx.HolderFactory.Create<TListValue>(compCtx, Name, std::move(args), ArgDesc, RetTypeDesc, ProcDesc, &FInfo, StructType, StructTypeDesc, compCtx.HolderFactory); + return compCtx.HolderFactory.Create<TListValue>(compCtx, Name, std::move(args), ArgDesc, RetTypeDesc, ProcDesc, &FInfo, CollationOid, StructType, StructTypeDesc, compCtx.HolderFactory); } private: @@ -1362,6 +1398,7 @@ private: } } const TComputationNodePtrVector ArgNodes; + const ui32 CollationOid; const TStructType* StructType; }; @@ -2757,7 +2794,7 @@ std::shared_ptr<arrow::compute::ScalarKernel> MakeToPgKernel(TType* inputType, T return kernel; } -std::shared_ptr<arrow::compute::ScalarKernel> MakePgKernel(TVector<TType*> argTypes, TType* resultType, TExecFunc execFunc, ui32 procId) { +std::shared_ptr<arrow::compute::ScalarKernel> MakePgKernel(TVector<TType*> argTypes, TType* resultType, TExecFunc execFunc, ui32 procId, ui32 collationOid) { std::shared_ptr<arrow::DataType> returnArrowType; MKQL_ENSURE(ConvertArrowType(AS_TYPE(TBlockType, resultType)->GetItemType(), returnArrowType), "Unsupported arrow type"); auto kernel = std::make_shared<arrow::compute::ScalarKernel>(ConvertToInputTypes(argTypes), ConvertToOutputType(resultType), @@ -2778,14 +2815,14 @@ std::shared_ptr<arrow::compute::ScalarKernel> MakePgKernel(TVector<TType*> argTy } kernel->null_handling = arrow::compute::NullHandling::COMPUTED_NO_PREALLOCATE; - kernel->init = [procId, pgArgTypes](arrow::compute::KernelContext*, const arrow::compute::KernelInitArgs&) { + kernel->init = [procId, pgArgTypes, collationOid](arrow::compute::KernelContext*, const arrow::compute::KernelInitArgs&) { auto state = std::make_unique<TPgKernelState>(); Zero(state->flinfo); GetPgFuncAddr(procId, state->flinfo); YQL_ENSURE(state->flinfo.fn_addr); state->resultinfo = nullptr; state->context = nullptr; - state->fncollation = DEFAULT_COLLATION_OID; + state->fncollation = collationOid ? collationOid : DEFAULT_COLLATION_OID; const auto& procDesc = NPg::LookupProc(procId); const auto& retTypeDesc = NPg::LookupType(procDesc.ResultType); state->Name = procDesc.Name; @@ -2855,7 +2892,8 @@ TComputationNodeFactory GetPgFactory() { return new TPgToRecord(ctx.Mutables, input, structType, std::move(members)); } - if (name == "PgResolvedCall") { + if (name == "PgResolvedCall" || name == "PgResolvedCall2") { + const bool hasCollation = (name == "PgResolvedCall2"); const auto useContextData = AS_VALUE(TDataLiteral, callable.GetInput(0)); const auto rangeFunctionData = AS_VALUE(TDataLiteral, callable.GetInput(1)); const auto nameData = AS_VALUE(TDataLiteral, callable.GetInput(2)); @@ -2864,9 +2902,18 @@ TComputationNodeFactory GetPgFactory() { auto rangeFunction = rangeFunctionData->AsValue().Get<bool>(); auto name = nameData->AsValue().AsStringRef(); auto id = idData->AsValue().Get<ui32>(); + + ui32 collationOid = 0; + ui32 argsStart = 4; + if (hasCollation) { + const auto collationOidData = AS_VALUE(TDataLiteral, callable.GetInput(4)); + collationOid = collationOidData->AsValue().Get<ui32>(); + argsStart = 5; + } + TComputationNodePtrVector argNodes; TVector<TType*> argTypes; - for (ui32 i = 4; i < callable.GetInputsCount(); ++i) { + for (ui32 i = argsStart; i < callable.GetInputsCount(); ++i) { argNodes.emplace_back(LocateNode(ctx.NodeLocator, callable, i)); argTypes.emplace_back(callable.GetInput(i).GetStaticType()); } @@ -2883,25 +2930,35 @@ TComputationNodeFactory GetPgFactory() { if (isList) { YQL_ENSURE(!useContext); - return new TPgResolvedMultiCall(ctx.Mutables, name, id, std::move(argNodes), std::move(argTypes), itemType, structType); + return new TPgResolvedMultiCall(ctx.Mutables, name, id, std::move(argNodes), std::move(argTypes), itemType, structType, collationOid); } else { YQL_ENSURE(!structType); if (useContext) { - return new TPgResolvedCallNode<true>(ctx.Mutables, name, id, std::move(argNodes), std::move(argTypes), returnType); + return new TPgResolvedCallNode<true>(ctx.Mutables, name, id, std::move(argNodes), std::move(argTypes), returnType, collationOid); } else { - return new TPgResolvedCallNode<false>(ctx.Mutables, name, id, std::move(argNodes), std::move(argTypes), returnType); + return new TPgResolvedCallNode<false>(ctx.Mutables, name, id, std::move(argNodes), std::move(argTypes), returnType, collationOid); } } } - if (name == "BlockPgResolvedCall") { + if (name == "BlockPgResolvedCall" || name == "BlockPgResolvedCall2") { + const bool hasCollation = (name == "BlockPgResolvedCall2"); const auto nameData = AS_VALUE(TDataLiteral, callable.GetInput(0)); const auto idData = AS_VALUE(TDataLiteral, callable.GetInput(1)); auto name = nameData->AsValue().AsStringRef(); auto id = idData->AsValue().Get<ui32>(); + + ui32 collationOid = 0; + ui32 argsStart = 2; + if (hasCollation) { + const auto collationOidData = AS_VALUE(TDataLiteral, callable.GetInput(2)); + collationOid = collationOidData->AsValue().Get<ui32>(); + argsStart = 3; + } + TComputationNodePtrVector argNodes; TVector<TType*> argTypes; - for (ui32 i = 2; i < callable.GetInputsCount(); ++i) { + for (ui32 i = argsStart; i < callable.GetInputsCount(); ++i) { argNodes.emplace_back(LocateNode(ctx.NodeLocator, callable, i)); argTypes.emplace_back(callable.GetInput(i).GetStaticType()); } @@ -2909,7 +2966,7 @@ TComputationNodeFactory GetPgFactory() { auto returnType = callable.GetType()->GetReturnType(); auto execFunc = FindExec(id); YQL_ENSURE(execFunc); - auto kernel = MakePgKernel(argTypes, returnType, execFunc, id); + auto kernel = MakePgKernel(argTypes, returnType, execFunc, id, collationOid); return new TBlockFuncNode(ctx.Mutables, ctx.RuntimeSettings->DatumValidation.Get(), callable.GetType()->GetName(), std::move(argNodes), argTypes, returnType, *kernel, kernel); } diff --git a/yql/essentials/parser/pg_wrapper/comp_factory_utils.cpp b/yql/essentials/parser/pg_wrapper/comp_factory_utils.cpp index 06b75438043..309e03621ea 100644 --- a/yql/essentials/parser/pg_wrapper/comp_factory_utils.cpp +++ b/yql/essentials/parser/pg_wrapper/comp_factory_utils.cpp @@ -169,7 +169,7 @@ NUdf::TUnboxedValue TPgConst::ExtractConst(i32 typeMod) const { return AnyDatumToPod(ret, TypeDesc.PassByValue); } -TFunctionCallInfo::TFunctionCallInfo(ui32 numArgs, const FmgrInfo* finfo) +TFunctionCallInfo::TFunctionCallInfo(ui32 numArgs, const FmgrInfo* finfo, ui32 collationOid) : NumArgs(numArgs) , CopyFmgrInfo(*finfo) { @@ -183,7 +183,7 @@ TFunctionCallInfo::TFunctionCallInfo(ui32 numArgs, const FmgrInfo* finfo) Zero(callInfo); callInfo.flinfo = &CopyFmgrInfo; // client may mutate fn_extra callInfo.nargs = NumArgs; - callInfo.fncollation = DEFAULT_COLLATION_OID; + callInfo.fncollation = collationOid ? collationOid : DEFAULT_COLLATION_OID; } FunctionCallInfoBaseData& TFunctionCallInfo::Ref() { @@ -197,8 +197,8 @@ TFunctionCallInfo::~TFunctionCallInfo() { } } -TPgResolvedCallState::TPgResolvedCallState(ui32 numArgs, const FmgrInfo* finfo) - : CallInfo(numArgs, finfo) +TPgResolvedCallState::TPgResolvedCallState(ui32 numArgs, const FmgrInfo* finfo, ui32 collationOid) + : CallInfo(numArgs, finfo, collationOid) , Args(numArgs) { } @@ -248,7 +248,7 @@ NUdf::TUnboxedValue TPgResolvedCall<UseContext>::CallFunction(TPgResolvedCallSta template <bool UseContext> THolder<TPgResolvedCallState> TPgResolvedCall<UseContext>::CreateState() const { - return MakeHolder<TPgResolvedCallState>(GetArgCount(), &FInfo); + return MakeHolder<TPgResolvedCallState>(GetArgCount(), &FInfo, NPg::InvalidCollationOid); } TPgCast::TPgCast(ui32 sourceId, ui32 targetId, bool hasTypeMod) @@ -496,7 +496,7 @@ template class TPgResolvedCall<false>; TPgResolvedCallWithCastState::TPgResolvedCallWithCastState( const TPgResolvedCall<false>& caller, const TVector<TPgCast>& casters) - : CallState(caller.GetArgCount(), &caller.GetFInfo()) + : CallState(caller.GetArgCount(), &caller.GetFInfo(), NPg::InvalidCollationOid) { CastStates.reserve(casters.size()); for (const auto& caster : casters) { diff --git a/yql/essentials/parser/pg_wrapper/comp_factory_utils.h b/yql/essentials/parser/pg_wrapper/comp_factory_utils.h index f31ebedfa9f..804ea63ab2a 100644 --- a/yql/essentials/parser/pg_wrapper/comp_factory_utils.h +++ b/yql/essentials/parser/pg_wrapper/comp_factory_utils.h @@ -103,7 +103,7 @@ protected: class TFunctionCallInfo { public: - TFunctionCallInfo(ui32 numArgs, const FmgrInfo* finfo); + TFunctionCallInfo(ui32 numArgs, const FmgrInfo* finfo, ui32 collationOid); ~TFunctionCallInfo(); TFunctionCallInfo(const TFunctionCallInfo&) = delete; @@ -119,7 +119,7 @@ private: }; struct TPgResolvedCallState { - TPgResolvedCallState(ui32 numArgs, const FmgrInfo* finfo); + TPgResolvedCallState(ui32 numArgs, const FmgrInfo* finfo, ui32 collationOid); TFunctionCallInfo CallInfo; NKikimr::NMiniKQL::TUnboxedValueVector Args; @@ -205,8 +205,8 @@ extern template class TPgResolvedCall<false>; struct TPgCastState { TPgCastState(const FmgrInfo* finfo1, const FmgrInfo* finfo2) - : CallInfo1(3, finfo1) - , CallInfo2(1, finfo2) + : CallInfo1(3, finfo1, NPg::InvalidCollationOid) + , CallInfo2(1, finfo2, NPg::InvalidCollationOid) { } diff --git a/yql/essentials/parser/pg_wrapper/pg_ops.cpp b/yql/essentials/parser/pg_wrapper/pg_ops.cpp index af7205eeb32..b9133398c22 100644 --- a/yql/essentials/parser/pg_wrapper/pg_ops.cpp +++ b/yql/essentials/parser/pg_wrapper/pg_ops.cpp @@ -52,8 +52,8 @@ public: : ZeroInterval_(zeroInterval) , EqCall_(eqCall) , GtCall_(gtCall) - , EqState_(MakeHolder<TPgResolvedCallState>(eqCall.GetArgCount(), &eqCall.GetFInfo())) - , GtState_(MakeHolder<TPgResolvedCallState>(gtCall.GetArgCount(), >Call.GetFInfo())) + , EqState_(MakeHolder<TPgResolvedCallState>(eqCall.GetArgCount(), &eqCall.GetFInfo(), NPg::InvalidCollationOid)) + , GtState_(MakeHolder<TPgResolvedCallState>(gtCall.GetArgCount(), >Call.GetFInfo(), NPg::InvalidCollationOid)) { } diff --git a/yql/essentials/parser/pg_wrapper/syscache.cpp b/yql/essentials/parser/pg_wrapper/syscache.cpp index 6e476f45108..7e95e9e3dfe 100644 --- a/yql/essentials/parser/pg_wrapper/syscache.cpp +++ b/yql/essentials/parser/pg_wrapper/syscache.cpp @@ -12,6 +12,7 @@ extern "C" { #include "catalog/pg_namespace.h" #include "catalog/pg_type.h" #include "catalog/pg_type_d.h" +#include "catalog/pg_collation.h" #include "catalog/pg_authid.h" #include "access/htup_details.h" #include "utils/fmgroids.h" @@ -129,7 +130,7 @@ bool ByNameProcEquals3(const THeapTupleKey& key1, const THeapTupleKey& key2) { } struct TSysCacheItem { - TSysCacheItem(THeapTupleHasher hasher, THeapTupleEquals equals, TupleDesc desc, ui32 numKeys = 1, + TSysCacheItem(THeapTupleHasher hasher, THeapTupleEquals equals, TupleDesc desc, ui32 numKeys = 1, THeapTupleHasher hasherRange1 = {}, THeapTupleEquals equalsRange1 = {}) : NumKeys(numKeys) , LookupMap(0, hasher, equals) @@ -199,7 +200,7 @@ struct TSysCache { static TSysCache& MutableInstance() { return *Singleton<TSysCache>(); - } + } TSysCache() { @@ -219,6 +220,7 @@ struct TSysCache { InitializeProcs(); InitializeTypes(); + InitializeCollations(); InitializeDatabase(); InitializeAuthId(); InitializeNameAndOidNamespaces(); @@ -477,7 +479,7 @@ struct TSysCache { Y_ENSURE(row->typalign == desc.TypeAlign); Y_ENSURE(row->typstorage == storage); - auto key1 = THeapTupleKey(oid, 0, 0, 0); + auto key1 = THeapTupleKey(oid, 0, 0, 0); lookupMap1.emplace(key1, h); auto key2 = THeapTupleKey((Datum)desc.Name.c_str(), PG_CATALOG_NAMESPACE, 0, 0); lookupMap2.emplace(key2, h); @@ -485,6 +487,59 @@ struct TSysCache { } + void InitializeCollations() { + TupleDesc tupleDesc = CreateTemplateTupleDesc(Natts_pg_collation); + FillAttr(tupleDesc, Anum_pg_collation_oid, OIDOID); + FillAttr(tupleDesc, Anum_pg_collation_collname, NAMEOID); + FillAttr(tupleDesc, Anum_pg_collation_collnamespace, OIDOID); + FillAttr(tupleDesc, Anum_pg_collation_collowner, OIDOID); + FillAttr(tupleDesc, Anum_pg_collation_collprovider, CHAROID); + FillAttr(tupleDesc, Anum_pg_collation_collisdeterministic, BOOLOID); + FillAttr(tupleDesc, Anum_pg_collation_collencoding, INT4OID); + FillAttr(tupleDesc, Anum_pg_collation_collcollate, TEXTOID); + FillAttr(tupleDesc, Anum_pg_collation_collctype, TEXTOID); + FillAttr(tupleDesc, Anum_pg_collation_colliculocale, TEXTOID); + FillAttr(tupleDesc, Anum_pg_collation_collicurules, TEXTOID); + FillAttr(tupleDesc, Anum_pg_collation_collversion, TEXTOID); + + auto& cacheItem = Items[COLLOID] = std::make_unique<TSysCacheItem>(OidHasher1, OidEquals1, tupleDesc); + auto& lookupMap = cacheItem->LookupMap; + + NPg::EnumCollation([&](ui32 oid, const NPg::TCollationDesc& desc) { + Datum values[Natts_pg_collation]; + bool nulls[Natts_pg_collation]; + Zero(values); + std::fill_n(nulls, Natts_pg_collation, true); + std::fill_n(nulls, Anum_pg_collation_collencoding, false); // fixed part of Form_pg_collation + FillDatum(Natts_pg_collation, values, nulls, Anum_pg_collation_oid, oid); + auto name = MakeFixedString(desc.Name, NAMEDATALEN); + FillDatum(Natts_pg_collation, values, nulls, Anum_pg_collation_collname, (Datum)name); + FillDatum(Natts_pg_collation, values, nulls, Anum_pg_collation_collnamespace, PG_CATALOG_NAMESPACE); + FillDatum(Natts_pg_collation, values, nulls, Anum_pg_collation_collowner, 1); + FillDatum(Natts_pg_collation, values, nulls, Anum_pg_collation_collprovider, (char)desc.Provider); + FillDatum(Natts_pg_collation, values, nulls, Anum_pg_collation_collisdeterministic, true); + FillDatum(Natts_pg_collation, values, nulls, Anum_pg_collation_collencoding, desc.Encoding); + if (!desc.Collate.empty()) { + FillDatum(Natts_pg_collation, values, nulls, Anum_pg_collation_collcollate, (Datum)MakeVar(desc.Collate)); + } + if (!desc.Ctype.empty()) { + FillDatum(Natts_pg_collation, values, nulls, Anum_pg_collation_collctype, (Datum)MakeVar(desc.Ctype)); + } + if (!desc.IcuLocale.empty()) { + FillDatum(Natts_pg_collation, values, nulls, Anum_pg_collation_colliculocale, (Datum)MakeVar(desc.IcuLocale)); + } + + HeapTuple h = heap_form_tuple(tupleDesc, values, nulls); + auto row = (Form_pg_collation)GETSTRUCT(h); + Y_ENSURE(row->oid == oid); + Y_ENSURE(NameStr(row->collname) == desc.Name); + Y_ENSURE(row->collprovider == (char)desc.Provider); + + auto key = THeapTupleKey(oid, 0, 0, 0); + lookupMap.emplace(key, h); + }); + } + static HeapTuple MakePgDatabaseHeapTuple(ui32 oid, const char* name) { TupleDesc tupleDesc = CreateTemplateTupleDesc(Natts_pg_database); FillAttr(tupleDesc, Anum_pg_database_oid, OIDOID); diff --git a/yql/essentials/providers/common/mkql/yql_provider_mkql.cpp b/yql/essentials/providers/common/mkql/yql_provider_mkql.cpp index d3f2d071b18..21b1137e6ef 100644 --- a/yql/essentials/providers/common/mkql/yql_provider_mkql.cpp +++ b/yql/essentials/providers/common/mkql/yql_provider_mkql.cpp @@ -2819,14 +2819,17 @@ TMkqlCommonCallableCompiler::TShared::TShared() { } bool rangeFunction = false; + ui32 collationOid = 0; for (const auto& child : node.Child(2)->Children()) { if (child->Head().Content() == "range") { rangeFunction = true; + } else if (child->Head().Content() == "collation_oid") { + collationOid = FromString<ui32>(child->Tail().Content()); } } auto returnType = ctx.BuildType(node, *node.GetTypeAnn()); - return ctx.ProgramBuilder.PgResolvedCall(node.IsCallable("PgResolvedCallCtx"), name, id, args, returnType, rangeFunction); + return ctx.ProgramBuilder.PgResolvedCall(node.IsCallable("PgResolvedCallCtx"), name, id, args, returnType, rangeFunction, collationOid); }); AddCallable("PgResolvedOp", [](const TExprNode& node, TMkqlBuildContext& ctx) { @@ -2840,7 +2843,7 @@ TMkqlCommonCallableCompiler::TShared::TShared() { } auto returnType = ctx.BuildType(node, *node.GetTypeAnn()); - return ctx.ProgramBuilder.PgResolvedCall(/*useContext=*/false, procName, procId, args, returnType, /*rangeFunction=*/false); + return ctx.ProgramBuilder.PgResolvedCall(/*useContext=*/false, procName, procId, args, returnType, /*rangeFunction=*/false, NPg::InvalidCollationOid); }); AddCallable("BlockPgResolvedCall", [](const TExprNode& node, TMkqlBuildContext& ctx) { @@ -2852,8 +2855,15 @@ TMkqlCommonCallableCompiler::TShared::TShared() { args.push_back(MkqlBuildExpr(*node.Child(i), ctx)); } + ui32 collationOid = 0; + for (const auto& child : node.Child(2)->Children()) { + if (child->Head().Content() == "collation_oid") { + collationOid = FromString<ui32>(child->Tail().Content()); + } + } + auto returnType = ctx.BuildType(node, *node.GetTypeAnn()); - return ctx.ProgramBuilder.BlockPgResolvedCall(name, id, args, returnType); + return ctx.ProgramBuilder.BlockPgResolvedCall(name, id, args, returnType, collationOid); }); AddCallable("BlockPgResolvedOp", [](const TExprNode& node, TMkqlBuildContext& ctx) { @@ -2867,7 +2877,7 @@ TMkqlCommonCallableCompiler::TShared::TShared() { } auto returnType = ctx.BuildType(node, *node.GetTypeAnn()); - return ctx.ProgramBuilder.BlockPgResolvedCall(procName, procId, args, returnType); + return ctx.ProgramBuilder.BlockPgResolvedCall(procName, procId, args, returnType, NPg::InvalidCollationOid); }); AddCallable("PgCast", [](const TExprNode& node, TMkqlBuildContext& ctx) { diff --git a/yql/essentials/sql/pg/pg_sql.cpp b/yql/essentials/sql/pg/pg_sql.cpp index 6990cbfefa7..9ea2a53e936 100644 --- a/yql/essentials/sql/pg/pg_sql.cpp +++ b/yql/essentials/sql/pg/pg_sql.cpp @@ -3725,6 +3725,33 @@ public: return result; } + bool ExtractCollationName(const CollateClause* value, TString& name) { + auto len = ListLength(value->collname); + if (len == 0) { + AddError("CollateClause: missing collation name"); + return false; + } + + auto x = ListNodeNth(value->collname, len - 1); + if (NodeTag(x) != T_String) { + NodeNotImplemented(value, x); + return false; + } + + name = StrVal(x); + return true; + } + + TAstNode* ParseCollateClause(const CollateClause* value, const TExprSettings& settings) { + AT_LOCATION(value); + // Collation propagation through arbitrary expressions (COALESCE, CASE, plain + // projections, ...) is not implemented - only ParseFuncCall picks up an explicit + // COLLATE that directly wraps one of its arguments (see there). Elsewhere COLLATE + // only affects collation-sensitive semantics (comparison, case mapping), not the + // expression's runtime value, so we just parse through to the wrapped expression. + return ParseExpr(value->arg, settings); + } + TAstNode* ParseExpr(const Node* node, const TExprSettings& settings) { switch (NodeTag(node)) { case T_A_Const: { @@ -3742,6 +3769,9 @@ public: case T_TypeCast: { return ParseTypeCast(CAST_NODE(TypeCast, node), settings); } + case T_CollateClause: { + return ParseCollateClause(CAST_NODE(CollateClause, node), settings); + } case T_BoolExpr: { return ParseBoolExpr(CAST_NODE(BoolExpr, node), settings); } @@ -4267,6 +4297,32 @@ public: callSettings.push_back(QL(QA("range"))); } + if (!value->agg_star) { + TMaybe<TString> collation; + for (int i = 0; i < ListLength(value->args); ++i) { + auto x = ListNodeNth(value->args, i); + if (NodeTag(x) != T_CollateClause) { + continue; + } + + TString collationName; + if (!ExtractCollationName(CAST_NODE(CollateClause, x), collationName)) { + return nullptr; + } + + if (collation && *collation != collationName) { + AddError(TStringBuilder() << "FuncCall: conflicting explicit collations: " << *collation << " and " << collationName); + return nullptr; + } + + collation = collationName; + } + + if (collation) { + callSettings.push_back(QL(QA("collation"), QAX(*collation))); + } + } + args.push_back(QVL(callSettings.data(), callSettings.size())); if (value->agg_star) { if (name != "count") { diff --git a/yql/essentials/sql/v1/builtin.cpp b/yql/essentials/sql/v1/builtin.cpp index cb91f956f89..43eeb6af9b4 100644 --- a/yql/essentials/sql/v1/builtin.cpp +++ b/yql/essentials/sql/v1/builtin.cpp @@ -860,7 +860,35 @@ public: } Args_[0] = BuildQuotedAtom(Args_[0]->GetPos(), Args_[0]->GetLiteralValue()); - Args_.insert(Args_.begin() + 1, RangeFunction ? Q(Y(Q(Y(Q("range"))))) : Q(Y())); + + TNodePtr settingsList = Y(); + if (RangeFunction) { + settingsList = L(settingsList, Q(Y(Q("range")))); + } + + // Optional named argument: PgCall("upper", x, "de-DE-x-icu" as Collation) + for (size_t i = 1; i < Args_.size();) { + const auto& label = Args_[i]->GetLabel(); + if (label.empty()) { + ++i; + continue; + } + + if (label != "Collation") { + ctx.Error(Args_[i]->GetPos()) << "PgCall: unsupported named argument: " << label << "; expected: Collation"; + return false; + } + + auto arg = Args_[i]; + if (!arg->Init(ctx, src)) { + return false; + } + + settingsList = L(settingsList, Q(Y(Q("collation"), MakeAtomFromExpression(Pos_, ctx, arg).Build()))); + Args_.erase(Args_.begin() + i); + } + + Args_.insert(Args_.begin() + 1, Q(settingsList)); return TCallNode::DoInit(ctx, src); } @@ -4218,6 +4246,28 @@ TNodeResult BuildBuiltinFunc( return TNonNull(TNodePtr(new TInvalidBuiltin(pos, "DISTINCT can only be used in aggregation functions"))); } + if ((normalizedName == "pgcall" || normalizedName == "pgrangecall") && mustUseNamed && *mustUseNamed) { + // PgCall/PgRangeCall support one named argument (collation => ...); TYqlPgCall::DoInit + // finds it by label, so just flatten the (positional tuple, named struct) pair that + // named-arg calls get reshaped into back into a single labeled arg list. + *mustUseNamed = false; + YQL_ENSURE(args.size() == 2); + auto positional = args[0]->GetTupleNode(); + YQL_ENSURE(positional); + auto named = args[1]->GetStructNode(); + YQL_ENSURE(named); + + TVector<TNodePtr> flatArgs(positional->Elements()); + for (const auto& arg : named->GetExprs()) { + flatArgs.push_back(arg); + } + + const bool isRange = (normalizedName == "pgrangecall"); + return TNonNull(isRange + ? TNodePtr(new TYqlPgCall<true>(pos, flatArgs)) + : TNodePtr(new TYqlPgCall<false>(pos, flatArgs))); + } + auto builtinCallback = builtinFuncs.find(normalizedName); if (builtinCallback != builtinFuncs.end()) { const auto& funcInfo = builtinCallback->second; diff --git a/yql/essentials/tests/s-expressions/minirun/part4/canondata/result.json b/yql/essentials/tests/s-expressions/minirun/part4/canondata/result.json index df0cc6a07e6..60a9d0991af 100644 --- a/yql/essentials/tests/s-expressions/minirun/part4/canondata/result.json +++ b/yql/essentials/tests/s-expressions/minirun/part4/canondata/result.json @@ -251,6 +251,20 @@ "uri": "https://{canondata_backend}/1937424/d5a759bb3d448219023419bd81b51a83c51e7fb9/resource.tar.gz#test.test_InMem-Guess--Results_/results.txt" } ], + "test.test[InMem-LPartitionsByKeys-default.txt-Debug]": [ + { + "checksum": "c5970844f3cd290c9cd0f6da7c09a92e", + "size": 537, + "uri": "https://{canondata_backend}/1903885/04250d1ff8dfb75396270ac9221941404e83162d/resource.tar.gz#test.test_InMem-LPartitionsByKeys-default.txt-Debug_/opt.yql" + } + ], + "test.test[InMem-LPartitionsByKeys-default.txt-Results]": [ + { + "checksum": "3159d6b27773bf2c0b434a77a64b62b2", + "size": 337, + "uri": "https://{canondata_backend}/1903885/04250d1ff8dfb75396270ac9221941404e83162d/resource.tar.gz#test.test_InMem-LPartitionsByKeys-default.txt-Results_/results.txt" + } + ], "test.test[InMem-ListFromRangeForDates-default.txt-Debug]": [ { "checksum": "9eb52e1fd2ea6bda896e831fdf864f27", diff --git a/yql/essentials/tests/s-expressions/minirun/part9/canondata/result.json b/yql/essentials/tests/s-expressions/minirun/part9/canondata/result.json index d9ea295302a..2a3a50eef4e 100644 --- a/yql/essentials/tests/s-expressions/minirun/part9/canondata/result.json +++ b/yql/essentials/tests/s-expressions/minirun/part9/canondata/result.json @@ -209,6 +209,20 @@ "uri": "https://{canondata_backend}/1936842/97e0ab044a6a521639a649b6b781407443c8c0f8/resource.tar.gz#test.test_InMem-Iterator-default.txt-Results_/results.txt" } ], + "test.test[InMem-LPartitionsByKeysSorted-default.txt-Debug]": [ + { + "checksum": "344f7eae976694446bf07e0a90cd6f07", + "size": 566, + "uri": "https://{canondata_backend}/1814674/2df5cbfd5d5a594d9f476819b99ebb23ca848426/resource.tar.gz#test.test_InMem-LPartitionsByKeysSorted-default.txt-Debug_/opt.yql" + } + ], + "test.test[InMem-LPartitionsByKeysSorted-default.txt-Results]": [ + { + "checksum": "20b62fca5b0e72f0ca2df54d7d61a5e2", + "size": 337, + "uri": "https://{canondata_backend}/1814674/2df5cbfd5d5a594d9f476819b99ebb23ca848426/resource.tar.gz#test.test_InMem-LPartitionsByKeysSorted-default.txt-Results_/results.txt" + } + ], "test.test[InMem-ReplaceMember-default.txt-Debug]": [ { "checksum": "356532c5c41738b56f422acc79110cbc", diff --git a/yql/essentials/tests/s-expressions/suites/InMem/LPartitionsByKeys.yqls b/yql/essentials/tests/s-expressions/suites/InMem/LPartitionsByKeys.yqls new file mode 100644 index 00000000000..15e48a09766 --- /dev/null +++ b/yql/essentials/tests/s-expressions/suites/InMem/LPartitionsByKeys.yqls @@ -0,0 +1,22 @@ +( +#comment +(let config (DataSource 'config)) + +(let res_sink (DataSink 'result)) +(let list (AsList +'((Uint32 '1) (String 'a)) +'((Uint32 '2) (String 'b)) +'((Uint32 '1) (String 'c)))) + +(let keyExtractor (lambda '(x) (Nth x '0))) +(let handler (lambda '(x) (Condense1 x + (lambda '(row) '((Nth row '0) (Concat (String '"values:") (ToString (Nth row '1))))) + (lambda '(row state) (AggrNotEquals (Nth row '0) (Nth state '0))) + (lambda '(row state) '((Nth state '0) (Concat (Concat (Nth state '1) (String '" ")) (Nth row '1)))) +))) +(let data (LPartitionsByKeys list keyExtractor (Void) (Void) handler)) + +(let world (Write! world res_sink (Key) data '())) +(let world (Commit! world res_sink)) +(return world) +) diff --git a/yql/essentials/tests/s-expressions/suites/InMem/LPartitionsByKeysSorted.yqls b/yql/essentials/tests/s-expressions/suites/InMem/LPartitionsByKeysSorted.yqls new file mode 100644 index 00000000000..3d20a56d06e --- /dev/null +++ b/yql/essentials/tests/s-expressions/suites/InMem/LPartitionsByKeysSorted.yqls @@ -0,0 +1,23 @@ +( +#comment +(let config (DataSource 'config)) + +(let res_sink (DataSink 'result)) +(let list (AsList +'((Uint32 '1) (String 'a)) +'((Uint32 '2) (String 'b)) +'((Uint32 '1) (String 'c)))) + +(let keyExtractor (lambda '(x) (Nth x '0))) +(let subkeyExtractor (lambda '(x) (Nth x '1))) +(let handler (lambda '(x) (Condense1 x + (lambda '(row) '((Nth row '0) (Concat (String '"values:") (ToString (Nth row '1))))) + (lambda '(row state) (AggrNotEquals (Nth row '0) (Nth state '0))) + (lambda '(row state) '((Nth state '0) (Concat (Concat (Nth state '1) (String '" ")) (Nth row '1)))) +))) +(let data (LPartitionsByKeys list keyExtractor (Bool 'false) subkeyExtractor handler)) + +(let world (Write! world res_sink (Key) data '())) +(let world (Commit! world res_sink)) +(return world) +) diff --git a/yql/essentials/tests/sql/minirun/part0/canondata/result.json b/yql/essentials/tests/sql/minirun/part0/canondata/result.json index aaf0e9ac659..90221a7ca8b 100644 --- a/yql/essentials/tests/sql/minirun/part0/canondata/result.json +++ b/yql/essentials/tests/sql/minirun/part0/canondata/result.json @@ -951,6 +951,20 @@ "uri": "https://{canondata_backend}/1937424/d02bb271c9ab67557c669f52bc80a0a698a8a91e/resource.tar.gz#test.test_pg-YQL_20895_having_depth_3-default.txt-Results_/results.txt" } ], + "test.test[pg-collate_icu-default.txt-Debug]": [ + { + "checksum": "1652b1b4cff4644c499e6dc69fee811c", + "size": 765, + "uri": "https://{canondata_backend}/1871102/3abca1c03e1dffe75d1d9477b6aa625e8d988fcc/resource.tar.gz#test.test_pg-collate_icu-default.txt-Debug_/opt.yql" + } + ], + "test.test[pg-collate_icu-default.txt-Results]": [ + { + "checksum": "b0b6a05f71c0ea34ff54854f875e6e0f", + "size": 1731, + "uri": "https://{canondata_backend}/1871102/3abca1c03e1dffe75d1d9477b6aa625e8d988fcc/resource.tar.gz#test.test_pg-collate_icu-default.txt-Results_/results.txt" + } + ], "test.test[pg-distinct_from_pg_null-default.txt-Debug]": [ { "checksum": "28eb3cf7598e0504999612fc78525b89", diff --git a/yql/essentials/tests/sql/minirun/part10/canondata/result.json b/yql/essentials/tests/sql/minirun/part10/canondata/result.json index eebfb9f633a..c8688325f56 100644 --- a/yql/essentials/tests/sql/minirun/part10/canondata/result.json +++ b/yql/essentials/tests/sql/minirun/part10/canondata/result.json @@ -349,6 +349,27 @@ "uri": "https://{canondata_backend}/1777230/52d24600ceada6f625f4161edfba1861d2eed32d/resource.tar.gz#test.test_blocks-pg_call-default.txt-Results_/results.txt" } ], + "test.test[blocks-pg_call_collation-default.txt-Debug]": [ + { + "checksum": "06af59fe488e80891c72af615fab34d8", + "size": 567, + "uri": "https://{canondata_backend}/1936842/42d71fee557fda1f6e31e9529eb8834c94fcba49/resource.tar.gz#test.test_blocks-pg_call_collation-default.txt-Debug_/opt.yql" + } + ], + "test.test[blocks-pg_call_collation-default.txt-Peephole]": [ + { + "checksum": "451da6daf3dc9800234a3a74b45a874b", + "size": 732, + "uri": "https://{canondata_backend}/1936842/42d71fee557fda1f6e31e9529eb8834c94fcba49/resource.tar.gz#test.test_blocks-pg_call_collation-default.txt-Peephole_/opt.yql" + } + ], + "test.test[blocks-pg_call_collation-default.txt-Results]": [ + { + "checksum": "15b1272df7ce88bf65e3f49c899e5b02", + "size": 1182, + "uri": "https://{canondata_backend}/1936842/42d71fee557fda1f6e31e9529eb8834c94fcba49/resource.tar.gz#test.test_blocks-pg_call_collation-default.txt-Results_/results.txt" + } + ], "test.test[blocks-take_skip-default.txt-Debug]": [ { "checksum": "14f19770f1c340e0707f94dd4c9351f4", diff --git a/yql/essentials/tests/sql/minirun/part2/canondata/result.json b/yql/essentials/tests/sql/minirun/part2/canondata/result.json index 859cdcf18ee..46167e94552 100644 --- a/yql/essentials/tests/sql/minirun/part2/canondata/result.json +++ b/yql/essentials/tests/sql/minirun/part2/canondata/result.json @@ -966,6 +966,20 @@ "uri": "https://{canondata_backend}/1773845/5e187b5c8712eed0b26575ad1c84f3158af43d1d/resource.tar.gz#test.test_pg-with_rec_distinct-default.txt-Results_/results.txt" } ], + "test.test[pg_catalog-pg_collation-default.txt-Debug]": [ + { + "checksum": "8a748166f29bb30eab0aadaa56beb680", + "size": 1174, + "uri": "https://{canondata_backend}/212715/18867d69579bd1c3245bfeba1a9a3d827bfd47f3/resource.tar.gz#test.test_pg_catalog-pg_collation-default.txt-Debug_/opt.yql" + } + ], + "test.test[pg_catalog-pg_collation-default.txt-Results]": [ + { + "checksum": "520bb16015bf0ec808f3934ca9a32551", + "size": 733, + "uri": "https://{canondata_backend}/212715/18867d69579bd1c3245bfeba1a9a3d827bfd47f3/resource.tar.gz#test.test_pg_catalog-pg_collation-default.txt-Results_/results.txt" + } + ], "test.test[pg_catalog-pg_namespace_pg_syntax-default.txt-Debug]": [ { "checksum": "227d6e0c6d613058c88f34d5bb633d20", diff --git a/yql/essentials/tests/sql/minirun/part3/canondata/result.json b/yql/essentials/tests/sql/minirun/part3/canondata/result.json index 65982115910..f987dc88b49 100644 --- a/yql/essentials/tests/sql/minirun/part3/canondata/result.json +++ b/yql/essentials/tests/sql/minirun/part3/canondata/result.json @@ -744,6 +744,20 @@ "uri": "https://{canondata_backend}/1781765/e6df7efbe28b8ae403e3d9815e1579ecc8bb5445/resource.tar.gz#test.test_pg-aggregate_scalar_minus_zero-default.txt-Results_/results.txt" } ], + "test.test[pg-collate_std-default.txt-Debug]": [ + { + "checksum": "29b4db3f942db540ae3c350b5fffa10b", + "size": 1426, + "uri": "https://{canondata_backend}/1942173/f178108c603ab2223424f5ca3701e7ac0acc65cb/resource.tar.gz#test.test_pg-collate_std-default.txt-Debug_/opt.yql" + } + ], + "test.test[pg-collate_std-default.txt-Results]": [ + { + "checksum": "6756fa684ac9c665cdc9434bfb356e17", + "size": 3722, + "uri": "https://{canondata_backend}/1942173/f178108c603ab2223424f5ca3701e7ac0acc65cb/resource.tar.gz#test.test_pg-collate_std-default.txt-Results_/results.txt" + } + ], "test.test[pg-compare-default.txt-Debug]": [ { "checksum": "d25a02298d33a67403f42601ea688cbe", diff --git a/yql/essentials/tests/sql/sql2yql/canondata/result.json b/yql/essentials/tests/sql/sql2yql/canondata/result.json index 657f918cfad..b52bb5fe2f7 100644 --- a/yql/essentials/tests/sql/sql2yql/canondata/result.json +++ b/yql/essentials/tests/sql/sql2yql/canondata/result.json @@ -2526,6 +2526,13 @@ "uri": "https://{canondata_backend}/1942100/698d95eec1cbd4e4ada1a8da105c203a7a12bb85/resource.tar.gz#test_sql2yql.test_blocks-pg_call_/sql.yql" } ], + "test_sql2yql.test[blocks-pg_call_collation]": [ + { + "checksum": "fc5bb37bca3be522317e2108bc7a25d7", + "size": 1384, + "uri": "https://{canondata_backend}/1946324/4da69cf65420cbbd1b36d493c4602a8dfff3583d/resource.tar.gz#test_sql2yql.test_blocks-pg_call_collation_/sql.yql" + } + ], "test_sql2yql.test[blocks-pg_op]": [ { "checksum": "6fde398b5c3fed0e3fa982b753fe6018", @@ -6929,6 +6936,20 @@ "uri": "https://{canondata_backend}/1942100/698d95eec1cbd4e4ada1a8da105c203a7a12bb85/resource.tar.gz#test_sql2yql.test_pg-coalesce_/sql.yql" } ], + "test_sql2yql.test[pg-collate_icu]": [ + { + "checksum": "229206950b633a4de2d73820c449ace8", + "size": 851, + "uri": "https://{canondata_backend}/1809005/58875b1649369677cd109dd8100bb7a1c63ac607/resource.tar.gz#test_sql2yql.test_pg-collate_icu_/sql.yql" + } + ], + "test_sql2yql.test[pg-collate_std]": [ + { + "checksum": "9f81b5324fbfe2e1fc479ef1d64a87d3", + "size": 1647, + "uri": "https://{canondata_backend}/1809005/58875b1649369677cd109dd8100bb7a1c63ac607/resource.tar.gz#test_sql2yql.test_pg-collate_std_/sql.yql" + } + ], "test_sql2yql.test[pg-compare]": [ { "checksum": "a25ef2b8abd25e4b952c428bcd821b03", @@ -8819,6 +8840,13 @@ "uri": "https://{canondata_backend}/1942100/698d95eec1cbd4e4ada1a8da105c203a7a12bb85/resource.tar.gz#test_sql2yql.test_pg_catalog-pg_class_/sql.yql" } ], + "test_sql2yql.test[pg_catalog-pg_collation]": [ + { + "checksum": "57863f2853565b02219bc45355998aad", + "size": 659, + "uri": "https://{canondata_backend}/1781765/b51b8ce704a88a316462b50b2ccb16b8e6aff2d3/resource.tar.gz#test_sql2yql.test_pg_catalog-pg_collation_/sql.yql" + } + ], "test_sql2yql.test[pg_catalog-pg_database]": [ { "checksum": "277583c5b369c99209d290134ee3c60f", @@ -14425,6 +14453,11 @@ "uri": "file://test_sql_format.test_blocks-pg_call_/formatted.sql" } ], + "test_sql_format.test[blocks-pg_call_collation]": [ + { + "uri": "file://test_sql_format.test_blocks-pg_call_collation_/formatted.sql" + } + ], "test_sql_format.test[blocks-pg_op]": [ { "uri": "file://test_sql_format.test_blocks-pg_op_/formatted.sql" @@ -20407,6 +20440,13 @@ "uri": "https://{canondata_backend}/1942100/698d95eec1cbd4e4ada1a8da105c203a7a12bb85/resource.tar.gz#test_sql_negative.test_match_recognize-measures_aggr_factory-_/err_file.out" } ], + "test_sql_negative.test[pg-fail_collate_mixed-]": [ + { + "checksum": "27098fda5bb566087d95068537a89604", + "size": 146, + "uri": "https://{canondata_backend}/1925821/a0711c4178a5e0568199e19991c318a3fb84b630/resource.tar.gz#test_sql_negative.test_pg-fail_collate_mixed-_/err_file.out" + } + ], "test_sql_negative.test[pragma-classic_division_sqlfail-]": [ { "checksum": "f1b771232b48beee3305efe6ac52b431", diff --git a/yql/essentials/tests/sql/sql2yql/canondata/test_sql_format.test_blocks-pg_call_collation_/formatted.sql b/yql/essentials/tests/sql/sql2yql/canondata/test_sql_format.test_blocks-pg_call_collation_/formatted.sql new file mode 100644 index 00000000000..e5b66e0f373 --- /dev/null +++ b/yql/essentials/tests/sql/sql2yql/canondata/test_sql_format.test_blocks-pg_call_collation_/formatted.sql @@ -0,0 +1,11 @@ +$data = [ + <|x: "straße"p|>, + <|x: "foo"p|>, +]; + +SELECT + PgCall('upper', x, 'de-DE-x-icu' AS Collation), + PgCall('upper', x) +FROM + as_table($data) +; diff --git a/yql/essentials/tests/sql/suites/blocks/pg_call_collation.yql b/yql/essentials/tests/sql/suites/blocks/pg_call_collation.yql new file mode 100644 index 00000000000..028dcb31263 --- /dev/null +++ b/yql/essentials/tests/sql/suites/blocks/pg_call_collation.yql @@ -0,0 +1,6 @@ +$data = [ + <|x:"straße"p|>, + <|x:"foo"p|>, +]; + +select PgCall("upper", x, "de-DE-x-icu" as Collation), PgCall("upper", x) from as_table($data); diff --git a/yql/essentials/tests/sql/suites/pg/collate_icu.sql b/yql/essentials/tests/sql/suites/pg/collate_icu.sql new file mode 100644 index 00000000000..5939a2a52c3 --- /dev/null +++ b/yql/essentials/tests/sql/suites/pg/collate_icu.sql @@ -0,0 +1,2 @@ +--!syntax_pg +select upper('straße' COLLATE "de-DE-x-icu"), upper('straße'), upper('i' COLLATE "tr-TR-x-icu"), upper('i'); diff --git a/yql/essentials/tests/sql/suites/pg/collate_std.sql b/yql/essentials/tests/sql/suites/pg/collate_std.sql new file mode 100644 index 00000000000..aaa80ce8759 --- /dev/null +++ b/yql/essentials/tests/sql/suites/pg/collate_std.sql @@ -0,0 +1,12 @@ +--!syntax_pg +select + upper('straße' COLLATE "default"), + upper('straße' COLLATE "C"), + upper('straße' COLLATE "POSIX"), + upper('straße' COLLATE "ucs_basic"), + upper('straße' COLLATE "unicode"), + upper('i' COLLATE "default"), + upper('i' COLLATE "C"), + upper('i' COLLATE "POSIX"), + upper('i' COLLATE "ucs_basic"), + upper('i' COLLATE "unicode"); diff --git a/yql/essentials/tests/sql/suites/pg/fail_collate_mixed.cfg b/yql/essentials/tests/sql/suites/pg/fail_collate_mixed.cfg new file mode 100644 index 00000000000..b106572754f --- /dev/null +++ b/yql/essentials/tests/sql/suites/pg/fail_collate_mixed.cfg @@ -0,0 +1 @@ +xsqlfail diff --git a/yql/essentials/tests/sql/suites/pg/fail_collate_mixed.sql b/yql/essentials/tests/sql/suites/pg/fail_collate_mixed.sql new file mode 100644 index 00000000000..90a4cc1b1b5 --- /dev/null +++ b/yql/essentials/tests/sql/suites/pg/fail_collate_mixed.sql @@ -0,0 +1,4 @@ +--!syntax_pg +/* custom error:FuncCall: conflicting explicit collations: default and C*/ +select + substr('foo' COLLATE "default", 1 COLLATE "C") diff --git a/yql/essentials/tests/sql/suites/pg_catalog/pg_collation.sql b/yql/essentials/tests/sql/suites/pg_catalog/pg_collation.sql new file mode 100644 index 00000000000..066a6da9f00 --- /dev/null +++ b/yql/essentials/tests/sql/suites/pg_catalog/pg_collation.sql @@ -0,0 +1,2 @@ +--!syntax_pg +select count(*) from pg_catalog.pg_collation; diff --git a/yt/yql/providers/yt/fmr/coordinator/impl/ut/ya.make b/yt/yql/providers/yt/fmr/coordinator/impl/ut/ya.make index 8ca30f57288..6deb56f58d7 100644 --- a/yt/yql/providers/yt/fmr/coordinator/impl/ut/ya.make +++ b/yt/yql/providers/yt/fmr/coordinator/impl/ut/ya.make @@ -23,7 +23,6 @@ PEERDIR( yt/yql/providers/yt/fmr/test_tools/mock_time_provider yt/yql/providers/yt/fmr/test_tools/sorted_partitioner yt/yql/providers/yt/fmr/test_tools/table_data_service - yt/yql/providers/yt/fmr/utils/yson_block_iterator/impl yt/yql/providers/yt/gateway/fmr yt/yql/providers/yt/gateway/file yt/yql/providers/yt/codec/codegen/llvm16 diff --git a/yt/yql/providers/yt/fmr/coordinator/operation_manager/impl/map_reduce/yql_yt_map_reduce_stage_operation_manager.cpp b/yt/yql/providers/yt/fmr/coordinator/operation_manager/impl/map_reduce/yql_yt_map_reduce_stage_operation_manager.cpp index b919e13dea8..bb0c896898a 100644 --- a/yt/yql/providers/yt/fmr/coordinator/operation_manager/impl/map_reduce/yql_yt_map_reduce_stage_operation_manager.cpp +++ b/yt/yql/providers/yt/fmr/coordinator/operation_manager/impl/map_reduce/yql_yt_map_reduce_stage_operation_manager.cpp @@ -255,6 +255,9 @@ private: reduceTaskParams.Input = taskInput; reduceTaskParams.SerializedReduceJobState = operationParams.SerializedReduceJobState; reduceTaskParams.ReduceOperationSpec = operationParams.ReduceOperationSpec; + // The map stage above always shuffles via a synthetic _yql_key_hash prefix (see + // GenerateIntermediateTable), unlike a plain Reduce operation's real YT-sorted input. + reduceTaskParams.SortByHasKeyHashPrefix = true; std::vector<TFmrTableOutputRef> outputRefs; std::transform( diff --git a/yt/yql/providers/yt/fmr/coordinator/partitioner/yql_yt_reduce_partitioner.cpp b/yt/yql/providers/yt/fmr/coordinator/partitioner/yql_yt_reduce_partitioner.cpp index 8fd368852d8..69b9a40dc92 100644 --- a/yt/yql/providers/yt/fmr/coordinator/partitioner/yql_yt_reduce_partitioner.cpp +++ b/yt/yql/providers/yt/fmr/coordinator/partitioner/yql_yt_reduce_partitioner.cpp @@ -3,9 +3,19 @@ #include "yql_yt_sorted_partitioner_base.h" #include <yt/yql/providers/yt/fmr/utils/comparator/yql_yt_binary_yson_compare_impl.h> +#include <library/cpp/yson/node/node_io.h> namespace NYql::NFmr { +namespace { + +// Binary YSON key rows may contain arbitrary non-UTF8 bytes; text YSON escapes them, keeping error messages valid UTF-8. +TString FormatKeyRow(const TString& binaryYsonRow) { + return NYT::NodeToYsonString(NYT::NodeFromYsonString(binaryYsonRow)); +} + +} // namespace + TReducePartitioner::TReducePartitioner( const std::unordered_map<TFmrTableId, std::vector<TString>>& partIdsForTables, const std::unordered_map<TString, std::vector<TChunkStats>>& partIdStats, @@ -47,7 +57,7 @@ TFmrTableKeysRange TReducePartitioner::GetReadRangeFromSlices(const std::vector< YQL_ENSURE(taskRangeLeftBorderInclusive, " task range left border should always be true for reduce paritioner"); if (taskRangeLeftBorder == taskRangeRightBorder && !taskRangeRightBorderInclusive) { - ythrow yexception() << "Key " << taskRangeLeftBorder.Row << " takes all slice and is too large, cannot partition"; + ythrow yexception() << "Key " << FormatKeyRow(taskRangeLeftBorder.Row) << " takes all slice and is too large, cannot partition"; } taskRange.SetFirstKeysBound(taskRangeLeftBorder, taskRangeLeftBorderInclusive); @@ -93,7 +103,7 @@ void TReducePartitioner::CheckMaxKeySizePerSlices(const std::vector<TSlice>& sli if (*curLeftBorder != curRightBorder) { if (currentFilledSlicesWeight > maxKeySizePerPart) { - ythrow yexception() << "Key " << curLeftBorder->Row << " weighs at least " << currentFilledSlicesWeight << " bytes which is larget than maxKeySizePerPart - " << maxKeySizePerPart << " - cannot partition"; + ythrow yexception() << "Key " << FormatKeyRow(curLeftBorder->Row) << " weighs at least " << currentFilledSlicesWeight << " bytes which is larget than maxKeySizePerPart - " << maxKeySizePerPart << " - cannot partition"; } curLeftBorder = Nothing(); currentFilledSlicesWeight = 0; @@ -102,7 +112,7 @@ void TReducePartitioner::CheckMaxKeySizePerSlices(const std::vector<TSlice>& sli } } if (currentFilledSlicesWeight > maxKeySizePerPart) { - ythrow yexception() << "Key " << curLeftBorder->Row << " weighs at least " << currentFilledSlicesWeight << " bytes which is larget than maxKeySizePerPart - " << maxKeySizePerPart; + ythrow yexception() << "Key " << FormatKeyRow(curLeftBorder->Row) << " weighs at least " << currentFilledSlicesWeight << " bytes which is larget than maxKeySizePerPart - " << maxKeySizePerPart; } CurrentLastKeyWeight_ = currentFilledSlicesWeight; } diff --git a/yt/yql/providers/yt/fmr/process/yql_yt_job_fmr.cpp b/yt/yql/providers/yt/fmr/process/yql_yt_job_fmr.cpp index 6f4bc3e7d50..7d9c7a57b7f 100644 --- a/yt/yql/providers/yt/fmr/process/yql_yt_job_fmr.cpp +++ b/yt/yql/providers/yt/fmr/process/yql_yt_job_fmr.cpp @@ -113,6 +113,7 @@ void TFmrUserJob::Save(IOutputStream& s) const { ReduceOperationSpec_, IsMapReduceReducer_, IsMapReduceMap_, + SortByHasKeyHashPrefix_, ForceTableIndexMarking_ ); } @@ -132,6 +133,7 @@ void TFmrUserJob::Load(IInputStream& s) { ReduceOperationSpec_, IsMapReduceReducer_, IsMapReduceMap_, + SortByHasKeyHashPrefix_, ForceTableIndexMarking_ ); } @@ -283,6 +285,15 @@ void TFmrUserJob::FillQueueFromReduceInput() { YQL_ENSURE(ReduceOperationSpec_.Defined()); auto reduceBy = ReduceOperationSpec_->ReduceBy; auto sortBy = ReduceOperationSpec_->SortBy; + // sortBy is [...reduceBy, ...tiebreaker] (e.g. a join's "_yql_sort"), with a leading + // _yql_key_hash only for MapReduce's hash-shuffled intermediate tables (SortByHasKeyHashPrefix_, + // set by the coordinator - see TReduceTaskParams::SortByHasKeyHashPrefix) - a plain Reduce + // reads its input pre-sorted by the real reduceBy columns, with no hash involved. Either + // way, task boundaries must only constrain on the reduce-key prefix (hash-if-present + + // reduceBy) - see the comment on CompareRowToBoundaryPrefix for why comparing the tiebreaker + // too would silently split a reduce group across tasks whenever a task cut lands between + // rows that share a reduce key but differ in tiebreaker. + const size_t numBoundaryKeyColumns = (SortByHasKeyHashPrefix_ ? 1 : 0) + reduceBy.Columns.size(); for (const auto& inputTableRef : InputTables_.Inputs) { if (auto fmrInput = std::get_if<TFmrTableInputRef>(&inputTableRef)) { blockIterators.push_back(MakeIntrusive<TTableDataServiceBlockIterator>( @@ -296,7 +307,9 @@ void TFmrUserJob::FillQueueFromReduceInput() { fmrInput->IsFirstRowInclusive, fmrInput->IsLastRowInclusive, fmrInput->FirstRowKeys, - fmrInput->LastRowKeys + fmrInput->LastRowKeys, + /*readAheadChunks*/ ui64{4}, + numBoundaryKeyColumns )); } else { ythrow TFmrNonRetryableJobException() << "YtTables unsupported inside Reduce task for now"; @@ -486,7 +499,8 @@ TStatistics TFmrUserJob::DoFmrJob(const TFmrUserJobOptions& options) { sortedWriter->NotifyRowEnd(); } sortedWriter->Flush(); - TStatistics result({{OutputTables_[0], sortedWriter->GetStats()}}); + auto sortedWriterStats = sortedWriter->GetStats(); + TStatistics result({{OutputTables_[0], sortedWriterStats}}); // Direct (map-bypass) outputs at indices 1..K were already written to the real TDS via // their normal writers (see InitializeFmrUserJob) and flushed generically by diff --git a/yt/yql/providers/yt/fmr/process/yql_yt_job_fmr.h b/yt/yql/providers/yt/fmr/process/yql_yt_job_fmr.h index 54a8ff4ffde..a0bf4c70c07 100644 --- a/yt/yql/providers/yt/fmr/process/yql_yt_job_fmr.h +++ b/yt/yql/providers/yt/fmr/process/yql_yt_job_fmr.h @@ -116,6 +116,14 @@ public: IsMapReduceMap_ = value; } + // Whether this Reduce job's input is sorted by [_yql_key_hash, ...ReduceOperationSpec's + // SortBy] (a MapReduce operation's reduce stage) or by SortBy alone with no hash column at + // all (a plain Reduce operation reading real YT-sorted input) - see + // TReduceTaskParams::SortByHasKeyHashPrefix, which this is set from. + void SetSortByHasKeyHashPrefix(bool value) { + SortByHasKeyHashPrefix_ = value; + } + // Set by the gateway (from execCtx->InputTables_, before this job's state is serialized to // workers) whenever the ORIGINAL OPERATION has more than one distinct input position (e.g. a // JOIN's mapper reading the same or different tables via multiple sections). This must be a @@ -180,6 +188,7 @@ private: TMaybe<TReduceOperationSpec> ReduceOperationSpec_; bool IsMapReduceReducer_ = false; bool IsMapReduceMap_ = false; + bool SortByHasKeyHashPrefix_ = false; bool ForceTableIndexMarking_ = false; // End of serializable part diff --git a/yt/yql/providers/yt/fmr/proto/request_options.proto b/yt/yql/providers/yt/fmr/proto/request_options.proto index aacb3822b15..aafb3f2a32a 100644 --- a/yt/yql/providers/yt/fmr/proto/request_options.proto +++ b/yt/yql/providers/yt/fmr/proto/request_options.proto @@ -300,6 +300,11 @@ message TReduceTaskParams { repeated TFmrTableOutputRef Output = 2; bytes SerializedReduceJobState = 3; TReduceOperationSpec ReduceOperationSpec = 4; + // Whether Input's rows are sorted by [_yql_key_hash, ...ReduceOperationSpec.SortBy] (true for + // a MapReduce operation's reduce stage, which shuffles via a synthetic hash column) or by + // ReduceOperationSpec.SortBy alone (a plain Reduce operation, which reads real YT-sorted + // input with no hash column at all). + bool SortByHasKeyHashPrefix = 5; } message TSortOperationParams { diff --git a/yt/yql/providers/yt/fmr/request_options/proto_helpers/yql_yt_request_proto_helpers.cpp b/yt/yql/providers/yt/fmr/request_options/proto_helpers/yql_yt_request_proto_helpers.cpp index 855b572b486..c8f992255b6 100644 --- a/yt/yql/providers/yt/fmr/request_options/proto_helpers/yql_yt_request_proto_helpers.cpp +++ b/yt/yql/providers/yt/fmr/request_options/proto_helpers/yql_yt_request_proto_helpers.cpp @@ -717,6 +717,7 @@ NProto::TReduceTaskParams ReduceTaskParamsToProto(const TReduceTaskParams& reduc protoReduceTaskParams.SetSerializedReduceJobState(reduceTaskParams.SerializedReduceJobState); auto protoReduceOperationSpec = ReduceOperationSpecToProto(reduceTaskParams.ReduceOperationSpec); protoReduceTaskParams.MutableReduceOperationSpec()->Swap(&protoReduceOperationSpec); + protoReduceTaskParams.SetSortByHasKeyHashPrefix(reduceTaskParams.SortByHasKeyHashPrefix); return protoReduceTaskParams; } @@ -730,6 +731,7 @@ TReduceTaskParams ReduceTaskParamsFromProto(const NProto::TReduceTaskParams& pro reduceTaskParams.Output = outputTables; reduceTaskParams.SerializedReduceJobState = protoReduceTaskParams.GetSerializedReduceJobState(); reduceTaskParams.ReduceOperationSpec = ReduceOperationSpecFromProto(protoReduceTaskParams.GetReduceOperationSpec()); + reduceTaskParams.SortByHasKeyHashPrefix = protoReduceTaskParams.GetSortByHasKeyHashPrefix(); return reduceTaskParams; } diff --git a/yt/yql/providers/yt/fmr/request_options/yql_yt_request_options.h b/yt/yql/providers/yt/fmr/request_options/yql_yt_request_options.h index a32e521ba36..a2694941ae3 100644 --- a/yt/yql/providers/yt/fmr/request_options/yql_yt_request_options.h +++ b/yt/yql/providers/yt/fmr/request_options/yql_yt_request_options.h @@ -538,6 +538,11 @@ struct TReduceTaskParams { std::vector<TFmrTableOutputRef> Output; TString SerializedReduceJobState; TReduceOperationSpec ReduceOperationSpec; + // Whether Input's rows are sorted by [_yql_key_hash, ...ReduceOperationSpec.SortBy] (true for + // a MapReduce operation's reduce stage, which shuffles via a synthetic hash column) or by + // ReduceOperationSpec.SortBy alone (a plain Reduce operation, which reads real YT-sorted + // input with no hash column at all). + bool SortByHasKeyHashPrefix = false; }; // Service column added by map stage to hash-route rows to the correct reducer. diff --git a/yt/yql/providers/yt/fmr/utils/yson_block_iterator/impl/ut/ya.make b/yt/yql/providers/yt/fmr/utils/yson_block_iterator/impl/ut/ya.make new file mode 100644 index 00000000000..61a5d2ff9f6 --- /dev/null +++ b/yt/yql/providers/yt/fmr/utils/yson_block_iterator/impl/ut/ya.make @@ -0,0 +1,17 @@ +UNITTEST() + +SRCS( + yql_yt_yson_tds_block_iterator_ut.cpp +) + +PEERDIR( + yt/yql/providers/yt/fmr/utils/yson_block_iterator/impl + yt/yql/providers/yt/fmr/request_options + yt/yql/providers/yt/fmr/table_data_service/local/impl + yt/yql/providers/yt/fmr/test_tools/yson + yt/yql/providers/yt/fmr/utils +) + +YQL_LAST_ABI_VERSION() + +END() diff --git a/yt/yql/providers/yt/fmr/utils/yson_block_iterator/impl/ut/yql_yt_yson_tds_block_iterator_ut.cpp b/yt/yql/providers/yt/fmr/utils/yson_block_iterator/impl/ut/yql_yt_yson_tds_block_iterator_ut.cpp new file mode 100644 index 00000000000..e2f92d30156 --- /dev/null +++ b/yt/yql/providers/yt/fmr/utils/yson_block_iterator/impl/ut/yql_yt_yson_tds_block_iterator_ut.cpp @@ -0,0 +1,88 @@ +#include <library/cpp/testing/unittest/registar.h> + +#include <yt/yql/providers/yt/fmr/utils/yson_block_iterator/impl/yql_yt_yson_tds_block_iterator.h> +#include <yt/yql/providers/yt/fmr/test_tools/yson/yql_yt_yson_helpers.h> +#include <yt/yql/providers/yt/fmr/table_data_service/local/impl/yql_yt_table_data_service_local.h> +#include <yt/yql/providers/yt/fmr/utils/yql_yt_table_data_service_key.h> + +namespace NYql::NFmr { + +namespace { + +// Two rows sharing the same reduce key (_yql_key_hash=1, k=5) but differing only in the +// tiebreaker column _yql_sort - e.g. a join's build-side row (sort=0) and one of its probe-side +// rows (sort=1). Mirrors the real production row shape from yt/yql/providers/yt/fmr/job/impl/ +// yql_yt_reduce_reader.cpp's dumps. +const TVector<TString> KeyColumns = {"_yql_key_hash", "k", "_yql_sort"}; + +std::vector<TIndexedBlock> ReadAllBlocks(TTableDataServiceBlockIterator::TPtr iter) { + std::vector<TIndexedBlock> blocks; + TIndexedBlock block; + while (iter->NextBlock(block)) { + blocks.push_back(block); + } + return blocks; +} + +ui64 CountRows(const std::vector<TIndexedBlock>& blocks) { + ui64 total = 0; + for (const auto& b : blocks) { + total += b.Rows.size(); + } + return total; +} + +} // namespace + +Y_UNIT_TEST_SUITE(TableDataServiceBlockIteratorBoundaryTests) { + Y_UNIT_TEST(TieBreakerColumnMustNotSplitReduceGroupAcrossBoundary) { + auto tds = MakeLocalTableDataService(); + const TString tableId = "t"; + const TString partId = "p0"; + const TString group = GetTableDataServiceGroup(tableId, partId); + const TString chunkId = GetTableDataServiceChunkId(0, TString()); + + const TString chunkText = + "{\"_yql_key_hash\"=1u;\"k\"=5u;\"_yql_sort\"=0u};\n" + "{\"_yql_key_hash\"=1u;\"k\"=5u;\"_yql_sort\"=1u};\n"; + tds->Put(group, chunkId, GetBinaryYson(chunkText)).GetValueSync(); + + // The task boundary itself is built from the sort=0 row's real key - exactly what + // happens in production when that row is the last physical row before a task cut. + const TString boundaryRow = GetBinaryYson( + "{\"_yql_key_hash\"=1u;\"k\"=5u;\"_yql_sort\"=0u}", + NYson::EYsonType::Node + ); + + std::vector<TTableRange> tableRanges = {TTableRange{.PartId = partId, .MinChunk = 0, .MaxChunk = 1}}; + std::vector<ESortOrder> sortOrders(KeyColumns.size(), ESortOrder::Ascending); + + // Without a cap (numBoundaryKeyColumns defaults to comparing every key column, including + // the tiebreaker): reproduces the bug - the sort=1 row looks like it's past the boundary + // and gets dropped, even though it shares the exact same reduce key. + { + auto iter = MakeIntrusive<TTableDataServiceBlockIterator>( + tableId, tableRanges, tds, KeyColumns, sortOrders, + /*neededColumns*/ TVector<TString>{}, /*serializedColumnGroupsSpec*/ TString(), + /*isFirstRowKeysInclusive*/ Nothing(), /*isLastRowKeysInclusive*/ true, + /*firstRowKeys*/ Nothing(), /*lastRowKeys*/ boundaryRow + ); + UNIT_ASSERT_VALUES_EQUAL(CountRows(ReadAllBlocks(iter)), 1); + } + + // With the fix - numBoundaryKeyColumns=2 caps comparison to [_yql_key_hash, k], excluding + // _yql_sort: both rows of the reduce group survive the same boundary. + { + auto iter = MakeIntrusive<TTableDataServiceBlockIterator>( + tableId, tableRanges, tds, KeyColumns, sortOrders, + /*neededColumns*/ TVector<TString>{}, /*serializedColumnGroupsSpec*/ TString(), + /*isFirstRowKeysInclusive*/ Nothing(), /*isLastRowKeysInclusive*/ true, + /*firstRowKeys*/ Nothing(), /*lastRowKeys*/ boundaryRow, + /*readAheadChunks*/ ui64{4}, /*numBoundaryKeyColumns*/ size_t{2} + ); + UNIT_ASSERT_VALUES_EQUAL(CountRows(ReadAllBlocks(iter)), 2); + } + } +} + +} // namespace NYql::NFmr diff --git a/yt/yql/providers/yt/fmr/utils/yson_block_iterator/impl/ya.make b/yt/yql/providers/yt/fmr/utils/yson_block_iterator/impl/ya.make index afb3020f720..7f68fea4899 100644 --- a/yt/yql/providers/yt/fmr/utils/yson_block_iterator/impl/ya.make +++ b/yt/yql/providers/yt/fmr/utils/yson_block_iterator/impl/ya.make @@ -21,3 +21,7 @@ PEERDIR( YQL_LAST_ABI_VERSION() END() + +RECURSE_FOR_TESTS( + ut +) diff --git a/yt/yql/providers/yt/fmr/utils/yson_block_iterator/impl/yql_yt_yson_tds_block_iterator.cpp b/yt/yql/providers/yt/fmr/utils/yson_block_iterator/impl/yql_yt_yson_tds_block_iterator.cpp index 584614844d9..e87a9791cbb 100644 --- a/yt/yql/providers/yt/fmr/utils/yson_block_iterator/impl/yql_yt_yson_tds_block_iterator.cpp +++ b/yt/yql/providers/yt/fmr/utils/yson_block_iterator/impl/yql_yt_yson_tds_block_iterator.cpp @@ -21,7 +21,8 @@ TTableDataServiceBlockIterator::TTableDataServiceBlockIterator( TMaybe<bool> isLastRowKeysInclusive, TMaybe<TString> firstRowKeys, TMaybe<TString> lastRowKeys, - ui64 readAheadChunks + ui64 readAheadChunks, + TMaybe<size_t> numBoundaryKeyColumns ) : TableId_(std::move(tableId)) , TableRanges_(std::move(tableRanges)) @@ -31,6 +32,7 @@ TTableDataServiceBlockIterator::TTableDataServiceBlockIterator( , NeededColumns_(std::move(neededColumns)) , SerializedColumnGroupsSpec_(std::move(serializedColumnGroupsSpec)) , ReadAheadChunks_(readAheadChunks) + , NumBoundaryKeyColumns_(numBoundaryKeyColumns.GetOrElse(KeyColumns_.size())) { if (SortOrders_.empty()) { SortOrders_.assign(KeyColumns_.size(), ESortOrder::Ascending); @@ -38,6 +40,7 @@ TTableDataServiceBlockIterator::TTableDataServiceBlockIterator( if (SortOrders_.size() != KeyColumns_.size()) { ythrow yexception() << "SortOrders and KeyColumns sizes are different"; } + Y_ENSURE(NumBoundaryKeyColumns_ <= KeyColumns_.size(), "numBoundaryKeyColumns must not exceed keyColumns size"); if (firstRowKeys) { FirstBoundary_ = TFmrTableKeysBoundary(*firstRowKeys, KeyColumns_, SortOrders_); @@ -144,24 +147,29 @@ bool IsNullColumnValue(TStringBuf blob, const TColumnOffsetRange& range) { return SliceRange(blob, range) == TStringBuf(&EntitySymbol, 1); } -// FirstRowKeys/LastRowKeys boundaries are built by the coordinator from a task's chunk-stats -// key columns (e.g. [_yql_key_hash, ...ReduceBy] for MapReduce reduce tasks), which can be a -// strict PREFIX of the fuller key column set (e.g. [_yql_key_hash, ...SortBy]) this iterator -// parses actual rows with. TFmrTableKeysBoundary parses its own Row bytes with the SAME -// (fuller) key columns as the row markup, so any column beyond the boundary's real prefix comes -// out with an IsValid()==false range - not because the value is legitimately null, but because -// the boundary blob simply never encoded it. Comparing that as "boundary < row" (the usual -// null-handling rule) would incorrectly exclude real rows that land exactly on the boundary, so -// this comparison stops at the boundary's own valid prefix instead of delegating to the -// generic null-aware CompareKeyRowsAcrossYsonBlocks. +// FirstRowKeys/LastRowKeys boundaries are built by the coordinator from a task's chunk-stats, +// i.e. from an actual physical row's real key - for MapReduce reduce tasks over joins that key +// is [_yql_key_hash, ...ReduceBy, ...SortBy tiebreaker] in full, tiebreaker included, because +// that's what TReducePartitioner sorts intermediate data by (see +// yql_yt_map_reduce_stage_operation_manager.cpp). It is NOT reliably a strict prefix that omits +// the tiebreaker: whichever physical row happens to land on a task cut contributes its own real +// tiebreaker value to the boundary blob. So this can't stop at "wherever the boundary blob runs +// out of valid columns" (that already-invalid range doesn't reliably appear here) - it must be +// told explicitly, via numBoundaryKeyColumns, how many leading columns actually define a reduce +// group, and ignore everything past that. Comparing the tiebreaker as a real bound column would +// silently split a reduce group in half whenever a task cut lands between two physical rows that +// share a reduce key but differ in tiebreaker (e.g. a join's build-side row vs. its probe-side +// rows) - each side would look like it's on the "wrong side" of the other's boundary. int CompareRowToBoundaryPrefix( TStringBuf rowBlob, const TRowIndexMarkup& row, TStringBuf boundaryBlob, const TRowIndexMarkup& boundaryMarkup, - const std::vector<ESortOrder>& sortOrders + const std::vector<ESortOrder>& sortOrders, + size_t numBoundaryKeyColumns ) { - for (ui64 colIdx = 0; colIdx + 1 < boundaryMarkup.size(); ++colIdx) { + const size_t limit = std::min<size_t>(numBoundaryKeyColumns, boundaryMarkup.empty() ? 0 : boundaryMarkup.size() - 1); + for (ui64 colIdx = 0; colIdx < limit; ++colIdx) { const auto& boundaryRange = boundaryMarkup[colIdx]; if (!boundaryRange.IsValid()) { // Boundary doesn't encode this column (or any further one, since it's a prefix) - @@ -204,7 +212,8 @@ bool TTableDataServiceBlockIterator::RowInKeyBounds(const TString& blob, const T row, FirstBoundary_->Row, FirstBoundary_->Markup, - SortOrders_ + SortOrders_, + NumBoundaryKeyColumns_ ); if (c < 0) { // if row < first boundary return false; @@ -218,7 +227,8 @@ bool TTableDataServiceBlockIterator::RowInKeyBounds(const TString& blob, const T row, LastBoundary_->Row, LastBoundary_->Markup, - SortOrders_ + SortOrders_, + NumBoundaryKeyColumns_ ); if (c > 0) { // if row > last boundary return false; diff --git a/yt/yql/providers/yt/fmr/utils/yson_block_iterator/impl/yql_yt_yson_tds_block_iterator.h b/yt/yql/providers/yt/fmr/utils/yson_block_iterator/impl/yql_yt_yson_tds_block_iterator.h index 4ab202869fc..8f661e5ec01 100644 --- a/yt/yql/providers/yt/fmr/utils/yson_block_iterator/impl/yql_yt_yson_tds_block_iterator.h +++ b/yt/yql/providers/yt/fmr/utils/yson_block_iterator/impl/yql_yt_yson_tds_block_iterator.h @@ -27,8 +27,14 @@ public: TMaybe<bool> isLastRowKeysInclusive = Nothing(), TMaybe<TString> firstRowKeys = Nothing(), TMaybe<TString> lastRowKeys = Nothing(), - ui64 readAheadChunks = 4 - + ui64 readAheadChunks = 4, + // How many leading keyColumns actually define a reduce group (e.g. _yql_key_hash + + // reduceBy, excluding a join's _yql_sort tiebreaker). Boundary comparisons stop after + // this many columns instead of running to the end of keyColumns - see the comment on + // CompareRowToBoundaryPrefix for why relying on the boundary blob happening to omit + // trailing columns is not safe. Defaults to comparing every column in keyColumns, which + // is correct whenever keyColumns has no trailing tiebreaker (e.g. non-reduce contexts). + TMaybe<size_t> numBoundaryKeyColumns = Nothing() ); ~TTableDataServiceBlockIterator() final; @@ -73,6 +79,7 @@ private: std::vector<TString> GroupNamesToRead_; ui64 ReadAheadChunks_ = 4; + const size_t NumBoundaryKeyColumns_; std::deque<TPrefetchEntry> PrefetchQueue_; }; diff --git a/yt/yt/client/unittests/mock/table_reader.cpp b/yt/yt/client/unittests/mock/table_reader.cpp index 1e314884241..ef3c44d0bde 100644 --- a/yt/yt/client/unittests/mock/table_reader.cpp +++ b/yt/yt/client/unittests/mock/table_reader.cpp @@ -10,7 +10,7 @@ namespace NYT::NApi { TMockTableReader::TMockTableReader(NTableClient::TTableSchemaPtr schema) : Schema_(std::move(schema)) , NameTable_(NTableClient::TNameTable::FromSchema(*Schema_)) -{} +{ } const NTableClient::TNameTablePtr& TMockTableReader::GetNameTable() const { |
